淺析vendor_init

淺析 VENDOR INIT

[email protected]

 

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相關內容進行的初步分析,其中內容可能有誤,如果發現,請幫忙告知,不甚感激,謝謝。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章