為Android應用程序讀取/dev下設備而提權(quán)(二)
在為Android應用程序讀取/dev下設備而提權(quán)(一)中,簡單總結(jié)了提權(quán)的兩種方法: device_init和init.rc 。在此篇文章中,我將詳細總結(jié)的是稍一不留神,就容易把人弄暈乎的init.c、device_init和init.rc 三者之間的關(guān)系,TA們到底是如何工作的。
目錄結(jié)構(gòu) ls一下system/core/init/ devices.c、devices.h、init.c、init.h、keywords.h、parser.c、property_service.c.... 另外system/core/rootdir/init.rc ,當然init.rc的位置可以另行指定。
init流程 init過程的起點是init.c : *注釋中的序號表示執(zhí)行順序 - int main(int argc, char **argv)
- {
- … …
- mkdir("/dev", 0755); //建立基本文件系統(tǒng)節(jié)點
- mkdir("/proc", 0755);
- mkdir("/sys", 0755);
- mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");
- mkdir("/dev/pts", 0755);
- mkdir("/dev/socket", 0755);
- mount("devpts", "/dev/pts", "devpts", 0, NULL);
- mount("proc", "/proc", "proc", 0, NULL);
- mount("sysfs", "/sys", "sysfs", 0, NULL);
- … …
- INFO("reading config file\n");
- parse_config_file("/init.rc"); // 1、 調(diào)用parse_config 函數(shù)解析init.rc腳本
- //11、經(jīng)過解析,init.rc的內(nèi)容就被分為多少個段,被串在action_list鏈表中。on 開頭的都是action類型的段,比如init段,init段用一個結(jié)構(gòu)體struct action表示, 其中name是init,所有這個段內(nèi)的命令,都被串在commands鏈表中。
- action_for_each_trigger("early-init", action_add_queue_tail); //12、 遍歷action_list鏈表,查找name是early-init的那個action,將這個節(jié)點放在action_queu e的尾部。
- drain_action_queue(); // 13、將action_queue尾部的節(jié)點遍歷,然后刪除。就相當于遍歷name是early-init的action節(jié)點內(nèi)的commands鏈表。就是在執(zhí)行init.rc腳本中 on early-init段內(nèi)的所有命令。
- … …
- INFO("device init\n");
- device_fd = device_init(); //常見必要的設備節(jié)點
- property_init(); //init 以后的任務就是proper_service
-
- action_for_each_trigger("init", action_add_queue_tail); //14、將init 段,加入action_queue
- drain_action_queue(); // 執(zhí)行init段得命令
- … …
- }
system/core/init/parser.c:
- static void parse_config(const char *fn, char *s)
- {
- struct parse_state state;
- char *args[MAXARGS];
- int nargs;
-
- nargs = 0;
- state.filename = fn;
- state.line = 1;
- state.ptr = s;
- state.nexttoken = 0;
- state.parse_line = parse_line_no_op; //這個函數(shù)是空的,就是什么都不做
- for (;;) {
- switch (next_token(&state)) { // 2、 和T_TEXT狀態(tài)配合,先把把每一行的參數(shù)都放在args數(shù)組里
- case T_EOF:
- state.parse_line(&state, 0, 0); // 最后看這,到此文件解析完成,也是上一段的完成,需要寫個NULL表示末尾。
- return;
- case T_NEWLINE:
- if (nargs) {
- int kw = lookup_keyword(args[0]); // 3、得到新的一行,開始解析,判斷一下拿到的第一個參數(shù)是什么關(guān)鍵字,這里面有幾種情,命令COMMAND,段SECTION,和選項OPTION,這個選項是針對服務的,開啟,關(guān)閉等操作。
- if (kw_is(kw, SECTION)) { // 4、判斷得到的關(guān)鍵字是不是段,keywords.h里定義了各種能解析的關(guān)鍵字分別是什么屬性。
- state.parse_line(&state, 0, 0); // 表示上一段解析結(jié)束,因為使用的是雙向鏈表,這樣就給鏈表最后一個元素寫NULL,表示到末尾了。
- parse_new_section(&state, kw, nargs, args); // 5、創(chuàng)建一個新段的鏈表,比如init段,先跳到這個函數(shù)看,然后再回來。
- } else {
- state.parse_line(&state, nargs, args); //10、 得到新的一行,通過上面的操作已經(jīng)知道現(xiàn)在是在什么段中,是on 還是service,行解析函數(shù)也做了相應變化,開始解析這一行,加入action的commands鏈表中。
- }
- nargs = 0;
- }
- break;
- case T_TEXT:
- if (nargs < MAXARGS) {
- args[nargs++] = state.text;
- }
- break;
- }
- }
- }
-
-
- void parse_new_section(struct parse_state *state, int kw,
- int nargs, char **args)
- {
- printf("[ %s %s ]\n", args[0],
- nargs > 1 ? args[1] : "");
- switch(kw) { // 6、這里判斷 是什么類型的段,不同類型的段使用的解析函數(shù)不同,說白了就是分命令還是服務。
- case K_service:
- state->context = parse_service(state, nargs, args);
- if (state->context) {
- state->parse_line = parse_line_service;
- return;
- }
- break;
- case K_on:
- state->context = parse_action(state, nargs, args); // 7、創(chuàng)建一個action 鏈表,把這個鏈表加入到action_list中
- if (state->context) {
- state->parse_line = parse_line_action; // 8、把行解析函數(shù)換掉,原來是parse_no_op 什么都不做,再在要把每行都解析成一個命令動作。把這個命令動作加入到action中的commands鏈表內(nèi)
- return;
- }
- break;
- }
- state->parse_line = parse_line_no_op; // 9、走到這就是出錯了,段的名字沒寫或者寫多了
- }
本章小結(jié) 經(jīng)過上面的分析,對/dev/設備權(quán)限的修改放在不同的位置會有覆蓋的效果,device.c內(nèi)的修改會覆蓋early-init段內(nèi)的命令,init 段內(nèi)的命令會覆蓋device.c中的修改,如果3個位置都有對用一個設備權(quán)限的修改,那init段的修改會最終生效。
|