添加SELinux比较好的参考文本
为 Android 8.0 添加开机启动脚本
Qidi 2018.01.11 (Markdown & Haroopad)
【写在前面】
本人对于 SELinux for Android 理解不深,下文中的各文件及安全规则虽都是我所编写,但也是一边查阅文档一边试验得出的。在此强行为文,若有理解错误之处,请各位工程师同仁热情指出。
一、需求描述
项目上需要在 Android 8.0 上实现开机自动调用可执行文件 /system/bin/aaa 去处理 /vendor/lib/bbb.old,并将处理后的文件保存为 /temp/reserved/bbb.new。这期间 /system/bin/aaa 在处理文件时会去访问 /dev/ddd 节点,并且在开机时 /temp/reserved/ 目录并不存在,需要每次开机时创建。
其中 /system/bin/aaa 和 /dev/ddd 是自定义添加的可执行文件和设备节点,在原生 Android 8.0 系统中并不存在。
二、实现思路
1) 编写一个脚本实现调用 /system/bin/aaa 对文件 /vendor/lib/bbb.old 进行处理。
2) 在 .rc 文件中新增创建 /temp/reserved/ 目录的代码和新增一个 service 用于启动 步骤1 中编写的脚本。
3) 新增 .te 文件并编写相应 sepolicy 规则以解决权限问题。
三、实现过程
首先编写开机启动脚本。因为 Google 启动了 Treble 计划,对 system 分区和 vendor 分区进行严格的分离,所以这样的自定义脚本我们最好不要放在 /system/bin/ 目录下。因为我工作于芯片公司,即 vendor 厂商,所以我把它放在 /vendor/bin/ 目录下。同时,从 Android 8.0 开始,SELinux for Android 也进行了模块化,为了避免不必要的权限问题,我们使用 /vendor/bin/sh 作为脚本解释器。我编写的开机脚本是 auto_run.sh,内容很简单,如下:
#!/vendor/bin/sh
/system/bin/aaa /vendor/lib/bbb.old /temp/reserved/bbb.new
脚本应该在编译时自动拷贝到 vendor/bin/ 目录下,所以我们还要在 makefile 中添加拷贝命令。以我这个项目为例,对应的 makefile 文件是 product_something.mk,为其添加如下代码:
# aaa autorun
PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/../../auto_run.sh:/vendor/bin/auto_run.sh
然后在相应的 .rc 文件中添加代码。这是为了在开机时创建 /temp/reserved/ 目录,以及以 service 的形式运行脚本 auto_run.sh。以我这个项目为例,需要在 init.vendorName.board.rc 中添加如下语句:
on init
mkdir /tmp 0770 root root
mkdir /temp/reserved 0770 root root
mount tmpfs tmpfs /temp/reserved
on boot
#aaa autorun
chmod 0666 /dev/ddd
chmod 0777 /system/bin/aaa
chmod 0666 /vendor/lib/bbb.old
start aaa_autorun
chmod 0666 /temp/reserved/bbb.new
service aaa_autorun /vendor/bin/sh /vendor/bin/auto_run.sh
user root
group root
disabled
oneshot
seclabel u:r:aaa_autorun:s0
这几行命令的意思是在系统 init 阶段创建 /temp/reserved/ 目录,定义名为 aaa_autorun 的服务,并在 boot 阶段以 root 身份显式启动且仅启动服务一次。
接着我们还要为上一步骤里定义的 aaa_autorun 服务编写 .te 文件,定义其所属的安全域(SELinux domain)。否则设备在开机后无法执行这个 service,并且我们可以在 dmesg 中看到下方这样的警告:
[ 9.910511] init: service aaa_autorun does not have a SELinux domain defined
在 device/<vendorName>/common/sepolicy/ 目录下新建 aaa_autorun.te 文件。我们可能并不清楚开机运行这个脚本并执行相应的调用需要哪些权限,所以我们可以先在 .te 文件中添加上基本的定义。这些基本的文件内容如下:
########################################
# sepolicy rules for aaa_autorun
########################################
type aaa_autorun, domain;
type aaa_autorun_exec, exec_type, vendor_file_type, file_type;
permissive aaa_autorun;
init_daemon_domain(aaa_autorun)
其中 aaa_autorun 这个域对应 .rc 文件中定义的服务 aaa_autorun,aaa_autorun_exec 对应脚本文件 auto_run.sh。语句 permissive aaa_autorun 是暂时添加的,目的是使服务在遇到权限问题时也可以正常执行,但会将所需的权限类型打印出来。init_daemon_domain 是一个宏,用来使 aaa_autorun 域生效。
因为 /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/auto_run.sh u:object_r:aaa_autorun_exec:s0
/dev/aaa u:object_r:aaa_device:s0
为了验证我们的修改是否有效,需要编译并烧写 boot.img 和 vendor.img,然后重启设备。
不过最初编写的 aaa_autorun.te 文件中,往往赋予开机脚本的权限会有不足,我们需要通过查看 dmesg 中的打印来添加上相应的权限。比如我们可能会看到下面这样的权限缺失提示:
[ 10.368517] type=1400 audit(1483292256.112:14): avc: denied { execute_no_trans } for pid=2768 comm="auto_run.sh" path="/vendor/bin/toybox_vendor" dev="mmcblk0p14" ino=222 scontext=u:r:aaa_autorun: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="auto_run.sh" path="/vendor/bin/toybox_vendor" dev="mmcblk0p14" ino=222 scontext=u:r:aaa_autorun: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_autorun: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_autorun: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_autorun: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_autorun: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_autorun: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_autorun:s0 tcontext=u:object_r:aaa_device:s0 tclass=chr_file permissive=1
那么我们就应该在 .te 文件中添加上这些权限,然后删除掉 permissive aaa_autorun 语句。以我的项目为例,添加上所需全部权限后,完整的 aaa_autorun.te 文件内容如下:
########################################
# sepolicy rules for aaa_autorun
########################################
type aaa_autorun, domain;
type aaa_autorun_exec, exec_type, vendor_file_type, file_type;
r_dir_file(aaa_autorun, rootfs)
r_dir_file(aaa_autorun, system_file)
r_dir_file(aaa_autorun, sysfs_type)
r_dir_file(aaa_autorun, aaa_device)
#permissive aaa_autorun;
init_daemon_domain(aaa_autorun)
allow aaa_autorun shell_exec:file { read getattr };
allow aaa_autorun vendor_toolbox_exec:file { read open execute execute_no_trans };
allow aaa_autorun system_data_file:file { getattr };
allow aaa_autorun system_data_file:dir { getattr };
allow aaa_autorun self:capability { dac_override sys_admin };
#allow aaa_autorun rootfs:filesystem { remount };
allow aaa_autorun rootfs:dir { write add_name create };
allow aaa_autorun system_file:file { execute_no_trans };
allow aaa_autorun aaa_device:chr_file { read write open ioctl };
allow aaa_autorun cache_file:dir { getattr };
allow aaa_autorun tee_data_file:dir { getattr };
allow aaa_autorun storage_file:dir { getattr };
allow aaa_autorun media_rw_data_file:dir { getattr };
allow aaa_autorun vendor_shell_exec:file { entrypoint };
allow aaa_autorun tmpfs:dir { write add_name create };
allow aaa_autorun aaa_autorun_tmpfs:file { create open };
至此,我们的工作就全部完成了。重新编译并烧写 boot.img 和 vendor.img 后,重启设备,可以看到开机脚本工作正常。