前面一篇文章分析了檔案安全上下文關聯過程。但是在SEAndroid中,除了要給檔案關聯安全上下文外,還需要給程序關聯安全上下文,因為隻有當程序和檔案都關聯安全上下文之後,SEAndroid安全政策才能發揮作用。也就是說,當一個程序試圖通路一個檔案時,SEAndroid會将程序和檔案的安全上下文提取出來,根據安全政策規則,決定是否允許通路。本文就詳細分析SEAndroid的程序安全上下文的關聯過程。
老羅的新浪微網誌:http://weibo.com/shengyangluo,歡迎關注!
在傳統的Linux系統中,每一個應用程式都對應有一個可執行檔案。在這種情況下,我們就可以在安全政策中設定一個規則:當一個可執行檔案加載到一個程序中執行時,該程序的安全上下文就設定為指定的值。也就是說,我們可以在安全政策中靜态地為程序設定安全上下文。然而,這種程序安全上下文設定方式不适合于Android系統中的應用程式程序。從前面Android系統程序Zygote啟動過程的源代碼分析和Android應用程式程序啟動過程的源代碼分析這兩篇文章可以知道,Android系統中的應用程式程序都是由Zygote程序fork出來。這些應用程式程序被Zygote程序fork出來之後,不像傳統Linux的應用程式程序一樣,會通過exec系統調用将對應的可執行檔案加載起來執行。這樣就會使得Zygote程序及其建立的所有應用程式程序對應的可執行檔案均為/system/bin/app_process。由于我們卻需要給不同的應用程式設定不同的安全上下文,以便給它們賦予不同的安全權限,是以我們需要在應用程式程序建立出來之後動态地設定它的安全上下文。
根據上面的描述,我們就總結出,在SEAndroid安全機制中,程序的安全上下文設定分為靜态和動态兩種方式,如圖1所示:
圖1 SEAndroid安全機制中的程序安全上下文關聯方式
接下來,我們就分别描述這兩種程序安全上下文設定方式。
1. 為獨立程序靜态地設定安全上下文
Android系統的第一個程序是init,其它所有的程序都是由init程序直接或者間接fork出來的。我們在前面SEAndroid安全機制中的檔案安全上下文關聯分析一篇文章提到,一個新建立的檔案的安全上下文在預設情況下來自于其父目錄。與此類似,一個新建立的程序的安全上下文在預設情況下來自于其父程序。是以,我們就先看看系統中的第一個程序init的安全上下文是如何設定的。
檢視init程序的啟動腳本system/core/rootdir/init.rc,可以看到以下的内容:
[plain] view plain copy
- on early-init
- ......
- # Set the security context for the init process.
- # This should occur before anything else (e.g. ueventd) is started.
- setcon u:r:init:s0
- ......
這段腳本的意思是init程序啟動之後就馬上調用函數setcon将自己的安全上下文設定為“u:r:init:s0”,即将init程序的domain指定為init。
接下來我們再看看init這個domain的定義,在external/sepolicy/init.te檔案中:
[plain] view plain copy
- # init switches to init domain (via init.rc).
- type init, domain;
- permissive init;
- # init is unconfined.
- unconfined_domain(init)
- tmpfs_domain(init)
- # add a rule to handle unlabelled mounts
- allow init unlabeled:filesystem mount;
第一個type語句将domain設定為init的屬性,這意味着init是用來描述程序的安全上下文的。
第二個permissive語句指定當domain為init的程序違反SEAndroid安全政策通路資源時,隻進行日志輸出,而不是拒絕執行。由于這裡列出來的内容是來自Android 4.3的,而Android 4.3開啟的是Permissive的SEAndroid模式,是以這裡會看到這樣的一個permissive語句。
第三個unconfined_domain語句是一個宏,定義在external/sepolicy/te_macros檔案中,用來指定init是一個不受限制的domain,即它可以通路系統中的大部分資源。它的定義如下所示:
[plain] view plain copy
- #####################################
- # unconfined_domain(domain)
- # Allow the specified domain to do anything.
- #
- define(`unconfined_domain', `
- typeattribute $1 mlstrustedsubject;
- typeattribute $1 unconfineddomain;
- ')
第四個tmpfs_domain語句也是定義在external/sepolicy/te_macros檔案中的一個宏,用來指定當domain為init的程序在type為tmpfs的目錄中建立檔案時,将新建立的檔案的type設定為init_tmpfs,并且允許domain為init的程序對它們進行讀和執行。它的定義如下所示:
[plain] view plain copy
- #####################################
- # tmpfs_domain(domain)
- # Define and allow access to a unique type for
- # this domain when creating tmpfs / shmem / ashmem files.
- define(`tmpfs_domain', `
- type $1_tmpfs, file_type;
- type_transition $1 tmpfs:file $1_tmpfs;
- # Map with PROT_EXEC.
- allow $1 $1_tmpfs:file { read execute execmod };
- ')
第5個allow語句允許domain為init的程序mount未指定安全上下文的檔案系統時,将其安全上下文設定為unlabeled。
上面列出的腳本就指明了init程序的安全上下文,以及它所具有的SEAndroid權限。接下來我們就再來看看負責建立應用程式程序的Zygote程序的安全上下文的設定過程。
Zygote程序是由init程序建立的,它的啟動指令定義在檔案system/core/rootdir/init.rc中,如下所示:
[plain] view plain copy
- service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
- class main
- socket zygote stream 660 root system
- onrestart write /sys/android_power/request_state wake
- onrestart write /sys/power/state on
- onrestart restart media
- onrestart restart netd
這意味着Zygote程序對應的可執行檔案為/system/bin/app_process。
通過檢查external/sepolicy/file_contexts,我們可以發現檔案/system/bin/app_process的安全上下文為“u:object_r:zygote_exec:s0”,如下所示:
[plain] view plain copy
- /system/bin/app_process u:object_r:zygote_exec:s0
也就是說,檔案/system/bin/app_process的type為zygote_exec。
在external/sepolicy/zygote.te檔案中,定義了一個名稱為zygote的domain,以及名稱為zygote_exec的type,如下所示:
[plain] view plain copy
- # zygote
- type zygote, domain;
- type zygote_exec, exec_type, file_type;
- permissive zygote;
- init_daemon_domain(zygote)
- unconfined_domain(zygote)
第一個type語句将domain設定為zygote的屬性,表明zygote是用來描述程序的安全上下文的。
第二個type語句将exec_type和file_type設定為zygote_exec的屬性,表明zygote_exec是用來描述可執行檔案的安全上下文的。
第三個permissive語句同樣是表明當domain為zygote的程序違反SEAndroid安全政策通路資源時,隻進行日志輸出,而不是拒絕執行。
第四個init_daemon_domain語句是一個宏,定義在檔案external/sepolicy/te_macros中,用來設定zygote這個domain的權限,它的定義如下所示:
[plain] view plain copy
- #####################################
- # init_daemon_domain(domain)
- # Set up a transition from init to the daemon domain
- # upon executing its binary.
- define(`init_daemon_domain', `
- domain_auto_trans(init, $1_exec, $1)
- tmpfs_domain($1)
- ')
宏init_daemon_domain由另外兩個宏tmpfs_domain和domain_auto_trans組成。宏tmpfs_domain的作用在前面已經分析過了,接下來我們重點關注宏domain_auto_trans的定義,也是在檔案external/sepolicy/te_macros中,如下所示:
[plain] view plain copy
- #####################################
- # domain_auto_trans(olddomain, type, newdomain)
- # Automatically transition from olddomain to newdomain
- # upon executing a file labeled with type.
- #
- define(`domain_auto_trans', `
- # Allow the necessary permissions.
- domain_trans($1,$2,$3)
- # Make the transition occur by default.
- type_transition $1 $2:process $3;
- ')
第二個type_transition語句指定當一個domain為init的程序建立一個子程序執行一個type為zygote_exec的檔案時,将該子程序的domain設定為zygote,而不是繼承父程序的domain。
第一個domain_trans語句是一個宏,也是定義在external/sepolicy/te_macros中,用來允許程序的domain從init修改為zygote,它的定義如下所示:
[plain] view plain copy
- #####################################
- # domain_trans(olddomain, type, newdomain)
- # Allow a transition from olddomain to newdomain
- # upon executing a file labeled with type.
- # This only allows the transition; it does not
- # cause it to occur automatically - use domain_auto_trans
- # if that is what you want.
- #
- define(`domain_trans', `
- # Old domain may exec the file and transition to the new domain.
- allow $1 $2:file { getattr open read execute };
- allow $1 $3:process transition;
- # New domain is entered by executing the file.
- allow $3 $2:file { entrypoint read execute };
- # New domain can send SIGCHLD to its caller.
- allow $3 $1:process sigchld;
- # Enable AT_SECURE, i.e. libc secure mode.
- dontaudit $1 $3:process noatsecure;
- # XXX dontaudit candidate but requires further study.
- allow $1 $3:process { siginh rlimitinh };
- ')
其中,最重要的是以下兩個allow語句:
[plain] view plain copy
- allow $1 $3:process transition;
- allow $3 $2:file { entrypoint read execute };
第一個allow語句允許domain為init的程序将domain修改為zygote。
第二個allow語句允許type為zygote_exec的可執行檔案作為進入zygote這個domain的入口點。
概括來說,在external/sepolicy/zygote.te檔案中,通過init_daemon_domain指明了Zygote程序的domain為zygote。我們可以從Zygote程序的建立過程來了解這些安全政策。首先, Zygote程序是由init程序fork出來的。在fork出來的時候,Zygote程序的domain來自于父程序init的domain,即此時Zygote程序的domain為init。接下來,剛剛fork出來的Zygote程序會通過系統接口exec将檔案/system/bin/app_process加載進來執行。由于上面提到的allow和type_transition規則的存在,使得檔案/system/bin/app_process被exec到剛剛fork出來的Zygote程序的時候,它的domain自動地從init轉換為zygote。這樣我們就可以給init程序和Zygote程序設定不同的domain,以便可以給它們賦予不同的SEAndroid安全權限。
回到external/sepolicy/zygote.te檔案中,最後一個unconfined_domain語句同樣是将zygote這個domain設定為一個不受限的domain,以便它可以通路系統中的大部分資源。
這樣,我們就以init和Zygote程序的安全上下文設定過程為例,說明了那些對應有不同可執行檔案的程序的安全上下文的關聯過程了。這些程序的安全上下文的設定方式與傳統的Linux系統的應用程式程序的設定方式是一緻的。接下來我們就再來分析Android系統的應用程式程序的安全上下文的關聯過程。
2. 為應用程式程序設定安全上下文
從前面Android應用程式程序啟動過程的源代碼分析一篇文章可以知道,應用程式程序是由ActivityManagerService請求Zygote程序建立的。ActivityManagerService在請求Zygote程序建立應用程式程序的時候,會傳遞很多參數,例如應用程式在安裝時配置設定到的uid和gid。增加了SEAndroid安全機制之後,ActivityManagerService傳遞給Zygote程序的參數包含了一個seinfo。這個seinfo與我們在前面SEAndroid安全機制中的檔案安全上下文關聯分析一文中介紹的seinfo是一樣的,不過它的作用是用來設定應用程式程序的安全上下文,而不是設定應用程式資料檔案的安全上下文。接下來我們就分析應用程式程序的安全上下文設定過程。
從前面Android應用程式程序啟動過程的源代碼分析一文的Step 1可以知道,當ActivityMangerService需要建立應用程式程序的時候,就會調用ActivityMangerService類的成員函數startProcessLocked,它的實作如下所示:
[java] view plain copy
- public final class ActivityManagerService extends ActivityManagerNative
- implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
- ......
- private final void startProcessLocked(ProcessRecord app,
- String hostingType, String hostingNameStr) {
- ......
- try {
- ......
- // Start the process. It will either succeed and return a result containing
- // the PID of the new process, or else throw a RuntimeException.
- Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
- app.processName, uid, uid, gids, debugFlags, mountExternal,
- app.info.targetSdkVersion, app.info.seinfo, null);
- ......
- } catch (RuntimeException e) {
- ......
- }
- }
- ......
- }
這個函數定義在檔案frameworks/base/services/java/com/android/server/am/ActivityManagerService.java中。
參數app指向的是一個ProcessRecord對象,用來描述正在建立的應用程式程序。其中,它的成員變量info指向的是一個ApplicationInfo對象。從前面SEAndroid安全機制中的檔案安全上下文關聯分析一文可以知道,這個ApplicationInfo對象有一個類型為String的成員變量seinfo,是在應用程式安裝的時候通過解析檔案mac_permissions.xml獲得的。
ActivityManagerService類的成員函數startProcessLocked通過調用Process類的靜态成員函數start來建立應用程式程序,其中就包含了要建立的應用程式程序的各種參數。從前面Android應用程式程序啟動過程的源代碼分析一篇文章可以知道,這些參數會通過Socket IPC傳遞給Zygote程序。最後,Zygote程序會通過調用ZygoteConnection類的成員函數runOnce來執行建立應用程式程序的工作。
ZygoteConnection類的成員函數runOnce的實作如下所示:
[java] view plain copy
- class ZygoteConnection {
- ......
- boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
- ......
- try {
- args = readArgumentList();
- ......
- } catch (IOException ex) {
- ......
- }
- ......
- try {
- parsedArgs = new Arguments(args);
- ......
- pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
- parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
- parsedArgs.niceName);
- } catch (IOException ex) {
- ......
- } catch (ErrnoException ex) {
- ......
- } catch (IllegalArgumentException ex) {
- ......
- } catch (ZygoteSecurityException ex) {
- ......
- }
- ......
- }
- ......
- }
這個函數定義在檔案frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java中。
ZygoteConnection類的成員函數runOnce首先是通過調用另外一個成員函數readArgumentList讀取ActivityManagerService發送過來的應用程式程序建立參數args,接着再建立一個Arguments對象來解析該參數。解析後得到的參數傳遞給Zygote類的靜态成員函數forkAndSpecialize,以便後者可以執行建立應用程式程序的工作。
Zygote類的靜态成員函數forkAndSpecialize的實作如下所示:
[java] view plain copy
- public class Zygote {
- ......
- public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags,
- int[][] rlimits, int mountExternal, String seInfo, String niceName) {
- preFork();
- int pid = nativeForkAndSpecialize(
- uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName);
- postFork();
- return pid;
- }
- native public static int nativeForkAndSpecialize(int uid, int gid, int[] gids, int debugFlags,
- int[][] rlimits, int mountExternal, String seInfo, String niceName);
- ......
- }
這個函數定義在檔案libcore/dalvik/src/main/java/dalvik/system/Zygote.java中。
Zygote類的靜态成員函數forkAndSpecialize的實作很簡單,它通過調用另外一個JNI函數nativeForkAndSpecialize來執行建立應用程式程序的工作。
Zygote類的JNI函數nativeForkAndSpecialize的由C++層的函數Dalvik_dalvik_system_Zygote_forkAndSpecialize來實作,如下所示:
[cpp] view plain copy
- static void Dalvik_dalvik_system_Zygote_forkAndSpecialize(const u4* args,
- JValue* pResult)
- {
- pid_t pid;
- pid = forkAndSpecializeCommon(args, false);
- RETURN_INT(pid);
- }
這個函數定義在檔案dalvik/vm/native/dalvik_system_Zygote.cpp中。
注意,Zygote類的JNI函數nativeForkAndSpecialize在調用的過程中,傳遞進來的參數都被儲存在函數Dalvik_dalvik_system_Zygote_forkAndSpecialize的參數args指向的一塊記憶體中。
函數Dalvik_dalvik_system_Zygote_forkAndSpecialize通過調用另外一個函數forkAndSpecializeCommon來執行建立應用程式程序的工作,它的實作如下所示:
[cpp] view plain copy
- static pid_t forkAndSpecializeCommon(const u4* args, bool isSystemServer)
- {
- pid_t pid;
- uid_t uid = (uid_t) args[0];
- gid_t gid = (gid_t) args[1];
- ......
- char *seInfo = NULL;
- char *niceName = NULL;
- if (isSystemServer) {
- ......
- } else {
- ......
- StringObject* seInfoObj = (StringObject*)args[6];
- if (seInfoObj) {
- seInfo = dvmCreateCstrFromString(seInfoObj);
- ......
- }
- StringObject* niceNameObj = (StringObject*)args[7];
- if (niceNameObj) {
- niceName = dvmCreateCstrFromString(niceNameObj);
- ......
- }
- ......
- }
- ......
- pid = fork();
- if (pid == 0) {
- ......
- err = setSELinuxContext(uid, isSystemServer, seInfo, niceName);
- ......
- }
- ......
- return pid;
- }
這個函數定義在檔案dalvik/vm/native/dalvik_system_Zygote.cpp中。
參數isSystemServer表示目前建立的是System Server程序還是應用程式程序。在我們這個場景中,它的值等于false,表示要建立的是應用程式程序。從參數args指向的記憶體可以獲得各種各樣的參數,例如uid、gid、seinfo和nice name等。
獲得了要建立的程序的各種參數之後,函數forkAndSpecializeCommon就通過系統調用fork建立出了一個子程序。注意,這時候函數forkAndSpecializeCommon是在Zygote程序中執行的。是以,這裡建立出來的子程序的安全上下文繼承于Zygote程序。從前面的分析可以知道,這個安全上下文為“u:r:zygote:s0”。
如果這時候我們什麼也不做的話,那麼建立出來的應用程式程序的安全上下文就會一直被設定為“u:r:zygote:s0”,這樣會使得應用程式具有Zygote程序一樣的SEAndroid安全權限。這是不允許的,是以,接下來需要通過調用函數setSELinuxContext來修改剛剛建立出來的應用程式程序的安全上下文。
函數setSELinuxContext的實作如下所示:
[cpp] view plain copy
- static int setSELinuxContext(uid_t uid, bool isSystemServer,
- const char *seInfo, const char *niceName)
- {
- #ifdef HAVE_ANDROID_OS
- return selinux_android_setcontext(uid, isSystemServer, seInfo, niceName);
- #else
- return 0;
- #endif
- }
這個函數定義在檔案dalvik/vm/native/dalvik_system_Zygote.cpp中。
函數setSELinuxContext的實作很簡單,它通過調用libselinux提供的函數selinux_android_setcontext來設定剛剛建立出來的應用程式程序的安全上下文。
函數selinux_android_setcontext的實作如下所示:
[cpp] view plain copy
- int selinux_android_setcontext(uid_t uid,
- int isSystemServer,
- const char *seinfo,
- const char *pkgname)
- {
- char *orig_ctx_str = NULL, *ctx_str;
- context_t ctx = NULL;
- int rc;
- if (is_selinux_enabled() <= 0)
- return 0;
- __selinux_once(once, seapp_context_init);
- rc = getcon(&ctx_str);
- ......
- ctx = context_new(ctx_str);
- orig_ctx_str = ctx_str;
- ......
- rc = seapp_context_lookup(SEAPP_DOMAIN, uid, isSystemServer, seinfo, pkgname, ctx);
- ......
- ctx_str = context_str(ctx);
- ......
- rc = security_check_context(ctx_str);
- ......
- if (strcmp(ctx_str, orig_ctx_str)) {
- rc = setcon(ctx_str);
- ......
- }
- rc = 0;
- out:
- ......
- return rc;
- ......
- }
這個函數定義在檔案external/libselinux/src/android.c中。
參數isSystemServer表示目前建立的是System Server程序還是應用程式程序。在我們這個場景中,它的值等于false,表示要建立的是應用程式程序。從參數args指向的記憶體可以獲得各種各樣的參數,例如uid、gid、seinfo和nice name等。
獲得了要建立的程序的各種參數之後,函數forkAndSpecializeCommon就通過系統調用fork建立出了一個子程序。注意,這時候函數forkAndSpecializeCommon是在Zygote程序中執行的。是以,這裡建立出來的子程序的安全上下文來繼承于Zygote程序。從前面的分析可以知道,這個安全上下文為“u:r:zygote:s0”。
如果這時候我們什麼也不做的話,那麼建立出來的應用程式程序的安全上下文就會一直被設定為“u:r:zygote:s0”,這樣會使得應用程式具有Zygote程序一樣的SEAndroid安全權限。這是不允許的,是以,接下來需要通過調用函數setSELinuxContext來修改剛剛建立出來的應用程式程序的安全上下文。
函數setSELinuxContext的實作如下所示:
[cpp] view plain copy
- static int setSELinuxContext(uid_t uid, bool isSystemServer,
- const char *seInfo, const char *niceName)
- {
- #ifdef HAVE_ANDROID_OS
- return selinux_android_setcontext(uid, isSystemServer, seInfo, niceName);
- #else
- return 0;
- #endif
- }
這個函數定義在檔案dalvik/vm/native/dalvik_system_Zygote.cpp中。
函數setSELinuxContext的實作很簡單,它通過調用libselinux提供的函數selinux_android_setcontext來設定剛剛建立出來的應用程式程序的安全上下文。
函數selinux_android_setcontext的實作如下所示:
[cpp] view plain copy
- int selinux_android_setcontext(uid_t uid,
- int isSystemServer,
- const char *seinfo,
- const char *pkgname)
- {
- char *orig_ctx_str = NULL, *ctx_str;
- context_t ctx = NULL;
- int rc;
- if (is_selinux_enabled() <= 0)
- return 0;
- __selinux_once(once, seapp_context_init);
- rc = getcon(&ctx_str);
- ......
- ctx = context_new(ctx_str);
- orig_ctx_str = ctx_str;
- ......
- rc = seapp_context_lookup(SEAPP_DOMAIN, uid, isSystemServer, seinfo, pkgname, ctx);
- ......
- ctx_str = context_str(ctx);
- ......
- rc = security_check_context(ctx_str);
- ......
- if (strcmp(ctx_str, orig_ctx_str)) {
- rc = setcon(ctx_str);
- ......
- }
- rc = 0;
- out:
- ......
- return rc;
- ......
- }
函數selinux_android_setcontext的執行過程如下所示:
A. 調用函數is_selinux_enabled檢查系統是否啟用了SELinux。如果沒有啟用的話,那就什麼也不用做就傳回。否則的話,就繼續往下執行。
B. 調用函數seapp_context_init讀取和解析我們在前面SEAndroid安全機制架構分析一文提到的seapp_contexts檔案。注意,__selinux_once是一個宏,它實際上是利用了pthread庫提供的函數pthread_once保證函數seapp_context_init在程序内有且僅有一次會被調用到,适合用來執行初始化工作。
C. 調用函數getcon獲得目前程序的安全上下文,儲存在變量ctx_str。
D. 調用函數context_new在原來的安全上下文的基礎上建立一個新的安全上下文ctx,以便可以獲得原來安全上下文的SELinux使用者、角色和安全級别等資訊。
E. 調用函數seapp_context_lookup根據傳進來的參數seinfo在seapp_contexts檔案中找到對應的Domain,并且将其設定為新的安全上下文ctr的Domain。
F. 調用函數context_str獲得新建立的安全上下文的字元串描述,以便可以調用函數security_check_context來驗證新建立的安全上下文的正确性。如果不正确的話,就出錯傳回。否則的話,繼續往下執行。
G. 比較原來的安全上下文和新建立的安全上下文。如果不一緻的話,那麼就調用函數setcon将目前程序的安全上下文設定為新建立的安全上下文。
在上面的幾個步驟中,最重要的就是B、E和G三步。其中,B和E這兩步在前面SEAndroid安全機制中的檔案安全上下文關聯分析一文中已經分析過了,是以接下來我們主要分析函數setcon的實作。
函數setcon通過以下三個宏來定義,如下所示:
[cpp] view plain copy
- #define setselfattr_def(fn, attr) \
- int set##fn(const security_context_t c) \
- { \
- return setprocattrcon(c, 0, #attr); \
- }
- #define all_selfattr_def(fn, attr) \
- getselfattr_def(fn, attr) \
- setselfattr_def(fn, attr)
- all_selfattr_def(con, current)
這三個宏定義在檔案external/libselinux/src/procattr.c中。
從這三個宏的定義就可以看出,函數setcon最終通過調用另外一個函數setprocattrcon來設定目前程序的安全上下文,其中,第一個參數c描述的是要設定的安全上下文,第三個參數的值等于"current"。
函數setprocattrcon的實作如下所示:
[cpp] view plain copy
- static int setprocattrcon(security_context_t context,
- pid_t pid, const char *attr)
- {
- char *path;
- int fd, rc;
- pid_t tid;
- ssize_t ret;
- int errno_hold;
- if (pid > 0)
- rc = asprintf(&path, "/proc/%d/attr/%s", pid, attr);
- else {
- tid = gettid();
- rc = asprintf(&path, "/proc/self/task/%d/attr/%s", tid, attr);
- }
- if (rc < 0)
- return -1;
- fd = open(path, O_RDWR);
- free(path);
- if (fd < 0)
- return -1;
- if (context)
- do {
- ret = write(fd, context, strlen(context) + 1);
- } while (ret < 0 && errno == EINTR);
- else
- do {
- ret = write(fd, NULL, 0);
- } while (ret < 0 && errno == EINTR);
- errno_hold = errno;
- close(fd);
- errno = errno_hold;
- if (ret < 0)
- return -1;
- else
- return 0;
- }
這個函數定義在檔案external/libselinux/src/procattr.c中。
函數setcon實際上就是打開proc檔案系統中的/proc/self/task/<tid>/attr/current檔案,并且向其寫入參數context所描述的安全上下文。其中,<tid>描述的是目前線程的id。向/proc/self/task/<tid>/attr/current檔案寫入安全上下文實際上就是将程序的安全上下文儲存在核心中用來描述程序的結構體task_struct中。這與檔案的安全上下文是儲存在檔案擴充屬性中是不一樣的。
這樣,新建立的應用程式程序的安全上下文就設定好了。
這個系列的文章學習到這裡,我們就分析完成程序和檔案的安全上下文的設定過程了。回憶前面SEAndroid安全機制簡要介紹和學習計劃一文,我們提出了一個問題,如何将裝置上的/system/bin/gpsd檔案下載下傳到PC上來。
通過常用的adb pull指令是無法将開啟了SEAndroid安全機制的裝置上将/system/bin/gpsd檔案讀取下來的。這是因為adb pull指令是通過運作在裝置上的adbd守護程序來讀取/system/bin/gpsd檔案,并且傳回來給PC的,而守護程序adbd沒有權限讀取檔案/system/bin/gpsd。
我們通過ls -Z和ps -Z指令可以來觀察/system/bin/gpsd檔案和adbd守護程序的安全上下文:
[plain] view plain copy
- ./adb shell ls -Z /system/bin/gpsd
- -rwxr-xr-x root shell u:object_r:gpsd_exec:s0 gpsd
- $ ./adb shell ps -Z | grep 'adbd'
- u:r:adbd:s0 shell 1978 1 /sbin/adbd
這意味着domain為adbd的程序無法讀取type為gpsd_exec的檔案。
實際上我們是可以在PC上通過執行adb shell cat指令來讀取裝置上的/system/bin/gpsd檔案的,如下所示:
[plain] view plain copy
- ./adb shell cat /system/bin/gpsd > ./gpsd
上面的指令實際上是通過shell啟動cat程式,并且通過這個cat程序來讀取檔案/system/bin/gpsd的内容。
從前面的分析可以知道,cat程序的安全上下文來自于其父程序shell,是以,我們通過在裝置上執行ps -Z指令觀察一下shell程序的安全上下文:
[plain] view plain copy
- $ ps -Z
- u:r:shell:s0 shell 23486 1978 /system/bin/sh
這意味着domain為shell的程序可以讀取type為gpsd_exec的檔案。
在SEAndroid中,shell和adbd這兩個domain的差別是什麼呢?我們可以通過一個稱為apol的工具做一個簡要分析。
首先我們要從裝置上讀取一個sepolicy檔案,如下所示:
[plain] view plain copy
- ./adb pull /sepolicy ./sepolicy
- 2372 KB/s (558936 bytes in 0.230s)
讀取出來的sepolicy檔案實際上描述的就是裝置使用的SEAndroid安全政策,我們可以通過apol工具對它進行分析。
接着我們打開apol工具,并且從File菜單中點選Open項打開上述sepolicy檔案。切換到Policy Components頁籤,在左則的Types清單中找到adbd項,輕按兩下檢視它的屬性,如圖2所示:
圖2 adbd屬性
這意味着adbd這個domain具有兩個屬性domain和mlstrustedsubject。
用同樣的方法檢視domain為shell的屬性,如圖3所示:
圖3 shell屬性
這意味着shell這個domain有三個屬性appdomain、domain和mlstructedsubject。與adbd相比,shell多了一個appdomain屬性。這又意味着具有appdomain屬性的domain具有讀取type為gpsd_exec的檔案的權限。
再用同樣的方法檢視type為gpsd_exec的屬性,如圖4所示:
圖4 gpsd_exec屬性
這意味着gpsd_exec這個type有兩個屬性exec_type和file_type。
我們将apol工具切換到Policy Rules頁籤,在Source type/attribute和Target type/attribute編輯框中分别填入“appdomain”和“gpsd_exec”,檢查具有appdomain屬性的程序是否可以讀取具有exec_file屬性的檔案的權限,點選右邊的New Search按鈕,結果如圖5所示:
圖5 具有屬性appdomain的程序對具有屬性exec_type的檔案的SEAndroid安全權限
這意味着具有屬性appdomain的程序對具有屬性exec_type的檔案具有讀取的權限。也就是說,裝置上的shell程序能讀取/system/bin/gpsd檔案的内容,是因為它的domain具有appdomain屬性,而裝置上的adbd程序的domain是不具有這個屬性的。
通過回答前面SEAndroid安全機制簡要介紹和學習計劃一文提出的問題,我們介紹了如何通過apol工具來分析裝置上的SEAndroid安全政策。這是一個非常有用的工具,希望大家可以掌握。在接下來的兩篇文章中,我們就繼續學習SEAndroid安全機制是如何支援Android系統的屬性通路以及Binder IPC調用的