本文轉載自:https://zhuanlan.zhihu.com/p/32868074
本人對於 SELinux for Android
理解不深,下文中的各文件及安全規則雖都是我所編寫,但也是一邊查閱文檔一邊試驗得出的。在此強行為文,若有理解錯誤之處,請各位工程師同仁熱情指出。
感謝
指出文章不足。按照 Project Treble 的要求,這種需求的規范做法應該是通過 HIDL 進行調用並添加上相應的 SELinux 權限,所以下文中的做法只能算是一種 workaround。相關機制我還在研究中,后續我再按規范來修改文章內容,如果有工程師朋友已經按照 Treble 規范實現過這樣的需求了,非常歡迎留言溝通交流。
文章就不刪了,作為對自己的警醒。身為工程師,不當浮躁趨名,而應謹慎穩重、厚積薄發。
------------------------------------ 分割線下內容作廢 -------------------------------------
一、需求描述
項目上需要在 Android 8.0
上實現開機自動調用可執行文件 /system/bin/aaa
去處理 /vendor/lib/bbb.so
,並將處理后的文件保存為 /tmp/ds/ccc.so
。這期間 /system/bin/aaa
在處理文件時會去訪問 /dev/ddd
節點,並且在開機時 /tmp/ds/
目錄並不存在,需要每次開機時創建。
其中 /system/bin/aaa
和 /dev/ddd
是自定義添加的可執行文件和設備節點,在原生 Android 8.0 系統中並不存在。
二、實現思路
- 編寫一個腳本實現調用
/system/bin/aaa
對文件/vendor/lib/bbb.so
進行處理。 - 在
.rc
文件中新增創建/tmp/ds/
目錄的代碼和新增一個 service 用於啟動 步驟1 中編寫的腳本。 - 新增
.te
文件並編寫相應 sepolicy 規則以解決權限問題。
三、實現過程
首先編寫開機啟動腳本。因為 Google 啟動了 Treble 計划,對 system 分區和 vendor 分區進行嚴格的分離,所以這樣的自定義腳本我們最好不要放在 /system/bin/
目錄下。因為我工作於芯片公司,即 vendor 廠商,所以我把它放在 /vendor/bin/
目錄下。同時,從 Android 8.0
開始,SELinux for Android
也進行了模塊化,為了避免不必要的權限問題,我們使用 /vendor/bin/sh
作為腳本解釋器。我編寫的開機腳本是 prepare_ds.sh
,內容很簡單,如下:
#!/vendor/bin/sh
/system/bin/aaa /vendor/lib/bbb.so /tmp/ds/ccc.so
腳本應該在編譯時自動拷貝到 vendor/bin/
目錄下,所以我們還要在 makefile 中添加拷貝命令。以我這個項目為例,對應的 makefile 文件是 product_mbox.mk
,為其添加如下代碼:
# aaa Security
PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/../../prepare_ds.sh:/vendor/bin/prepare_ds.sh
然后在相應的 .rc
文件中添加代碼。這是為了在開機時創建 /tmp/ds/
目錄,以及以 service 的形式運行腳本 prepare_ds.sh
。以我這個項目為例,需要在 init.vendorName.board.rc
中添加如下語句:
on init
mkdir /tmp 0770 root root
mkdir /tmp/ds 0770 root root
mount tmpfs tmpfs /tmp/ds
on boot
#aaa security
chmod 0666 /dev/ddd
chmod 0777 /system/bin/aaa
chmod 0666 /vendor/lib/bbb.so
start aaa_security
chmod 0666 /tmp/ds/ccc.so
service aaa_security /vendor/bin/sh /vendor/bin/prepare_ds.sh
user root
group root
disabled
oneshot
seclabel u:r:aaa_security:s0
這幾行命令的意思是在系統 init 階段創建 /tmp/ds/
目錄,定義名為 aaa_security 的服務,並在 boot 階段以 root 身份顯式啟動且僅啟動服務一次。
接着我們還要為上一步驟里定義的 aaa_sercurity
服務編寫 .te
文件,定義其所屬的安全域(SELinux domain)。否則設備在開機后無法執行這個 service,並且我們可以在 dmesg 中看到下方這樣的警告:
[ 9.910511] init: service aaa_security does not have a SELinux domain defined
在 device/<vendorName>/common/sepolicy/
目錄下新建 aaa_security.te
文件。我們可能並不清楚開機運行這個腳本並執行相應的調用需要哪些權限,所以我們可以先在 .te
文件中添加上基本的定義。這些基本的文件內容如下:
########################################
# sepolicy rules for aaa_security
########################################
type aaa_security, domain;
type aaa_security_exec, exec_type, vendor_file_type, file_type;
permissive aaa_security;
init_daemon_domain(aaa_security)
其中 aaa_security
這個域對應 .rc
文件中定義的服務 aaa_security,aaa_security_exec
對應腳本文件 prepare_ds.sh
。語句 permissive aaa_security
是暫時添加的,目的是使服務在遇到權限問題時也可以正常執行,但會將所需的權限類型打印出來。init_daemon_domain
是一個宏,用來使 aaa_security 域生效。
因為 /system/bin/aaa
在處理文件時要訪問 /dev/ddd
節點,所以我們也要為這個設備節點編寫文件 aaa_device.te
。內容如下:
########################################
# sepolicy rules for aaa_device
########################################
type aaa_device, file_type, dev_type;
最后,在 device/<vendorName>/sepolicy/file_contexts
里為這些新增的文件指定要使用的安全上下文,我們的工作就接近完成了。新增語句如下:
/vendor/bin/prepare_ds.sh u:object_r:aaa_security_exec:s0
/dev/aaa u:object_r:aaa_device:s0
為了驗證我們的修改是否有效,需要編譯並燒寫 boot.img 和 vendor.img,然后重啟設備。
不過最初編寫的 aaa_security.te
文件中,往往賦予開機腳本的權限會有不足,我們需要通過查看 dmesg 中的打印來添加上相應的權限。比如我們可能會看到下面這樣的權限缺失提示:
[ 10.368517] type=1400 audit(1483292256.112:14): avc: denied { execute_no_trans } for pid=2768 comm="prepare_ds.sh" path="/vendor/bin/toybox_vendor" dev="mmcblk0p14" ino=222 scontext=u:r:aaa_security:s0 tcontext=u:object_r:vendor_toolbox_exec:s0 tclass=file permissive=1
[ 10.434460] type=1400 audit(1483292256.112:14): avc: denied { execute_no_trans } for pid=2768 comm="prepare_ds.sh" path="/vendor/bin/toybox_vendor" dev="mmcblk0p14" ino=222 scontext=u:r:aaa_security:s0 tcontext=u:object_r:vendor_toolbox_exec:s0 tclass=file permissive=1
[ 10.662017] type=1400 audit(1483292256.408:16): avc: denied { read write } for pid=2775 comm="aaa" name="aaa" dev="tmpfs" ino=1287 scontext=u:r:aaa_security:s0 tcontext=u:object_r:device:s0 tclass=chr_file permissive=1
[ 10.666135] type=1400 audit(1483292256.408:16): avc: denied { read write } for pid=2775 comm="aaa" name="aaa" dev="tmpfs" ino=1287 scontext=u:r:aaa_security:s0 tcontext=u:object_r:device:s0 tclass=chr_file permissive=1
[ 10.666145] type=1400 audit(1483292256.408:17): avc: denied { open } for pid=2775 comm="aaa" path="/dev/ddd" dev="tmpfs" ino=1287 scontext=u:r:aaa_security:s0 tcontext=u:object_r:aaa_device:s0 tclass=chr_file permissive=1
[ 10.666264] type=1400 audit(1483292256.408:17): avc: denied { open } for pid=2775 comm="aaa" path="/dev/ddd" dev="tmpfs" ino=1287 scontext=u:r:aaa_security:s0 tcontext=u:object_r:aaa_device:s0 tclass=chr_file permissive=1
[ 10.666271] type=1400 audit(1483292256.408:18): avc: denied { ioctl } for pid=2775 comm="aaa" path="/dev/ddd" dev="tmpfs" ino=1287 ioctlcmd=0x10 scontext=u:r:aaa_security:s0 tcontext=u:object_r:aaa_device:s0 tclass=chr_file permissive=1
[ 10.679685] type=1400 audit(1483292256.408:18): avc: denied { ioctl } for pid=2775 comm="aaa" path="/dev/ddd" dev="tmpfs" ino=1287 ioctlcmd=0x10 scontext=u:r:aaa_security:s0 tcontext=u:object_r:aaa_device:s0 tclass=chr_file permissive=1
那么我們就應該在 .te
文件中添加上這些權限,然后刪除掉 permissive aaa_security
語句。以我的項目為例,添加上所需全部權限后,完整的 aaa_security.te
文件內容如下:
########################################
# sepolicy rules for aaa_security
########################################
type aaa_security, domain;
type aaa_security_exec, exec_type, vendor_file_type, file_type;
r_dir_file(aaa_security, rootfs)
r_dir_file(aaa_security, system_file)
r_dir_file(aaa_security, sysfs_type)
r_dir_file(aaa_security, aaa_device)
#permissive aaa_security;
init_daemon_domain(aaa_security)
allow aaa_security shell_exec:file { read getattr };
allow aaa_security vendor_toolbox_exec:file { read open execute execute_no_trans };
allow aaa_security system_data_file:file { getattr };
allow aaa_security system_data_file:dir { getattr };
allow aaa_security self:capability { dac_override sys_admin };
#allow aaa_security rootfs:filesystem { remount };
allow aaa_security rootfs:dir { write add_name create };
allow aaa_security system_file:file { execute_no_trans };
allow aaa_security aaa_device:chr_file { read write open ioctl };
allow aaa_security cache_file:dir { getattr };
allow aaa_security tee_data_file:dir { getattr };
allow aaa_security storage_file:dir { getattr };
allow aaa_security media_rw_data_file:dir { getattr };
allow dolby_security vendor_shell_exec:file { entrypoint };
allow dolby_security tmpfs:dir { write add_name create };
allow dolby_security dolby_security_tmpfs:file { create open };
至此,我們的工作就全部完成了。重新編譯並燒寫 boot.img 和 vendor.img 后,重啟設備,可以看到開機腳本工作正常。