【Android】【底层原理】深入解析SELinux模块
SELinux模块不仅是一个安全功能,更是Android安全架构的基石。理解SELinux对于处理系统级问题、进行深度定制和应对安全挑战至关重要。
1. SELinux 概述:从“自由放任”到“强制管控”
什么是SELinux?
SELinux最初由美国国家安全局开发,是一种强制访问控制 系统。它内置于Linux内核中,用于补充传统的自主访问控制。
Android的安全演进
- Android 4.3及以前:宽容模式
- 传统的Linux权限模型(DAC)是主力。只要进程拥有正确的UID/GID,它就可以访问对应文件。
- 问题:如果一个应用被提升到
root权限,它就可以为所欲为,访问整个系统。root滥用是主要安全隐患。
- Android 4.4:引入SELinux,但处于宽容模式
- 开始引入SELinux策略,但默认设置为
permissive。在此模式下,SELinux会记录违规访问,但不会实际阻止。主要用于收集策略和调试。
- 开始引入SELinux策略,但默认设置为
- Android 5.0及以后:全面强制执行
- 默认设置为
enforcing模式。任何违反SELinux策略的操作都会被直接拒绝并记录。这标志着Android安全模型的质的飞跃。
- 默认设置为
2. 核心原理:主体、客体和策略
想象一下一个高度机密的政府大楼:
- 主体:大楼里的人员(例如:进程
/system/bin/surfaceflinger,com.android.phone)。 - 客体:被访问的资源(例如:文件
/dev/gpu/fb0, 套接字, 属性sys.powerctl)。 - 策略:一套极其详细的安保规则,规定了哪个身份的人,在什么角色下,可以访问哪个区域的哪个资源。
SELinux不是简单地看你的“工作证”(UID),而是看你所在的“安全许可级别”和“部门”,并严格规定你能做什么。
核心概念
-
标签
- 系统中的每个主体和客体都被打上了一个SELinux标签(也称为安全上下文)。
- 格式:
user:role:type:level(在Android中,最常用和最重要的是type)。 - 示例:
- 一个进程:
system_u:system_r:system_server_t:s0 - 一个文件:
system_u:object_r:system_file_t:s0 - 一个设备节点:
u:object_r:gpu_device_t:s0
- 一个进程:
-
策略
- 策略是一组预定义的规则,明确规定了
source_type能否对target_type执行class上的permission。 - 规则语法:
allow source_type target_type : class { permission_set }; - 实例解析:
# 允许 surfaceflinger 进程(source_type)对 framebuffer 设备(target_type)进行读写和打开操作 allow surfaceflinger gpu_device : chr_file { read write open };surfaceflinger:进程的type上下文。gpu_device:文件(设备节点)的type上下文。chr_file:客体类别,代表字符设备文件。{ read write open }:允许的操作权限集合。
- 策略是一组预定义的规则,明确规定了
-
域转换
- 一个进程如何从一个不受限制的域(如
init)转换到一个受限制的域(如system_server_t)。 - 通常通过可执行文件的标签和策略规则实现。当
init(PID 1)进程执行/system/bin/app_process时,由于这个二进制文件被打上了zygote_exec的标签,策略规则允许它自动切换到zygote域。
# init 进程可以执行被打上 zygote_exec 标签的文件 allow init zygote_exec : file execute; # 当执行 zygote_exec 文件时,进程可以从 init 域转换到 zygote 域 domain_auto_trans(init, zygote_exec, zygote) - 一个进程如何从一个不受限制的域(如
3. Android中的SELinux工作模式
模式
enforcing:强制模式。违反策略的操作被拒绝。这是生产环境的默认设置。permissive:宽容模式。违反策略的操作被允许,但会记录到日志中。用于开发和调试。disabled:完全禁用。不加载SELinux。
查看和设置模式
# 查看当前模式
getenforce
# 输出:Enforcing# 临时切换到宽容模式(需要root权限)
setenforce 0
# 临时切换回强制模式
setenforce 1# 查看内核启动参数中的设置(决定初始模式)
cat /proc/cmdline | grep androidboot.selinux
4. Android SELinux策略的架构
Android的策略不是单一文件,而是一个复杂的集合,主要位于/system/etc/selinux和/vendor/etc/selinux。
-
分割的策略
- 平台策略:AOSP通用策略,位于
/system/etc/selinux。 - 供应商策略:SoC厂商(如Qualcomm)提供的策略,位于
/vendor/etc/selinux。 - ODM策略:设备制造商(如Samsung, Xiaomi)提供的策略,位于
/odm/etc/selinux。 - 这种分割是Project Treble的要求,使得系统框架和硬件相关的策略可以独立更新。
- 平台策略:AOSP通用策略,位于
-
策略文件
*.te:类型强制文件,是策略的核心,包含了所有的allow规则。file_contexts:定义了文件系统上的文件、目录、设备节点的安全上下文。property_contexts:定义了Android系统属性(sys.,ctl.,persist.等)的安全上下文。service_contexts:定义了Binder服务的安全上下文。seapp_contexts:定义了应用进程和数据目录的安全上下文。mac_permissions.xml:与seapp_contexts配合,根据应用签名或包名为其分配SELinux上下文。
5. 实战:如何分析和解决SELinux问题
这是作为开发者和定制ROM维护者的核心技能。
步骤1:抓取日志
当遇到权限问题时,首先检查SELinux是否拒绝。
# 使用 logcat 并 grep avc 信息
adb logcat | grep -i "avc:"# 或者使用 dmesg
adb shell dmesg | grep -i "avc:"
一条典型的AVC拒绝日志如下:
[ 12.345678] type=1400 audit(0.0:123): avc: denied { read } for pid=1234 comm="my_daemon" name="my_device" dev="tmpfs" ino=5678 scontext=u:r:my_daemon_t:s0 tcontext=u:object_r:unlabeled:s0 tclass=chr_file permissive=0
步骤2:解析日志
让我们解剖这条日志:
avc: denied { read }:SELinux拒绝了读操作。pid=1234 comm="my_daemon":肇事者是进程my_daemon,PID为1234。scontext=u:r:my_daemon_t:s0:源上下文,即进程的安全标签。它是my_daemon_t域。tcontext=u:object_r:unlabeled:s0:目标上下文,即要访问的客体的安全标签。目前是unlabeled,说明这个设备节点还没有被正确标记。tclass=chr_file:目标类别,这是一个字符设备文件。
结论:策略不允许在my_daemon_t域中的进程读取标签为unlabeled的字符设备文件。
步骤3:制定解决方案
方案A(推荐,遵循最小权限原则):如果my_device是一个新的自定义设备节点。
- 定义新的Type:
在device/your_company/your_device/sepolicy/vendor/my_device.te中:# 定义 my_device 的类型 type my_device, dev_type; - 标记设备节点:
在device/.../sepolicy/vendor/file_contexts中:# /dev/my_device 被打上 my_device 的标签 /dev/my_device u:object_r:my_device_device:s0 - 编写允许规则:
在my_device.te文件中添加:# 允许 my_daemon_t 域对 my_device 设备进行必要的操作 allow my_daemon_t my_device_device:chr_file { open read write ioctl };
方案B(快速但危险,仅用于调试):临时放宽策略。
# 非常宽泛的规则:允许 my_daemon_t 域访问任何字符设备(危险!)
allow my_daemon_t dev_type:chr_file { open read write };
永远不要在产品中使用这种宽泛规则,它破坏了SELinux的安全边界。
步骤4:编译和验证
- 将修改的策略文件放入设备源码的相应位置。
- 重新编译
bootimage或vendorimage并刷入。 - 将设备切换到
permissive模式进行测试,确保新的策略能正常工作且没有新的拒绝日志。 - 最后,切换回
enforcing模式进行最终验证。
6. Neverallow规则:策略的“防火墙”
SELinux策略本身也受到保护。neverallow规则是策略的“元规则”,它禁止在策略中出现某些危险的授权。
示例:
# 绝不允许任何应用域(appdomain)直接访问 GPU 设备
neverallow appdomain gpu_device:chr_file { open read write };
如果有人(包括设备制造商)在.te文件中添加了违反neverallow的规则,在编译时就会报错,无法通过。这确保了策略基础的安全性底线。
总结:为什么SELinux对Android如此重要?
- 遏制漏洞:即使一个应用通过漏洞获得了
root权限,SELinux策略也会将其限制在其域内,防止其对系统其他部分进行横向攻击。 - 最小权限原则:每个进程只能拥有完成其本职工作所必需的最少权限,别无其他。
- 防御纵深:SELinux与Linux DAC、Capabilities、App Sandbox等共同构成了Android的纵深防御体系。
- 审计能力:详细的AVC日志为安全审计和问题排查提供了宝贵信息。
对于开发者而言,掌握SELinux意味着你能够:
- 深入理解Android系统的安全模型。
- 自信地进行系统级开发和定制。
- 快速定位和解决那些看似“神秘”的权限拒绝问题。
- 为你的设备或产品构建更安全、更健壮的软件。
