1. 什么是SELinux:
作为 Android 安全模型的一部分,Android 使用安全增强型 Linux (SELinux) 对所有进程强制执行强制访问控制 (MAC),甚至包括以 Root/超级用户权限运行的进程(Linux 功能)。很多公司和组织都为 Android 的 SELinux 实现做出了贡献。借助 SELinux,Android 可以更好地保护和限制系统服务、控制对应用数据和系统日志的访问、降低恶意软件的影响,并保护用户免遭移动设备上的代码可能存在的缺陷的影响。
SELinux 按照默认拒绝的原则运行:任何未经明确允许的行为都会被拒绝。SELinux 可按两种全局模式运行:
宽容模式(Permissive mode):权限拒绝事件会被记录下来,但不会被强制执行。
强制模式(Enforcing mode):权限拒绝事件会被记录下来并强制执行。
Android 中包含 SELinux(处于强制模式)和默认适用于整个 AOSP 的相应安全政策。在强制模式下,非法操作会被阻止,并且尝试进行的所有违规行为都会被内核记录到 dmesg 和 logcat。开发时,您应该先利用这些错误信息对软件和 SELinux 政策进行优化,再对它们进行强制执行。
此外,SELinux 还支持基于域的宽容模式。在这种模式下,可将特定域(进程)设为宽容模式,同时使系统的其余部分处于全局强制模式。简单来说,域是安全政策中用于标识一个进程或一组进程的标签,安全政策会以相同的方式处理所有具有相同域标签的进程。借助基于域的宽容模式,可逐步将 SELinux 应用于系统中越来越多的部分,还可以为新服务制定政策(同时确保系统的其余部分处于强制模式)。
2. 设置SELinux模式:
对于受SELinux权限影响的功能验证,我们可以先将 SELinux 模式调整到 Permissive mode,然后再测试确认是否与 SELinux约束相关。
在userdebug版本中:
adb root
adb shell setenforce 0
adb shell getenforce
注意,重启后SELinux模式会恢复为enforcing,若有功能需要在启动时验证,则可以单独重启Android。
3. 版本演变
低于安卓4.3 - 默认不支持SELinux
安卓4.3 - 宽容模式
安卓4.4 - 部分强制模式(多数设备依然是宽容模式)
安卓5.1 - 默认强制模式
高于安卓8.0 - 强制模式且Sepolicy规则被分成多个部分
4. 关键文件
通常情况下,不能直接修改 system/sepolicy 文件,但可以添加或修改自己的设备专用政策文件(位于 /device/
要实现 SELinux,必须创建或修改以下文件:
1.新的 SELinux 政策源代码 (*.te) 文件 - 位于 /device/
请勿更改 Android 开放源代码项目提供的 app.te 文件,否则可能会破坏所有第三方应用。
2.file_contexts - 位于 sepolicy 子目录中。该文件用于为文件分配标签,并且可供多种用户空间组件使用。在创建新政策时,请创建或更新该文件,以便为文件分配新标签。
3.genfs_contexts - 位于 sepolicy 子目录中。该文件用于为不支持扩展属性的文件系统(例如,proc 或 vfat)分配标签。此配置会作为内核政策的一部分进行加载,但更改可能对核心内 inode 无效。要全面应用更改,需要重新启动设备,或卸载后重新装载文件系统。此外,通过使用 context=mount 选项,还可以为装载的特定系统文件(例如 vfat)分配特定标签。
4.property_contexts - 位于 sepolicy 子目录中。该文件用于为 Android 系统属性分配标签,以便控制哪些进程可以设置这些属性。在启动期间,init 进程会读取此配置。
5.service_contexts - 位于 sepolicy 子目录中。该文件用于为 Android Binder 服务分配标签,以便控制哪些进行可以为相应服务添加(注册)和查找(查询)Binder 引用。在启动期间,servicemanager 进程会读取此配置。
6.seapp_contexts - 位于 sepolicy 子目录中。该文件用于为应用进程和 /data/data 目录分配标签。在每次应用启动时,zygote 进程都会读取此配置;在启动期间,installd 会读取此配置。
7.mac_permissions.xml - 位于 sepolicy 子目录中。该文件用于根据应用签名和应用软件包名称(后者可选)为应用分配 seinfo 标记。然后,分配的 seinfo 标记可在 seapp_contexts 文件中用作密钥,以便为带有该 seinfo 标记的所有应用分配特定标签。在启动期间,system_server 会读取此配置。
5. 规范
根据SELinux规范,完整的SContext字符串为:
user:role:type[:range]
以 “/build.prop u:object_r:rootfs:s0”为例:
u:为user的意思,它代表创建这个文件的SELinux user
object_r:在SELinux中,文件类型都用object_r来表示它的role
rootfs:它表示该目录或文件对应的Type是rootfs,type会在te文件中定义
s0:SELinux的Multi-Level Security(MLS)机制
根据 SELinux 规范,完整的 SELinux 策略规则语句格式为:
allow domains types:classes permissions
domain:一个进程或一组进程的标签,也称为域类型
type:一个对象(文件,套接字等)或一组对象的标签
class:要访问的对象的类型
permission:要执行的操作,例如读写等
示例:
allow appdomain app_data_file:file rw_file_perms
表示所有appdomain域都可以读取和写入带有app_data_file标签的文件
6. 调试工具
一般使用 audit2allow 进行权限的调试,audit2allow 工具可以获取 dmesg 拒绝事件并将其转换成相应的 SELinux 政策声明。因此,该工具有助于大幅加快 SELinux 开发速度。但是该工具并不能完全调试 OK,还需要用户自行根据需要修改。
在 ubuntu 中安装 policycoreutils:
sudo apt-get install policycoreutils
提取设备内部的sepolicy规则文件:
adb pull /sys/fs/selinux/policy
用logcat抓取log后使用audit2allow工具解析:
adb logcat -b all -d | audit2allow -p policy
解析后会打印类似如下内容:
#============= xxread ==============
allow xxread shell_exec:file map;
也可以通过log去自行解析,通过过滤“avc”相关的信息,比如:
12-28 02:26:50.659 3058 3058 W init.app.hibern: type=1400 audit(0.0:66): avc: denied { map } for path="/system/bin/sh" dev="overlay" ino=20087 scontext=u:r:xxread:s0 tcontext=u:object_r:shell_exec:s0 tclass=file permissive=0
同样能得出上面那段内容,但是需要一定的解读能力。
然后基于此内容将对应的allow权限添加至对应的 .te 文件中即可(上面这段就要在xxread.te文件中添加)。
7. 编译验证
对于SELinux策略修改的验证,可以使用:
make selinux_policy
编译之后,会在相应的out目录下生成新的文件
system(\vendor\system_ext)/etc/selinux/
把新生成的文件push到机器对应的路径下替换即可
8. 问题案例
这个编译问题出在原本把新增的文件放在vendor目录下,由于文件中需要引用system下的可执行文件,所以根据audit2allow增加了访问system_file的allow规则,但是这样一来编译就会提示与系统默认定义的neverallow规则冲突了,我们是通过/vendor分区去执行/system分区命令,这是由于Google启动的Treble计划,为实现分区可独立升级,不允许进行跨分区调用。
由于log中提示“neverallow on line 1024 of system/sepolicy/public/domain.te”,想尝试在domain里面的那个neverallow中使用“-xxx”来去掉对该域的限制,后来编译发现又报错了:
提示意思是domain.te是不允许被修改的,编译时会与默认的api目录下的domain.te进行比对,一有差异就会中断,这点在第4小节中已经提到了,/system/sepolicy/ 目录下的规则是不允许修改的。
所以最后把此文件放在system目录下,然后使用system分区的te规则文件。
9. 思路总结
先确认问题是否与 Selinux 权限相关,关闭权限限制,先调试功能,再调试权限。
小问题可以使用 audit2allow 工具直接加。
Neverallow 问题添加缩小域范围,自定义新的权限域进行规避。
添加权限尽量不修改系统定义域,以免其它进程访问不到,引起功能问题。
多项目共基线修改,注意添加兼容问题。防止不同项目编译错误。
不到万不得已不修改 system/sepolicy/下的文件,防止 CTS 认证失败。