淺析 VENDOR INIT
https://source.android.google.cn/security/selinux/vendor-init
推薦先閱讀一下以上Google官方文章
定義vendor_init context,從這裏可以看出,在odm和vendor分區的文件相關操作,其和vendor_init的關聯性比較大
使用場景:
1.SystemProperties 屬性加載相關
開機過程在load system property的時候,其都會走到該函數load_properties_from_file,隨後會調用到LoadProperties,其有如下代碼
就是當prop文件是在vendor/odm分區的時候,系統會用vendor_init的context去設置該perperity,在HandlePropertySet 函數中,會調用如下代碼做selinux檢查
其中source_context就是vendor_init,target_context就是要設置的屬性對應的secontext(定義在property_contexts中),最後調用selinux_check_access函數來檢查vendor_init對該屬性的set操作是否allow
所以如果通過如下方式添加或者修改屬性值
PRODUCT_PROPERTY_OVERRIDES += persist.vendor.bluetooth.modem_nv_support=true
有可能在實際操作中出現通過adb shell getprop 獲取不到該值,需要注意,此時有2種可能性。
①代碼中功能生效,但是adb shell getprop獲取不到
針對該情況,說明屬性值已經被正確的設置到系統中,只是通過adb shell getprop不能獲取,原因在於shell環境的secontext是u:r:shell:s0,如果沒有加上對應的selinux policy,或者有些屬性本身就對shell neverallow get,那麼就讀取不到,譬如上文提到的
type=1400 audit(0.0:35835): avc: denied { read } for name="u:object_r:vendor_default_prop:s0" dev="tmpfs" ino=16504 scontext=u:r:shell:s0 tcontext=u:object_r:vendor_default_prop:s0 tclass=file permissive=0
對於vendor_default_prop而言,vendor_init有設置權限,通過以下selinux 規則可以知道
set_prop(vendor_init, vendor_default_prop)
但是對於shell context而言,其沒有明確聲明權限讀取,而SELINXU policy中就是 權限默認是不允許的,除非主動聲明(allow subject object:class operation),否則都視爲不允許,所以如果沒有聲明,就是不被允許,也就會出現上面log中描述的權限被拒絕。
②代碼中功能不生效,adb shell getprop也獲取不到
針對該情況,說明該屬性值在開機過程中並沒有被成功設置到property service中,這個時候就要抓到開機log,一般情況下,說明vendor_init 沒有權限去設置該屬性,那麼此時就需要添加vendor_init 對該屬性的set權限的對應規則了。
2.service相關
在init中,我們可以發現如下代碼
所以這裏可以預估到在service相關以及on section相關的流程中,vendor_init也會有相應作用。
這裏我們先來看一下init進程中的InitializeSubcontexts,請注意該函數的返回值std::vector<Subcontext> subcontexts; 是Subcontext的vector,不是string類型。其中
注意下,使用的是c++ 11新特新 auto結合for來使用,這裏就會從paths_and_secontexts的二維數組中取出來其中一行,其中的2個元素分別爲path_prefix和secontext,然後通過
subcontexts.emplace_back(path_prefix, secontext);直接將path_prefix和secontext new成一個subcontext對象,然後加入到subcontexts這個vector集合中,這裏的話有2個subcontext對應,需要注意subcontext對象
New的時候會執行for函數
看完該函數我們可以發現該函數主要做了以下事情
① 創建一對socket socket_和subcontext_socket
② 在當前進程上下文(init進程)下執行fork操作,創建子進程
③ 在子進程中,重新設置進程的context,這裏就是u:r:vendor_init:s0了(同時由於paths_and_secontexts是有2個,所以這裏會創建2次,這樣也就最終會有2個vendor_init,也就最終導致了系統有2個vendot_init context的init進程),並且傳入參數(fd,secontext,init進程的路徑,以及subcontext),重新執行init(vendor_init)進程
Init(vendor_init)進程中會執行以下code邏輯
將context(這裏就是vendor_init)以及subcontext_socket和function_map作爲變量傳入SubcontextProcess,subcontext_socket保存到SubcontextProcess的init_fd_中,然後就執行死循環MainLoop,也就是2個子進程已經進入了死循環,等待命令來處理的狀態,那麼命令從何而來?也就是我們平時怎麼切入到vendor_init的上下文環境?
④上述是子進程的邏輯,init主進程fork之前,會先創建socket_和subcontext_socket一對sockerpair,可以猜想到,init與vendor_init之間的通訊,很有可能就是通過他們倆,socket_保存在subcontext對象的變量中,這裏要注意,2個subcontext對象的上下文還是在init進程,而非vendor_init子進程中,SubcontextProcess是運行在vendor_init子進程上下文裏。
以上講述了vendor_init子進程創建的過程,接下來我們就講和subcontext緊密關聯的部分code
先來看service部分(system/core/init/service.cpp),在service ParseSection的環節中
會通過service初始化傳入到onrestart變量中(onrestart是一個action變量)
也就是該subcontext會和位於vendor/odm 的 rc文件中的service的onrestart操作相關聯,其中vendor分區rc文件對應的vendor subcontext,odm分區對應rc文件對應odm subcontext。
當service某些原因發生了重啓
ReapOneProcess->service->Reap(siginfo)->onrestart_.ExecuteAllCommands()->
command.InvokeFunc(subcontext_);
注意下invokefunc函數
其中subcontext_就是onrestart傳入進去的subcontext,execute_in_subcontext_則是new command時候傳入的
從代碼得知,當前new command應該是在AddCommand中產生的
第二種情況傳入了f的話,execute_in_subcontext_默認就是false,第一種情況,如果沒有傳入f的話,那麼會在function_map_中去查找,其最終對應的是builtins.cpp中的
其中第一個代表command,第二,三2個數字代表可傳入參數個數的範圍,第四個代表就是表示execute_in_subcontext_,第五個表示最終command執行的函數。
我們再回到InvokeFunc
①如果當前subcontext_爲空,也就是action對應的rc文件是在system分區,那麼直接直接調用RunBuiltinFunction(func_, args_, kInitContext);就是在init進程(u:r:init:s0)中執行該函數。
②如果subcontext_,但是execute_in_subcontext_爲空(也就是function_map_中那些第四個參數爲false的情況),這裏會先執行subcontext->ExpandArgs,請注意ExpandArgs和③中一樣,也會最終調用TransmitMessage,然後會在vendor_init(SubcontextProcess)中執行,但是通過代碼可以發現,其實SubcontextProcess的ExpandArgs函數並沒有真正執行什麼,只是做了一些strings的檢查和轉換,隨後在SubcontextProcess返回後,又在init中執行
RunBuiltinFunction(func_, *expanded_args, subcontext->context()) ,這裏我們可以看到在init進程中執行這些函數用到subcontext->context()的地方其實很少,當前只有do_restorecon_recursive,do_installkey和do_init_user0會應到args.context。
③subcontext_不爲null和execute_in_subcontext_爲true(就是command對應的rc爲odm or vendor,並且在function_map中的參數爲true),這種情況下會執行到
subcontext->Execute(args_) ---->subcontext->TransmitMessage
這裏可以看到,文章一開始講的socket_在這裏排上用場了,這裏就直接將args參數相關的command通過socket_傳輸,通訊方就是socketpair的另一個,也就是傳入到SubcontextProcess中的init_fd_,這樣SubcontextProcess子進程MainLoop就會從ReadMessage中喚醒過來
然後解析參數,這裏subcontext_command.command_case()的類型爲SubcontextCommand::kExecuteCommand,原因是在subcontext->Execute中,以下代碼
mutable_execute_command會通過set_has_execute_command()將subcontext_command類型設置爲kExecuteCommand(這些代碼都是protobuf自動生成的,在out目錄),所以這裏執行Runcommand
在該函數中,可以看到熟悉的init中的執行代碼,RunBuiltinFunction,也就是在vendor_init中執行command,可以發現,vendor_init中設置屬性(command中setprop)的操作通過
reply->add_properties_to_set()重新回傳到init進程,然後在init的subcontext->Execute函數
中重新設置,當然這裏的context_是vendor_init。
這裏有2點值得思考
①爲什麼不在SubcontextProcess子進程中執行真正的設置屬性的操作,而是要回傳到init中去做該操作
我個人的理解是 propertyservice是運行在init父進程中,其最終設置只能在init父進程,子進程已經沒有propertyservice的運行環境。
②上述操作可否不用傳來傳去,直接在init進程操作,過濾對應command傳入到SubcontextProcess子進程?
這個我覺得應該可行
TransmitMessage裏面的數據傳輸使用了Google Protocol Buffer格式,使用方法等可參見
https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html
https://www.cnblogs.com/dkblog/archive/2012/03/27/2419010.html
3. On section相關
上面章節討論了service相關,該節我們來看一下on section相關,rc中,on section相關的解析函數爲ActionParser,其解析主函數ParseSection實現如下
通過代碼我們很明顯的發現,subcontexts_和2章節中一致,這裏的代碼涉及到2部分,on event section 以及on property section,不管是event 還是property,最終都會生成action,只是on event section是event_trigger,而on properton是property_triggers。這裏說明一下event_trigger,譬如on boot,那麼event_trigger就是boot。
ParseTriggers函數就是用來解析event_trigger和property_triggers,從代碼邏輯來看,可以發現一個section,可以是event_trigger或者property_triggers,或
event_trigger+property_triggers;一個event section只能有一個event_trigger+0個或者多個property_triggers,譬如(on)boot 或者(on) fs等;而property_triggers可以由多個property_trigger一起觸發,譬如如下代碼
on property:sys.usb.config=none && property:sys.usb.configfs=0
stop adbd
在ParsePropertyTrigger中,我們可以看到有如下邏輯
這裏可以看到,如果subcontext不爲空(也就是rc文件不在/system/),那麼property_trigger是有合法性判斷的,只有屬性名字在kExportedActionableProperties或者屬性是以kPartnerPrefixes開頭的,才被允許進行property_trigger。
Section被解析正確後,會new一個action來保存
隨後解析section的command,通過以下函數將其加入到action中
最終加入到action_managerde action隊列中
我們來簡單看一下action的執行流程
在init的main函數中
am.ExecuteOneCommand() -> action->ExecuteOneCommand(current_command_)
->action->ExecuteCommand
流程就走到了command.InvokeFunc(subcontext_),這裏是不是很熟悉?對了,在上面service流程中已經講述了整個InvokeFunc流程,這裏就不累述了。
通過以上我們可以總結以下幾點
①整個android系統action(command)執行入口是在init中(am.ExecuteOneCommand())。
②subcontext code的運行上下文是在init進程,SubcontextProcess 的運行上下文是在vendor_init子進程(vendor_init context)
③對於action(section command)而言,如果其定義是在vendor/odm分區,其會有subcontext關聯,但是其command是否執行在vendor_init,需要看該command的execute_in_subcontext_是否爲true,也就是builtin_functions中定義的第四個參數。
④對於service而言,我們可以看到do_start以及do_class_restart的execute_in_subcontext_都爲false,所以service本身的執行域都是在init中,這裏可以從service起來後的ppid可以看到,其都爲1;但是onrestart的command話會根據③的規則選擇運行的init,vendor_init進程
⑤可以看到setprop command {"setprop",{2, 2, {true, do_setprop}}},說明setprop命令如果subcontex存在,那麼其會選擇vendor_init作爲運行域,但是通過Page7的分析我們可以得知,其最終還是會回傳給init域去做處理,這裏是我不太明白的地方,這裏希望你們能夠幫忙解答疑惑。
以上是鄙人對VENDOR_INIT相關內容進行的初步分析,其中內容可能有誤,如果發現,請幫忙告知,不甚感激,謝謝。