在“上一篇android init.rc文件語法詳解”,但是到了android5.0之後,按照上面的方法做,可能我們要啓動的服務就起不來了。這是因爲採用了新的安全機制了——SEAndroid/SElinux的安全機制。
下面就介紹下,在SEAndroid/SElinux如何配置才能啓動init.rc裏面定義的服務。
下面我們在rc文件裏面定義一個服務
service test1 /system/bin/test
user root
group root
disabled
oneshot
然後再on boot得時候啓動這個
on boot
start test1
如果在android5.0之前的系統,這樣就可以在on boot啓動這個服務了。但是到了android5.0之後,這個服務可能啓動失敗了。
查看串口信息(注意不是logcat的信息,是串口信息),我們會看到“init: Warning! Service test1 needs a SELinux domain defined; please fix!”這樣警告。這是因爲我們沒有爲service test1定義SELinux的權限規則。
對於沒有定義SELinux的權限規則的service,系統只是給出一條警告,還是會繼續啓動這個進程。如果我們的服務沒有觸及到未允許的權限操作,那麼這個服務一樣會正常啓動的,我們可以直接無視這個警告。但是如果觸及到未允許的權限操作,那麼這個服務可能就不能正常啓動。這就需要我們定義一個SELinux domain,添加需要的權限。下面就介紹如何操作。
第一步,在devices/platform/platform-sub/sepolicy/目錄下添加一個test1.te的文件(注意不同的平臺可能路徑不完全一樣,反正找到sepolicy目錄就好了,如果在上面介紹的路徑下沒有sepolicy目錄,我們也可以直接添加到external/sepolicy/下)。
然後在sepolicy目錄下的Android.mk文件中添加下面的內容
BOARD_SEPOLICY_UNION := \
... \
test1.te
第二步,在我們新建的test1.te文件中添加如下內容
type test1, domain;
type test1_exec, exec_type, file_type;
init_daemon_domain(test1)
然後在file_contexts(如果在sepolicy目錄下沒有,也可以添加到external/sepolicy/file_contexts)文件中添加如下內容
/system/bin/test u:object_r:test1_exec:s0
然後編譯,燒錄到目標板,重新啓動。這時再看串口信息,“init: Warning! Service test1 needs a SELinux domain defined; please fix!”這個警告沒有了。但是我們的服務還是沒有正常啓動起來。這是因爲我們還沒有添加權限。
第三步,我們查看logcat的調試信息。我們可以通過by Log Message="avc"來過濾。
在logcat裏面,我們會看到類似下面的警告
avc: denied { execute_no_trans } for path="/system/bin/toolbox" dev="mmcblk0p7" ino=306 scontext=u:r:test1:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0
說明test1沒有execute_no_trans的權限。這就需要我們添加權限,我們可以在test1.te文件中按下面的格式添加權限
allow scontext tcontext:tclass {denied ...};
例如對於上面的警告,我們可以加上如下的語句
allow test1 system_file:file {execute_no_trans};
也可以是這樣 allow test1 system_file:file execute_no_trans;
如果最後面的denied 是多項 就必須用{}括起來。如果是一項是可以不要括號的。
如果我們想賦予這個class下的所有權限,可以用通配符*號來代替。例如
allow test1 system_file:file *;
下面舉幾個例子
avc: denied { execute } for name="pppd" dev="mmcblk0p7" ino=254 scontext=u:r:pppd_gprs:s0 tcontext=u:object_r:ppp_exec:s0 tclass=file permissive=0
對於上面的警告,則需在pppd_gprs.te(如果沒有這個文件需要參考前面的方法添加這個文件)添加下面的語句
allow pppd_gprs ppp_exec:file execute ;
denied { write } for name="/" dev="mmcblk0p2" ino=1 scontext=u:r:init:s0 tcontext=u:object_r:vfat:s0 tclass=dir permissive=0
在init.te添加下面的語句
allow init vfat:dir { write };
另外添加了權限,有可能編譯出錯。
例如我們添加了
allow test1 system_file:file entrypoint;
可能會出現下面的編譯錯誤
libsepol.check_assertion_helper: neverallow on line 239 of external/sepolicy/domain.te (or line 5194 of policy.conf) violated by allow test1 system_file:file { entrypoint };
我們看看domain.te的239行,有下面的語句
neverallow domain { file_type -exec_type }:file entrypoint;
neverallow和我們定義的allow衝突了,我們需要設置test1爲例外
所以要修改上面那一句爲
neverallow { domain -test1 } { file_type -exec_type }:file entrypoint;
再編譯,通過了。把image下載到目標板上再運行,服務正常跑起來了。
如果還是不能正常運行,再看logcat的調試信息,看看還有沒有avc的警告,如果有,繼續重複前面的步驟,直到沒有警告爲止,服務就可以正常運行了。
通過上面的方法,在init.rc裏面啓動服務已經沒有問題了,但是我們有很多服務不是在init.rc裏面啓動的,而是在其他的服務或者進程中根據某些條件,通過property_set("ctl.start", "xxx")、property_set("ctl.stop", "xxx")來啓動或者結束服務。例如我們常用的3G、4G的驅動,往往就是在rild中初始化模塊OK後啓動pppd_gprs這個服務的。
但是在5.0下,我們往往會發現rild正常工作了,但是pppd_gprs沒有啓動。logcat看也沒有avc相關的警告。下面就以這個爲例子,看看怎麼解決這個問題。
我們接上調試串口,可以看到
init: sys_prop: Unable to start service ctl [pppd_gprs] uid:0 gid:1001 pid:148
init: sys_prop: Unable to stop service ctl [pppd_gprs] uid:0 gid:1001 pid:148
這是因爲android在SELINUX的基礎上增加了對property的權限的限制。
這個串口信息是在 /system/core/init/property_service.c的handle_property_set_fd函數中打印的。通過這個調試信息可以看出,是PID=148的進程,沒有設置ctl.start的權限,造成的。再用ps查看當前進程,可以看到PID爲148的進程是rild。那麼我們給rild賦予權限就好了。下面介紹如何添加
property對應的上下文都定義在external/sepolicy/property_contexts文件中,打開這個文件我們可以看到裏面有“ctl. u:object_r:ctl_default_prop:s0”的定義,說明ctl.start和ctl.stop對應的上下文是ctl_default_prop。在這裏,我們要給rild賦予設置環境變量的權限。
我們就需要在rild.te的文件中增加allow rild ctl_default_prop:property_service set;就可以了。如果沒有這個文件,我們可以參考前面的介紹自己添加一個文件。然後再編譯,運行,之前的提示沒有了,再看pppd_gprs也已經啓動起來了。
在pppd_gprs的腳本里面,也許有設置property的地方,我們會發現設置property沒有生效,看串口信息,有init:sys_prop: permission denied uid:169 name:net.ppp0.local-ip的提示。這個也是在handle_property_set_fd中打印的,這個原因是一樣的,也是pppd_gprs服務沒有權限。同樣的參考原來的方法增加權限就好了。如果不知道是哪個服務設置的property,我們可以把source_ctx也打印出來,就知道是哪個服務了。
最後還有一個問題,如果我們設置的property在property_contexts沒有定義怎麼辦呢?
例如前面舉例的net.ppp0.local-ip沒有定義,那怎麼處理呢?方法如下
1 在property_contexts中添加 net.ppp0.local-ip u:object_r:net_radio_ppp0_prop:s0(net_radio_ppp0_prop可以取任何有名字,也可以是這個文件裏面已經定義的名字)
2 在property.te文件中增加type net_radio_ppp0_prop, property_type;(如果1中定義的是已經有的名字,2就不需要了)
3 參考前面的方法添加權限。就可以了