編輯:關於Android編程
Android系統通過屬性暴露設備和運行時信息,並且可以通過設置屬性來控制系統行為。因此,屬性也像文件一樣,是一種需要保護的資源。在啟用SEAndroid之前,敏感屬性只能被預先設定的進程進行設置。啟用SEAndroid之後,敏感屬性會進一步被SEAndroid安全策略保護。這樣就可以更有效地保護系統屬性了。在本文中,我們就詳細分析SEAndroid安全機制對Android屬性設置保護提供的支持。
老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!
在分析SEAndroid安全機制對Android屬性保護的支持之前,我們首先要了解Android屬性的實現框架,如圖1所示:
圖1 Android屬性的實現框架
Android系統的所有屬性都是保存在內存中。這塊屬性內存區是通過將文件/dev/__properties__映射到內存中創建的,它由頭部區域和屬性值區域兩部分內容組成。
屬性區頭部通過結構體prop_area描述,如下所示:
struct prop_area { unsigned volatile count; unsigned volatile serial; unsigned magic; unsigned version; unsigned reserved[4]; unsigned toc[1]; };這個結構體定義在文件bionic/libc/include/sys/_system_properties.h中。
結構體prop_area的各個成員變量的含義如下所示:
count: 屬性內在區當前包含的屬性個數。
serial: 增加或者修改屬性時用到的同步變量。
magic: 屬性內存區魔數,用來識別屬性內存區。
version: 屬性內在區版本號,用來識別屬性內存區。
reserved: 保留字段。
toc: 屬性內容索引表。
屬性值區域由一系列的屬性組成,每一個屬性都通過結構體prop_info描述,如下所示:
struct prop_info { char name[PROP_NAME_MAX]; unsigned volatile serial; char value[PROP_VALUE_MAX]; };這個結構體定義在文件bionic/libc/include/sys/_system_properties.h中。
結構體prop_info的各個成員變量的含義如下所示:
name: 屬性名稱,長度最大為32個字節。
serial: 修改屬性值時用到的同步變量,同時也用來描述屬性值的長度。
value: 屬性值,長度最大為92個字節。
屬性內存區域最多可以容納的屬性個數為247個,並且每一個屬性在頭部的索引表都對應有一個類型為unsigned的索引項。每一個屬性占用的字節數為32 + 4 + 92 = 128,每一個屬性索引項占用的字節數是4個字節,因此屬性內存區頭部,即結構體prop_area,總區占用的字節數是4 + 4 + 4 + 4 + 4 * 4 + 4 * 247 = 1020。緊接著屬性內存區頭部的屬性值區域。考慮到對齊因素,屬性值區域從屬性區域的1024個字節偏移開始。因此,整個屬性區域占用的內存大小是1024 + 247 * 128 = 32768個字節,剛好就是32Kb。
在結構體prop_area中,magic和version是兩個用來識別屬性內存區域的字段,它們的值分別被設置為PROP_AREA_MAGIC和PROP_AREA_VERSION。PROP_AREA_MAGIC和PROP_AREA_VERSION是兩個宏,它們的值如下所示:
#define PROP_AREA_MAGIC 0x504f5250 #define PROP_AREA_VERSION 0x45434f76這兩個宏定義在文件bionic/libc/include/sys/_system_properties.h中。
在結構體prop_area中,toc是table of content的簡稱,描述的是屬性索引表。每一個屬性在這個索引表裡面都對應有一個索引項。每一個索引項都使用一個無符號數來表示。其中,最高一個字節描述的是對應的屬性的名稱的長度,低三個字節描述的是對應的屬性相對於屬性內存區域開頭的偏移。通過宏TOC_NAME_LEN和TOC_TO_INFO可以獲得一個索引項對應的屬性的名稱的長度和內存位置,如下所示:
#define TOC_NAME_LEN(toc) ((toc) >> 24) #define TOC_TO_INFO(area, toc) ((prop_info*) (((char*) area) + ((toc) & 0xFFFFFF)))這兩個宏定義在文件bionic/libc/include/sys/_system_properties.h中。
其中,toc描述的是一個索引項,而area描述的是屬性內存區域頭部。也就是說,給出一個結構體prop_area,以及一個索引項n,我們就可以找到一個對應的prop_info結構體。這個功能可以通過調用函數__system_property_find_nth來實現。
函數__system_property_find_nth的實現如下所示:
const prop_info *__system_property_find_nth(unsigned n) { prop_area *pa = __system_property_area__; if(n >= pa->count) { return 0; } else { return TOC_TO_INFO(pa, pa->toc[n]); } }這個函數定義在文件libc/bionic/system_properties.c中。
在函數__system_property_find_nth中,__system_property_area__指向的就是一個prop_area結構體,而參數n描述的就是一個屬性索引項。只有在參數n的值小於__system_property_area__描述的屬性區域當前包含的屬性個數的前提下,使用宏TOC_TO_INFO獲得的prop_info結構體才是正確的。
此外,給出一個結構體prop_area,以及一個屬性名稱,我們也可以找到一個對應的的prop_info結構體。這個功能可以通過調用函數__system_property_find來實現。
函數__system_property_find的實現如下所示:
const prop_info *__system_property_find(const char *name) { prop_area *pa = __system_property_area__; unsigned count = pa->count; unsigned *toc = pa->toc; unsigned len = strlen(name); prop_info *pi; while(count--) { unsigned entry = *toc++; if(TOC_NAME_LEN(entry) != len) continue; pi = TOC_TO_INFO(pa, entry); if(memcmp(name, pi->name, len)) continue; return pi; } return 0; }這個函數定義在文件libc/bionic/system_properties.c中。
函數__system_property_find通過遍歷__system_property_area__描述的屬性區域頭部中的索引表,找到每一個屬性的名稱,並且拿來與參數name描述的屬性名稱比較,直到找到一個相等的值為止,這樣就意味著找到了對應的prop_info結構體。
在結構體prop_area中,serial用來同步屬性的增加或者修改。每一次屬性修改或者增輥它的值都會增加1,如下面的代碼片段所示:
pa = __system_property_area__; /*update prop info or add prop info*/ pa->serial++; __futex_wake(&pa->serial, INT32_MAX);serial的值增加1之後,還需要調用系統接口__futex_wake來喚醒處於睡眠狀態的並且是正在監聽serial值變化的最多INT32_MAX個進程。進程可以通過下面的代碼片段來監聽serial值變化:
prop_area *pa = __system_property_area__; n = pa->serial; do { __futex_wait(&pa->serial, n, 0); } while(n == pa->serial);上面的代碼片段首先是獲得serial原來的值n,然後調用系統接口__futex_wait等待serial的值發生變化。如果沒有發生變化,那麼進程就會進入睡眠狀態,直到第三個參數指定的超時時間到來為止。當進程從系統接口__futex_wait返回時,需要檢查serial原來的值n是否與當前的值相等。如果相等,那麼就說明調用系統接口__futex_wait是超時返回。這時候就需要再次調用__futex_wait等待serial的值發生變化。這與Linux內核的順序鎖(seqlock)實現是類似的。
在結構體prop_info中,serial的最高字節用來描述對應的屬性的值的長度,低三個字用來同步屬性值的讀寫,並且最低的一位是用來描述屬性是否正在修改中。我們可以通過宏SERIAL_VALUE_LEN和SERIAL_DIRTY來獲得對應的屬性的值的長度,以及檢查屬性值是否正在修改中,如下所示:
#define SERIAL_VALUE_LEN(serial) ((serial) >> 24) #define SERIAL_DIRTY(serial) ((serial) & 1)這兩個宏定義在文件bionic/libc/include/sys/_system_properties.h中。
修改屬性值之前,首先是要將對應的prop_info結構體的serial值的最低一位設置為1。修改屬性值之後,需要將對應的prop_info結構體的serial值加1,同時也會將其最低一位恢復為0。這個功能可以通過調用函數update_prop_info來實現,如下所示:
static void update_prop_info(prop_info *pi, const char *value, unsigned len) { pi->serial = pi->serial | 1; memcpy(pi->value, value, len + 1); pi->serial = (len << 24) | ((pi->serial + 1) & 0xffffff); __futex_wake(&pi->serial, INT32_MAX); }這個函數定義在文件system/core/init/property_service.c中。
在函數update_prop_info中,參數pi描述的是要修改的屬性,參數value和len描述的是修改後的值及其長度。修改完成之後,需要調用系統接口__futex_wake喚醒正在監聽serial值變化的最多INT32_MAX個進程。
相應地,一個進程在讀取屬性值中,需要檢查是否有另外一個進程正在修改該屬性的值。如果有的話,就需要等待。這個功能通過調用函數__system_property_read來實現,如下所示:
int __system_property_read(const prop_info *pi, char *name, char *value) { unsigned serial, len; for(;;) { serial = pi->serial; while(SERIAL_DIRTY(serial)) { __futex_wait((volatile void *)&pi->serial, serial, 0); serial = pi->serial; } len = SERIAL_VALUE_LEN(serial); memcpy(value, pi->value, len + 1); if(serial == pi->serial) { if(name != 0) { strcpy(name, pi->name); } return len; } } }這個函數定義在文件bionic/libc/bionic/system_properties.c中。
在函數__system_property_read中,參數pi表示要讀取的屬性,參數name和value用來保存讀取出來的屬性名稱和屬性值。
在for循環內部的while循環,是用來確保結構體pi的serial的最低一位不等於1,也就是確定沒有其它進程正在修改它值中,這是通過調用系統接口__futex_wait來實現的。確定沒有其它進程正在修改結構體pi之後,就可以將它的屬性名稱和屬性值讀取出來。首先是讀出屬性值,接著再讀出屬性名稱。然而,在讀取屬性值的時候,有可能其它進程又對該屬性進行了修改,這時候就需要檢查之前獲得的serai值與當前的serial值是否相等。如果不相等的話,那麼就說明有其它進程對正在讀取的屬性進行了修改,因此就需要重新執行for循環進行重新讀取。
從這裡就可以看出,結構體prop_area和結構體prop_info的成員變量serial的作用是類似的,不同的是前者用來同步整個屬性內存區域屬性的增加或者修改,而後者僅公是用來同步某一個屬性的讀寫。
理解了屬性內存區域的結構之後,我們再來看屬性內存區域的創建和初始化。屬性內存區域是由init進程在啟動的過程中創建和初始化。創建和初始化完成之後,其它進程可以將這塊屬性內存區域以只讀的方式映射到自己的地址空間去,這樣其它進程就可以直接從自己的地址空間讀出屬性值。另一方面,如果其它進程需要增加或者修改屬性的值,那麼就必須要通過init進程來進行。Init進程在啟動的時候,會創建一個屬性管理服務。這個屬性管理服務會創建一個Server端Socket,用來接收其它進程發送過來的增加或者修改屬性的請求。
在init進程的入口函數main中,與屬性相關的邏輯如下所示:
int main(int argc, char **argv) { ...... property_init(); ...... if (!is_charger) property_load_boot_defaults(); ...... queue_builtin_action(property_service_init_action, "property_service_init"); ...... for(;;) { ...... execute_one_command(); ...... if (!property_set_fd_init && get_property_set_fd() > 0) { ufds[fd_count].fd = get_property_set_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; property_set_fd_init = 1; } ...... nr = poll(ufds, fd_count, timeout); ...... for (i = 0; i < fd_count; i++) { if (ufds[i].revents == POLLIN) { if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd(); ...... } } } return 0; }
這個函數定義在文件system/core/init/init.c中。
在main函數中,首先調用函數property_init來創建屬性內存區域。接著再判斷當前是否處於充電模式,即變量is_charger的值是否等於true。如果不等於的話,那麼就說明系統是處於正常啟動的過程中,這時候就會調用函數property_load_boot_defaults初始化一些默認的屬性。接下來是將函數property_service_init_action加入到內部的一個命令隊列中去,以便在接下來的for循環中可以通過函數execute_one_command來執行。函數property_service_init_action的作用是啟動屬性管理服務。
在main函數最後的for循環中,除了調用函數execute_one_command執行命令隊列中的命令之外,還做其它事情。其中的一件事情就是監聽其它進程通過Socket發送過來的增加或者修改屬性的請求。用來接收請求的Server端Socket是在函數property_service_init_action的調用過程中創建的,並且可以通過函數get_property_set_fd來獲得它的文件描述符。
有了這個Server端Socket的文件描述符之後,就可以通過函數poll來監聽其它進程發送過來的請求了。也就是說,一旦其它進程發送請求過來,那麼Init進程就會從函數poll返回,並且獲得一個類型為POLLIN的事件。如果這個POLLIN事件對應的文件描述符就是用來接收增加或者修改屬性請求的Server端Socket的文件描述符,那麼就調用另外一個函數handle_property_set_fd來處理請求。
接下來我們就分析上面描述的函數property_init、property_load_boot_defaults、property_service_init_action和get_property_set_fd的實現。等到後面分析屬性的增加或者修改時,再分析函數handle_property_set_fd的實現。
函數property_init的實現如下所示:
void property_init(void) { init_property_area(); }這個函數定義在文件system/core/init/property_service.c中。
函數property_init的實現很簡單,它通過調用另外一個函數init_property_area來創建和初始化一塊屬性內存區域。
函數init_property_area的實現如下所示:
/* (8 header words + 247 toc words) = 1020 bytes */ /* 1024 bytes header and toc + 247 prop_infos @ 128 bytes = 32640 bytes */ #define PA_COUNT_MAX 247 #define PA_INFO_START 1024 #define PA_SIZE 32768 static workspace pa_workspace; static prop_info *pa_info_array; extern prop_area *__system_property_area__; static int init_property_area(void) { prop_area *pa; if(pa_info_array) return -1; if(init_workspace(&pa_workspace, PA_SIZE)) return -1; fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC); pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START); pa = pa_workspace.data; memset(pa, 0, PA_SIZE); pa->magic = PROP_AREA_MAGIC; pa->version = PROP_AREA_VERSION; /* plug into the lib property services */ __system_property_area__ = pa; property_area_inited = 1; return 0; }這個函數定義在文件system/core/init/property_service.c中。
函數init_property_area首先是檢查全局變量pa_info_array的值。如果不等於NULL,那麼就說明屬性內存區域已經創建過了,因此就直接返回。如果等於NULL,接下來就調用函數init_workspace來創建一塊大小等於PA_SIZE的屬性內存區域,並且將創建出來的屬性內存區域的地址保存在全局變量pa_workspace所指向的一個workspace結構體的成員變量data中。
全局變量pa_info_array指向的是屬性值區域,它位於屬性內存區域開始偏移PA_INFO_START的位置上,因此,將全局變量pa_workspace所指向的一個workspace結構體的成員變量data的值再加上PA_INFO_START的值,就可以得到屬性值區域的開始位置。以後就可以從這個位置開始增加屬性。
函數init_property_area再接下來將屬性內存區域的頭部轉化為一個prop_area結構體pa,並且初始化好它的成員變量magic和version,
函數init_property_area最後已經創建和初始好的屬性內存區域保存在全局變量__system_property_area__中,並且將另外一個全局變量property_area_inited的值設置為1,以表示屬性內存區域已經初始化完畢。
接下來我們再分析函數init_workspace創建屬性內存區域的過程,如下所示:
static int init_workspace(workspace *w, size_t size) { void *data; int fd; /* dev is a tmpfs that we can use to carve a shared workspace * out of, so let's do that... */ fd = open(PROP_FILENAME, O_RDWR | O_CREAT | O_NOFOLLOW, 0644); if (fd < 0) return -1; if (ftruncate(fd, size) < 0) goto out; data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(data == MAP_FAILED) goto out; close(fd); fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW); if (fd < 0) return -1; w->data = data; w->size = size; w->fd = fd; return 0; out: close(fd); return -1; }這個函數定義在文件system/core/init/property_service.c中。
從函數init_workspace的實現我們就可以看出屬性內存區域是如何創建的。首先是調用函數open以讀寫方式打開一個名稱為PROP_FILENAME的文件, 接著再調用函數ftruncate將該文件的大小設置為參數size指定的值,最後再調用函數mmap以共享的方式將前面打開的文件映射到內存中來。映射成功後,就可以得一塊大小為size的屬性內存區域了。
PROP_FILENAM是一個定義在文件bionic/libc/include/sys/_system_properties.h中的宏,如下所示:
#define PROP_FILENAME "/dev/__properties__"
注意,由於上面打開文件/dev/__properties__之後,需要調用函數ftruncate設置它的大小,因此,這時候/dev/__properties__文件是以讀寫方式打開的。打開得到的文件描述符需要保存在參數w指向的一個workspace結構體中,以便以後可以對它進行關閉。由於以後我們要求不能通過文件描述符來直接向屬性內存區域寫入數據,因此,函數init_workspace接下來需要將前面打開的文件/dev/__properties__關閉,然後再重新以只讀方式打開。這樣得到的文件描述符就不可以用來寫入數據了。
回到init進程的入口函數main中,接下來我們再分析函數property_load_boot_defaults的實現。
函數property_load_boot_defaults的實現如下所示:
void property_load_boot_defaults(void) { load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT); }這個函數定義在文件system/core/init/property_service.c中。
函數property_load_boot_defaults通過調用另外一個函數load_properties_from_file將定義在文件PROP_PATH_RAMDISK_DEFAULT裡面的屬性加載前面創建的屬性內存區域中。
PROP_PATH_RAMDISK_DEFAULT是一個宏,定義在文件bionic/libc/include/sys/_system_properties.h中,如下所示:
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"再回到init進程的入口函數main中,接下來我們再分析函數property_service_init_action的實現。
函數property_service_init_action的實現如下所示:
static int property_service_init_action(int nargs, char **args) { /* read any property files on system or data and * fire up the property service. This must happen * after the ro.foo properties are set above so * that /data/local.prop cannot interfere with them. */ start_property_service(); return 0; }
這個函數定義在文件system/core/init/init.c中。
函數property_service_init_action的實現很簡單,它通過調用另外一個函數start_property_service來啟動屬性管理服務。
函數start_property_service的實現如下所示:
void start_property_service(void) { int fd; load_properties_from_file(PROP_PATH_SYSTEM_BUILD); load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT); load_override_properties(); /* Read persistent properties after all default values have been loaded. */ load_persistent_properties(); fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0); if(fd < 0) return; fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFL, O_NONBLOCK); listen(fd, 8); property_set_fd = fd; }這個函數定義在文件system/core/init/property_service.c中。
函數start_property_service首先是調用函數load_properties_from_file將定義在文件PROP_PATH_SYSTEM_BUILD和PROP_PATH_SYSTEM_DEFAULT中的屬性加載到前面創建的屬性內存區域中。接著又調用函數load_override_properties判斷當前是否是以debug模式啟動。如果是的話,那就再將定義在文件PROP_PATH_LOCAL_OVERRIDE中的屬性加載到前面創建的屬性內存區域中來。再接下來還會調用函數load_persistent_properties將保存在目錄PERSISTENT_PROPERTY_DIR中的持久屬性加載到前面創建的屬性內存區域中來。
上面提到的PROP_PATH_SYSTEM_BUILD、PROP_PATH_SYSTEM_DEFAULT和PROP_PATH_LOCAL_OVERRIDE和PERSISTENT_PROPERTY_DIR均是定義在文件bionic/libc/include/sys/_system_properties.h中的宏,如下所示:
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop" #define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop" #define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"另外一個宏PERSISTENT_PROPERTY_DIR定義在文件ystem/core/initprop/erty_service.c中。
函數start_property_service加載完成保存在指定文件中的屬性之後,就調用函數create_socket創建了一個名稱為PROP_SERVICE_NAME的Socket,並且調用函數listen來監聽其它進程發送過的增加或者修改屬性請求。
PROP_SERVICE_NAME是一個定義在文件bionic/libc/include/sys/_system_properties.h中的宏,如下所示:
#define PROP_SERVICE_NAME "property_service"
最後,函數start_property_service將前面創建Socket獲得的文件描述符保存在全局變量property_set_fd中,以便init進程的main函數可以通過函數get_property_set_fd獲取。
函數get_property_set_fd的實現如下所示:
int get_property_set_fd() { return property_set_fd; }這個函數定義在文件system/core/init/property_service.c中。
這樣,我們就分析完成了Init進程創建和初始化屬性內存區域以及創建屬性管理服務的過程。前面我們提到,其它進程也需要將Init進程創建的屬性內存區域映射到自己的進程地址訪問來,以便可以對屬性進行直接的讀取。接下來我們繼續分析其它進程是如何將Init進程創建的屬性內存區域映射到自己的進程地址空間的。
我們以Android應用程序進程為例來說明屬性內存區域映射到非Init進程地址空間的過程。從Android應用程序進程啟動過程的源代碼分析前面一文可以知道,Android應用程序進程是由Zygote進程fork出來的。Zygote進程是作為Init進程的一個服務啟動的,啟動腳本如下所示:
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這段啟動腳本定義在文件system/core/rootdir/init.rc中。
在Init進程中,所有的服務都是通過函數service_start啟動的,它的定義如下所示:
void service_start(struct service *svc, const char *dynamic_args) { ...... char *scon = NULL; int rc; if (is_selinux_enabled() > 0) { char *mycon = NULL, *fcon = NULL; ...... rc = getcon(&mycon); ...... rc = getfilecon(svc->args[0], &fcon); ...... rc = security_compute_create(mycon, fcon, string_to_security_class("process"), &scon); ...... } ...... pid = fork(); if (pid == 0) { struct socketinfo *si; ...... int fd, sz; ...... if (properties_inited()) { get_property_workspace(&fd, &sz); sprintf(tmp, "%d,%d", dup(fd), sz); add_environment("ANDROID_PROPERTY_WORKSPACE", tmp); } ...... setsockcreatecon(scon); for (si = svc->sockets; si; si = si->next) { int socket_type = ( !strcmp(si->type, "stream") ? SOCK_STREAM : (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET)); int s = create_socket(si->name, socket_type, si->perm, si->uid, si->gid); if (s >= 0) { publish_socket(si->name, s); } } ...... if (!dynamic_args) { if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) { ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno)); } } else { ...... execve(svc->args[0], (char**) arg_ptrs, (char**) ENV); } ...... } ...... }這個函數定義在文件system/core/init/init.c中。
參數svc描述的是要啟動的服務的信息,參數dynamic_args描述的是服務啟動參數。
Init進程啟動的服務都是運行在一個獨立的進程裡面的,所以這裡需要調用fork函數。在fork進程前面,與SEAndroid相關的邏輯如下所示:
1. 在創建進程之前,調用函數is_selinux_enabled檢查系統是否開啟了SEAndroid。如果開啟了,那麼就繼續調用函數getcon來獲得當前進程(即init進程)的安全上下文mycon,以及調用函數getfilecon獲得服務的可執行文件(保存在參數args[0])的安全上下文fcon。有了這兩個安全上下文之後,就可以調用另外一個函數security_compute_create到內核的SELinux模塊去查詢獲得即將要創建的進程的安全上下文scon。
2. 在創建進程之後,調用函數properties_inited檢查Init進程是否已經創建和初始化好屬性內存區域了。如果已經創建和初始化好,那麼就繼續調用函數get_property_workspace來獲得用來描述屬性內存區域的文件描述符fd以及屬性內存區域的大小sz。我們在前面提到,屬性內存區域是通過將文件/dev/__properties__映射到內存中創建的。因此,這裡得到的文件描述符fd實際上指向的就是文件/dev/__properties__。再接下來將屬性內存區域的文件描述符以及大小拼接成一個字符串,並且將得到的字符串作為環境變量ANDROID_PROPERTY_WORKSPACE的值。
3. 調用函數setsockcreatecon設置新創建的Socket的安全上下文,這是因為接下來要為Zygote進程創建一個名稱zygote的Socket。這個Socket是在Init進程的啟動腳本init.rc中配置要創建的,並且是用來接收ActivityManagerService發送過來的應用程序進程創建請求的。這一步說明在SEAndroid中,除了進程、文件、屬性,Socket也是有安全上下文的。通過給Socket設置安全上下文,我們就可以通過SEAndroid安全策略來設置什麼樣的進程可以連接什麼樣的Socket。例如,我們可以通過SEAndroid安全策略限定只有ActivityManagerService所運行在的System Server進程才會權限連接名稱為zygote的Socket。這樣就可以使得只有有權限的進程,才可以請求Zygote進程創建應用程序進程。
4. 調用函數execve在新創建的進程中加載參數args[0]所描述的要執行文件,實際上就是/system/bin/app_process文件。從前面SEAndroid安全機制中的進程安全上下文關聯分析一文可以知道,加載了/system/bin/app_process文件之後,新進程的安全上下文的domain就會被設置為zygote了。這樣就可以與init進程的安全上下文區分開來。
應用程序進程都是通過Zygote進程fork出來的,這意味著應用程序進程會繼續父進程zygote的環境變量,也就是會繼續前面第2步創建的環境變量ANDROID_PROPERTY_WORKSPACE。有了這個環境變量之後,就可以得到用來描述在Init進程創建的屬性內存區域的文件描述符以及大小了。
在Android系統中,所有進程基本上都會使用到C庫,應用程序進程也不例外。Android系統使用的C庫為bionic,它定義了一個函數 __libc_preinit,是在C庫為bioni動態鏈接的時候調用的,如下所示:
// We flag the __libc_preinit function as a constructor to ensure // that its address is listed in libc.so's .init_array section. // This ensures that the function is called by the dynamic linker // as soon as the shared library is loaded. __attribute__((constructor)) static void __libc_preinit() { // Read the kernel argument block pointer from TLS. void* tls = const_cast這個函數定義在文件bionic/libc/bionic/libc_init_dynamic.cpp中。(__get_tls()); KernelArgumentBlock** args_slot = &reinterpret_cast (tls)[TLS_SLOT_BIONIC_PREINIT]; KernelArgumentBlock* args = *args_slot; // Clear the slot so no other initializer sees its value. // __libc_init_common() will change the TLS area so the old one won't be accessible anyway. *args_slot = NULL; __libc_init_common(*args); // Hooks for the debug malloc and pthread libraries to let them know that we're starting up. pthread_debug_init(); malloc_debug_init(); }
函數__libc_preinit前面的關鍵字__attribute__((constructor))是用來告訴編譯器gcc和動態鏈接器linker,在加載bionic庫的時候,要執行後面定義的函數。
函數__libc_preinit首先是從線程本地存儲區域中獲得本進程的啟動參數args,接著再調用函數__libc_init_common來初始化C庫。初始化C庫之後,函數__libc_preinit再調用另外兩個函數pthread_debug_init和malloc_debug_init來通知模塊pthread和malloc,C庫已經初始化好了。
函數__libc_init_common的實現如下所示:
void __libc_init_common(KernelArgumentBlock& args) { ...... __system_properties_init(); // Requires 'environ'. }這個函數定義在文件bionic/libc/bionic/libc_init_common.cpp中。
函數__libc_init_common用來初始化C庫,其中的一個工作就是調用函數__system_properties_init來初始化屬性內存區域。
函數__system_properties_init的定義如下所示:
static unsigned dummy_props = 0; prop_area *__system_property_area__ = (void*) &dummy_props; int __system_properties_init(void) { bool fromFile = true; int result = -1; if(__system_property_area__ != ((void*) &dummy_props)) { return 0; } int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW); if ((fd < 0) && (errno == ENOENT)) { ...... fd = get_fd_from_env(); fromFile = false; } prop_area *pa = mmap(NULL, fd_stat.st_size, PROT_READ, MAP_SHARED, fd, 0); ...... if((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION)) { munmap(pa, fd_stat.st_size); goto cleanup; } __system_property_area__ = pa; result = 0; cleanup: if (fromFile) { close(fd); } return result; }這個函數定義在文件bionic/libc/bionic/system_properties.c中。
在文件system_properties.c中,定義有一個全局變量__system_property_area__,它的初始值被設置為一個全局變量dummy_props的地址,用來表明在本進程中,屬性內存區域還沒有初始過。一旦本進程的屬性內存區域初始化之後,全局變量__system_property_area__就會指向這塊屬性內存區域。
了解了全局變量__system_property_area__的作用之後,我們就開始分析函數__system_properties_init初始化屬性內存區域的過程。
首先是調用函數open以只讀方式打開文件PROP_FILENAME。從前面的分析可以知道,PROP_FILENAME是一個宏,它的值等於/dev/__properties__,init進程也是通過打開它來創建屬性內存區域的。如果打開文件PROP_FILENAME失敗,那麼就通過調用函數get_fd_from_env來獲得指向屬性內存區域的文件描述符。
函數get_fd_from_env的實現如下所示:
static int get_fd_from_env(void) { char *env = getenv("ANDROID_PROPERTY_WORKSPACE"); if (!env) { return -1; } return atoi(env); }這個函數定義在文件bionic/libc/bionic/system_properties.c中。
函數get_fd_from_env是通過讀取環境變量ANDROID_PROPERTY_WORKSPACE來獲得指向屬性內在區域的文件描述符的。這個環境變量剛好就是在Zygote進程創建的時候設置的。也就是說,應用程序進程可以通過父進程Zygote設置的環境變量ANDROID_PROPERTY_WORKSPACE來獲得指向屬性內存區域的文件描述符。
回到函數__system_properties_init中,獲得了指向屬性內存區域的文件描述符之後,接下來就調用函數mmap以只讀共享的方式將該文件描述符指向的文件/dev/__properties__映射到本進程來。我們注意到,init進程也是通過共享的方式來映射文件/dev/__properties__的。正是由於如此,init進程和其它進程才可以做到共享屬性內存區域。但是,屬性內存區域在init進程中可讀可寫的,而在其它進程中只可以讀。
獲得了屬性內存區域之後,就會驗證它頭部的魔數和版本號是否正確。正確的話,就說明得到的是一塊有效的屬性內存區域,因此,就可以把它的地址保存在全局變量__system_property_area__中。
這樣,我們就以應用程序進程為例,分析了其它進程初始化屬性內存區域的過程。重點就是通過共享方式將/dev/__properties__映射到自己的進程地址空間來。以實現共享在init進程創建的屬性內存區域。
接下來我們再來分析屬性的讀取。C庫提供了一個函數__system_property_get,用來讀取Android系統中的屬性,它的定義如下所示:
int __system_property_get(const char *name, char *value) { const prop_info *pi = __system_property_find(name); if(pi != 0) { return __system_property_read(pi, 0, value); } else { value[0] = 0; return 0; } }這個函數定義在文件bionic/libc/bionic/system_properties.c中。
參數name用來指定要讀取的屬性的名稱,參數value用來保存讀取出來的屬性的值。
函數__system_property_get首先是調用函數__system_property_find在前面已經初始化好的屬性內存區域__system_property_area__中找到與參數name對應的一個結構體prop_info,接著再調用函數__system_property_read來讀保存在該結構體中的屬性的值。
函數__system_property_find和__system_property_read在前面都已經分析過了,這裡就不再詳述。從函數__system_property_get的實現就可以看出,讀取Android系統的屬性是沒有權限控制的,並且可以直接從本進程的地址空間讀取。然而,屬性的增加或者修改就沒有那麼簡單了,接下來我們就進行詳細分析,這個過程也是本文的主題,前面的內容都是鋪墊。
C庫除了提供函數__system_property_get來讀取屬性之外,還提供了另外一個函數__system_property_set來增加或者修改屬性,它的定義如下所示:
int __system_property_set(const char *key, const char *value) { int err; prop_msg msg; if(key == 0) return -1; if(value == 0) value = ""; if(strlen(key) >= PROP_NAME_MAX) return -1; if(strlen(value) >= PROP_VALUE_MAX) return -1; memset(&msg, 0, sizeof msg); msg.cmd = PROP_MSG_SETPROP; strlcpy(msg.name, key, sizeof msg.name); strlcpy(msg.value, value, sizeof msg.value); err = send_prop_msg(&msg); if(err < 0) { return err; } return 0; }這個函數定義在文件bionic/libc/bionic/system_properties.c中。
參數key描述的是要增加或者修改的屬性的名稱,參數value描述的是要增加或者要修改的屬性的值。
函數__system_property_get首先是將要增加或者修改的屬性的相關信息封裝在一個類型為PROP_MSG_SETPROP的消息中,接著再調用函數send_prop_msg將它發送給init進程。
函數send_prop_msg的實現如下所示:
static const char property_service_socket[] = "/dev/socket/" PROP_SERVICE_NAME; static int send_prop_msg(prop_msg *msg) { struct pollfd pollfds[1]; ...... struct sockaddr_un addr; ...... int s; ...... s = socket(AF_LOCAL, SOCK_STREAM, 0); ...... memset(&addr, 0, sizeof(addr)); namelen = strlen(property_service_socket); strlcpy(addr.sun_path, property_service_socket, sizeof addr.sun_path); addr.sun_family = AF_LOCAL; alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1; if(TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen)) < 0) { close(s); return result; } r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0)); if(r == sizeof(prop_msg)) { ...... pollfds[0].fd = s; pollfds[0].events = 0; r = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */)); if (r == 1 && (pollfds[0].revents & POLLHUP) != 0) { result = 0; } else { ...... result = 0; } } close(s); return result; }這個函數定義在文件bionic/libc/bionic/system_properties.c中。
在文件ystem_properties.c中,定義有一個全局變量property_service_socket,它的值等於"/dev/socket/" PROP_SERVICE_NAME。從前面分析屬性管理服務的啟動過程可以知道,PROP_SERVICE_NAME是一個宏,它的值等於“property_service”,是屬性管理服務正在監聽的一個Socket的名稱。
清楚上面的信息之後,函數send_prop_msg的執行過程就簡單了,如下所示:
1. 調用函數socket創建一個本地Socket,並且將它要連接的對端Socket的地址設置為/dev/socket/property_service。
2. 調用函數connect請求與對端Socket進行連接。
3. 連接成功後,調用函數send向對端發送參數msg描述的消息。
4. 發送成功後,調用函數poll等待對端關閉連接。
一旦對端Socket關閉了連接,就說明增加或者修改屬性成功了。
前面在分析init進程的入口函數main時提到,一旦有進程通過文件/dev/socket/property_service描述的Socket發送連接請求時,函數handle_property_set_fd就會被調用,它的實現如下所示:
static int property_set_fd = -1; void handle_property_set_fd() { prop_msg msg; int s; int r; int res; struct ucred cr; struct sockaddr_un addr; socklen_t addr_size = sizeof(addr); socklen_t cr_size = sizeof(cr); char * source_ctx = NULL; if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) { return; } /* Check socket options here */ if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { close(s); ERROR("Unable to receive socket options\n"); return; } r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), 0)); if(r != sizeof(prop_msg)) { ERROR("sys_prop: mis-match msg size received: %d expected: %d errno: %d\n", r, sizeof(prop_msg), errno); close(s); return; } switch(msg.cmd) { case PROP_MSG_SETPROP: msg.name[PROP_NAME_MAX-1] = 0; msg.value[PROP_VALUE_MAX-1] = 0; getpeercon(s, &source_ctx); if(memcmp(msg.name,"ctl.",4) == 0) { // Keep the old close-socket-early behavior when handling // ctl.* properties. close(s); if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) { handle_control_message((char*) msg.name + 4, (char*) msg.value); } else { ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n", msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid); } } else { if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) { property_set((char*) msg.name, (char*) msg.value); } else { ERROR("sys_prop: permission denied uid:%d name:%s\n", cr.uid, msg.name); } // Note: bionic's property client code assumes that the // property server will not close the socket until *AFTER* // the property is written to memory. close(s); } freecon(source_ctx); break; default: close(s); break; } }這個函數定義在system/core/init/property_service.c中。
函數handle_property_set_fd的執行過程如下所示:
1. 調用函數accept接受其它進程發送過來的連接請求。
2. 接受了請求後,調用函數getsocketopt獲得發送端進程的信息,例如uid和guid。
2. 調用函數recv獲取請求的消息內容,並且保存在一個類型為prop_msg的結構體msg中。
3. 目前屬性管理服務只處理類型為PROP_MSG_SETPROP的消息,對應的就是增加或者修改屬性的請求,按照下面的步驟進行處理。
4. 調用函數getpeercon獲得發送端Socket的安全上下文source_ctx,一般就是等於發送端進程的安全上下文了。
5. 如果發送過來的消息請求處理的屬性的名稱以“ctl.”開頭,那麼就說明要處理的是一個控制消息。這時候先調用check_control_perms檢查發送端進程是否具有相應的權限。如果有的話,再調用函數handle_control_message來處理該消息。
6. 如果發送過來的消息請求處理的屬性的名稱不是以“ctl.”開頭,那麼就說明要處理的是一個普通消息。這時候先調用check_perms檢查發送端進程是否具有相應的權限。如果有的話,再調用函數property_set來處理該消息。
我們以普通消息的處理過程為例,來說明SEAndroid是如何保護系統的屬性的。
函數check_perms的實現如下所示:
static int check_perms(const char *name, unsigned int uid, unsigned int gid, char *sctx) { int i; unsigned int app_id; if(!strncmp(name, "ro.", 3)) name +=3; if (uid == 0) return check_mac_perms(name, sctx); app_id = multiuser_get_app_id(uid); if (app_id == AID_BLUETOOTH) { uid = app_id; } for (i = 0; property_perms[i].prefix; i++) { if (strncmp(property_perms[i].prefix, name, strlen(property_perms[i].prefix)) == 0) { if ((uid && property_perms[i].uid == uid) || (gid && property_perms[i].gid == gid)) { return check_mac_perms(name, sctx); } } } return 0; }這個函數定義在system/core/init/property_service.c中。
參數name描述的是要增加或者修改的屬性的名稱,參數uid、gid和sctc分別描述要增加或者修改屬性的進程的uid、gid和安全上下文。
函數check_perms的作用是檢查請求增加或者修改屬性的進程是否具有應的權限,其中就包括基於UID/GID的權限檢查和基於SEAndroid的權限檢查,它的檢查過程如下所示:
1. 如果請求的進程的uid等於0,即這是一個root用戶進程,那麼就略過基於ID/GID的權限檢查,而直接調用函數check_mac_perms進行SEAndroid權限檢查。
2. 如果請求的進程的uid不等於0,就那麼就先調用multiuser_get_app_id檢查請求的進程是否是一個應用程序進程。如果是應用程序進程的話,使用其App ID進行安全檢查。在單用戶環境中,應用程序進程的UID即為應用程序的App ID,但是在多用戶環境中,兩者是不相等的。
3. 遍歷數組property_perms。檢查名稱為name的屬性是否在它的配置當中。如果在它的配置之中,一方面是要求請求進程具有與配置一樣的UID/GID,另一方面是要求請求進程的安全上下文符合SEAndroid安全策略定義的規則。如果不在它的配置當中,那麼就拒絕對名稱為name的屬性進行增加或者修改。
數組property_perms實際上是定義了一個白名單,用來規則什麼樣的進程可以增加或者修改什麼樣的屬性,它定義在文件system/core/init/property_service.c中,如下所示:
/* White list of permissions for setting property services. */ struct { const char *prefix; unsigned int uid; unsigned int gid; } property_perms[] = { { "net.rmnet0.", AID_RADIO, 0 }, { "net.gprs.", AID_RADIO, 0 }, { "net.ppp", AID_RADIO, 0 }, { "net.qmi", AID_RADIO, 0 }, { "net.lte", AID_RADIO, 0 }, { "net.cdma", AID_RADIO, 0 }, { "ril.", AID_RADIO, 0 }, { "gsm.", AID_RADIO, 0 }, { "persist.radio", AID_RADIO, 0 }, { "net.dns", AID_RADIO, 0 }, { "sys.usb.config", AID_RADIO, 0 }, { "net.", AID_SYSTEM, 0 }, { "dev.", AID_SYSTEM, 0 }, { "runtime.", AID_SYSTEM, 0 }, { "hw.", AID_SYSTEM, 0 }, { "sys.", AID_SYSTEM, 0 }, { "service.", AID_SYSTEM, 0 }, { "wlan.", AID_SYSTEM, 0 }, { "bluetooth.", AID_BLUETOOTH, 0 }, { "dhcp.", AID_SYSTEM, 0 }, { "dhcp.", AID_DHCP, 0 }, { "debug.", AID_SYSTEM, 0 }, { "debug.", AID_SHELL, 0 }, { "log.", AID_SHELL, 0 }, { "service.adb.root", AID_SHELL, 0 }, { "service.adb.tcp.port", AID_SHELL, 0 }, { "persist.sys.", AID_SYSTEM, 0 }, { "persist.service.", AID_SYSTEM, 0 }, { "persist.security.", AID_SYSTEM, 0 }, { "persist.service.bdroid.", AID_BLUETOOTH, 0 }, { "selinux." , AID_SYSTEM, 0 }, { NULL, 0, 0 } };例如,以“net.”開頭的屬性只有系統用戶進程(AID_SYSTEM)才可以增加或者修改。
一旦通過了基於UID/GID的白名單檢查之後, 接下來就調用函數check_mac_perms進行SEAndroid安全檢查,它的實現如下所示:
static int check_mac_perms(const char *name, char *sctx) { if (is_selinux_enabled() <= 0) return 1; char *tctx = NULL; const char *class = "property_service"; const char *perm = "set"; int result = 0; if (!sctx) goto err; if (!sehandle_prop) goto err; if (selabel_lookup(sehandle_prop, &tctx, name, 1) != 0) goto err; if (selinux_check_access(sctx, tctx, class, perm, name) == 0) result = 1; freecon(tctx); err: return result; }這個函數定義在system/core/init/property_service.c中。
參數name描述的是要進行增加或者修改的屬性的名稱,參數sctx描述的是請求增加或者修改屬性的進程的安全上下文。
只有在啟用了SEAndroid的情況下,函數check_mac_perms才會進行SEAndroid安全檢查。sehandle_prop是一個全局變量,用來描述我們在前面SEAndroid安全機制框架分析一文分析的文件external/sepolicy/property_contexts。這個文件定義了具有什麼樣的安全上下文的進程能夠增加或者修改什麼樣的屬性。
函數首先調用函數selabel_lookup獲得增加或者修改名稱為name的屬性所需要的安全上下文tctx,接著再調用函數selinux_check_access檢查請求增加或者修改屬性的進程的安全上下文sctx是否有權限對安全上下文為tctx的屬性進行操作。
全局變量sehandle_prop是在init進程啟動的時候初始化的,如下所示:
static int selinux_enabled = 1; int main(int argc, char **argv) { ...... if (selinux_enabled) { if (selinux_android_load_policy() < 0) { selinux_enabled = 0; INFO("SELinux: Disabled due to failed policy load\n"); } else { selinux_init_all_handles(); } } else { INFO("SELinux: Disabled by command line option\n"); } ...... }這個函數定義在文件system/core/init/init.c中。
在啟用SEAndroid的情況下,init進程首先調用函數selinux_android_load_policy加載SEAndroid安全策略到內核中的SELinux模塊中,接著再調用函數selinux_init_all_handles來加載描述文件和屬性安全上下文的文件。
函數selinux_init_all_handles的定義如下所示:
void selinux_init_all_handles(void) { sehandle = selinux_android_file_context_handle(); sehandle_prop = selinux_android_prop_context_handle(); }這個函數定義在文件system/core/init/init.c中。
函數selinux_android_file_context_handle由libseliux提供,用來打開我們在前面SEAndroid安全機制框架分析一文分析的文件external/sepolicy/file_contexts。因為init進程在啟動的過程中,需要創建一些文件。因此,它需要打開文件external/sepolicy/file_contexts,來獲得新創建的文件的安全上下文,並且進行相應的設置。設置文件安全上下文的過程可以參考前面SEAndroid安全機制中的文件安全上下文關聯分析一文。
下面我們主要分析一下函數selinux_android_prop_context_handle的實現,如下所示:
static const struct selinux_opt seopts_prop[] = { { SELABEL_OPT_PATH, "/data/security/property_contexts" }, { SELABEL_OPT_PATH, "/property_contexts" }, { 0, NULL } }; struct selabel_handle* selinux_android_prop_context_handle(void) { int i = 0; struct selabel_handle* sehandle = NULL; while ((sehandle == NULL) && seopts_prop[i].value) { sehandle = selabel_open(SELABEL_CTX_ANDROID_PROP, &seopts_prop[i], 1); i++; } if (!sehandle) { ERROR("SELinux: Could not load property_contexts: %s\n", strerror(errno)); return NULL; } INFO("SELinux: Loaded property contexts from %s\n", seopts_prop[i - 1].value); return sehandle; }這個函數定義在文件system/core/init/init.c中。
函數selinux_android_prop_context_handle依次檢查設備上的/data/security/property_contexts和/property_contexts文件。如果其中一個存在,就調用selinux庫提供的函數selabel_open對它進行打開並且解析,以便以後可以調用函數selabel_lookup進行檢索。
關於函數selabel_open、selabel_lookup,以及前面提到的selinux_check_access,我們就不進行分析了,它們無非就是執行一些文本分析和比較的工作。
這樣,我們就分析完成屬性增加或者修改所要進行的UID/GID和SEAndroid安全檢查了。回到前面分析的函數handle_property_set_fd中,一旦通過了安全檢查,接下來就會調用函數property_set對請求的屬性進行增加或者修改。
函數property_set的實現如下所示:
int property_set(const char *name, const char *value) { prop_area *pa; prop_info *pi; ...... pi = (prop_info*) __system_property_find(name); if(pi != 0) { /* ro.* properties may NEVER be modified once set */ if(!strncmp(name, "ro.", 3)) return -1; pa = __system_property_area__; update_prop_info(pi, value, valuelen); pa->serial++; __futex_wake(&pa->serial, INT32_MAX); } else { pa = __system_property_area__; if(pa->count == PA_COUNT_MAX) return -1; pi = pa_info_array + pa->count; pi->serial = (valuelen << 24); memcpy(pi->name, name, namelen + 1); memcpy(pi->value, value, valuelen + 1); pa->toc[pa->count] = (namelen << 24) | (((unsigned) pi) - ((unsigned) pa)); pa->count++; pa->serial++; __futex_wake(&pa->serial, INT32_MAX); } /* If name starts with "net." treat as a DNS property. */ if (strncmp("net.", name, strlen("net.")) == 0) { if (strcmp("net.change", name) == 0) { return 0; } /* * The 'net.change' property is a special property used track when any * 'net.*' property name is updated. It is _ONLY_ updated here. Its value * contains the last updated 'net.*' property. */ property_set("net.change", name); } else if (persistent_properties_loaded && strncmp("persist.", name, strlen("persist.")) == 0) { /* * Don't write properties to disk until after we have read all default properties * to prevent them from being overwritten by default values. */ write_persistent_property(name, value); } else if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) { selinux_reload_policy(); } property_changed(name, value); return 0; }這個函數定義在文件system/core/init/property_service.c中。
參數name表示要增加或者修改的屬性的名稱,參數value表示要增加或者修改的屬性的新值。
函數property_set首先是調用函數__system_property_find檢查名稱為name的屬性是否已經存在屬性內存區域中:
1. 如果存在的話,那麼就會得到一個類型為prop_info的結構體pi,表示接下來要做的是修改屬性。這時候就會調用我們在上面分析的函數update_prop_info進指定的屬性進行修改。
2. 如果不存在的話,那麼指針pi的值就會等於NULL。表示接下來要做的增加屬性。這時候只需要在屬性內存區域的屬性值列表pa_info_array的最後增加一項即可。
注意,無論是修改屬性,還是增加屬性,都需要將屬性內存區域頭部的serial值增加1,表示對屬性內存區域進行了一次修改,並且調用函數__futex_wake通知正在等待屬性內存區域修改完成的進程。
增加或者修改完成屬性之後,還要進行以下的檢查:
1. 如果屬性的名稱是以“net.”開頭,但是又不等於“net.change”,那麼就將名稱為“net.change”的屬性設置為1,表示網絡屬性發生了變化。
2. 如果屬性的名稱是以“persist.”開頭,那麼就表示該屬性應該是持久化儲存到文件中去,因此就會調用函數write_persistent_property執行這個操作,以便系統下次啟動後,可以將該屬性的初始值設置為系統上次關閉時的值。
3. 如果屬性的名稱等於“selinux.reload_policy”,並且前面給它設置的值等於1,那麼就表示要重新加載SEAndroid策略,這是通過調用函數selinux_reload_policy來實現的。SEAndroid安全策略的加載過程可以參考前面SEAndroid安全機制框架分析一文。
最後,函數property_set調用另外一個函數property_changed發送一個名稱為name的屬性發生了變化的通知。以便init進程可以執行在啟動腳本init.rc中配置的操作。
至此,我們就分析完成SEAndroid安全機制對Android屬性設置的保護支持了,並且順便分析了Android屬性的實現原理。屬性是Android系統中特有的資源,通過它們除了可以獲得系統的信息之外,還可以對系統的行為進行控制,因此,SEAndroid安全機制需要像文件一樣對它們進行保護。在Android系統中,除了屬性是特有的概念之外,還有Binder IPC也是特有的機制。在接下來的一篇文章中,我們就繼續分析SEAndroid安全機制對Binder IPC的保護支持,敬請關注!更多信息可以關注老羅的新浪微博:http://weibo.com/shengyangluo。
效果:靜態導入平滑移動到指定位置的模板代碼布局:activity_main item_list
微信團隊推送了一條消息,大家期待已久的微信公眾平台手機版正式上線,當然目前還僅限HTML5的操作,暫無客戶端下載。那麼對於要運營微信公眾號的人來說,這個微信
經過前三篇文章的學習,Volley的用法我們已經掌握的差不多了,但是對於Volley的工作原理,恐怕有很多朋友還不是很清楚。因此,本篇文章中我們就來一起閱讀
在Launcher3中,有三處長按拖拽處理:主屏幕(Workspace)上的圖標和小部件文件夾中的圖標抽屜中的圖標和小部件這三種情況的拖拽處理是相似的的,我們只需知道其中