3. Android SELinux架构

Android 从 4.3 开始引入 SELinux 的宽容模式,到 5.0 全面进入强制模式,再到 8.0 随 Treble 架构把策略拆分为平台与厂商两大部分,SELinux 已经从"附加安全特性"演变为 Android 安全模型的基石。理解 Android 中 SELinux 的架构,核心是理解三件事:策略在运行时如何被加载与生效、策略文件在系统分区上如何被切分、以及平台与厂商策略之间如何通过接口保持兼容。

一、Android 中 SELinux 的实现机制

Android 的 SELinux 实现由内核态与用户态两部分协同完成。内核态是标准的 Linux SELinux LSM(Linux Security Modules)子系统,负责在系统调用层面执行访问判定;用户态则由 init、libselinux、selinux_check_access 以及 setenforce 等组件构成,负责策略加载、模式切换和标签管理。

系统启动时,init 在挂载 selinuxfs 后,从 /system/etc/selinux 与 /vendor/etc/selinux 读取策略文件,通过 /sys/fs/selinux/load 写入内核。策略加载成功后,内核开始对进程、文件、socket、属性等对象执行强制访问控制。Android 还引入了 selinux_android_restorecon 机制,在文件创建或系统升级后自动恢复正确的安全上下文。

# 查看当前 SELinux 模式
adb shell getenforce
# Enforcing

# 查看进程上下文
adb shell ps -Z | grep system_server
# u:r:system_server:s0  system  1234  ... system_server

# 查看文件上下文
adb shell ls -Z /system/bin/init
# u:object_r:init_exec:s0  /system/bin/init

Android 的 SELinux 还引入了 neverallow 规则,它在编译期而非运行期生效,用于禁止策略中出现某些危险授权。这是 Android 策略开发中最重要的约束之一:即使某条 allow 规则在运行期能通过,只要它违反了 neverallow,编译就会失败。

二、策略分区:system、vendor、product、odm

Android 的策略文件并非集中在一处,而是按分区切分。每个分区都有自己的 etc/selinux 目录,存放该分区所属组件的类型定义、上下文和规则。这种切分与 Android 的分区模型一一对应,目的是让不同供应商、不同产品线可以独立维护自己的策略,而不互相污染。

分区策略路径典型内容维护方
system/system/etc/selinux平台核心域、属性、公共类型AOSP
vendor/vendor/etc/selinuxHAL、驱动、厂商守护进程SoC/ODM
product/product/etc/selinux产品定制应用与服务OEM
odm/odm/etc/selinuxODM 专属硬件适配ODM
system_ext/system_ext/etc/selinux系统扩展模块AOSP/OEM

运行时,init 会按顺序加载这些分区的策略,最终合并成一份完整策略写入内核。因此,策略分区不是"多份独立策略",而是"一份策略的多个来源"。这也意味着跨分区的类型引用必须通过公共接口进行,否则会出现编译期未定义或运行期拒绝。

三、平台策略与厂商策略的边界

平台策略(platform policy)由 AOSP 维护,定义 system 分区中所有进程、文件、服务的类型与规则。厂商策略(vendor policy)由 SoC 厂商和 ODM 维护,定义 HAL、内核驱动、厂商守护进程等。两者之间通过 public、private、vendor_public 等属性进行接口约束。

关键规则是:平台策略不能直接 allow 厂商自定义类型,厂商策略也不能随意 allow 平台私有类型。双方只能通过 public 类型和 vendor_public 属性进行交互。这种设计保证了平台升级时,厂商策略不会因为平台内部类型变化而失效。

核心原则:平台与厂商之间只通过"公共接口"通信。平台侧用 public 标记可被厂商引用的类型,厂商侧用 vendor_public 标记可被平台引用的类型。任何跨越这条边界的直接引用,都会在 Treble 兼容性检查中被拦截。

四、Treble 与策略兼容性

Treble 的核心目标是让 system 分区可以独立于 vendor 分区升级。对 SELinux 而言,这意味着平台策略与厂商策略必须保持 ABI 级别的兼容:平台升级后,旧厂商策略仍能加载并正常工作;厂商升级后,平台策略也不需要同步修改。

为实现这一点,Android 引入了 vendor_public 属性、compatibility 目录以及 checkpolicy 的兼容性检查。编译时,sepolicy 构建系统会验证厂商策略是否只引用了平台公开的类型,以及平台策略是否只引用了厂商公开的类型。运行期,init 会检查策略版本与 ro.build.version.selinux 是否匹配。

# 查看策略版本
adb shell getprop ro.build.version.selinux
# 30.0

# 查看厂商策略中引用的平台公共类型
grep -r "type system_server" vendor/sepolicy/
# 若出现未在 public 中声明的类型,编译会报 neverallow 或 undefined

常见的兼容性问题包括:厂商策略引用了平台私有类型、平台升级后删除了某个 public 类型、厂商策略中使用了未声明的属性。这些问题通常在编译期暴露,但若厂商策略以预编译二进制形式提供,则可能在运行期才表现为 avc: denied。

五、典型场景与排障思路

假设某设备升级到新 Android 版本后,厂商 HAL 服务无法启动,日志中出现大量 avc: denied。排障时应先确认策略是否加载成功,再确认拒绝的具体类型与权限。

# 查看最近拒绝日志
adb shell dmesg | grep avc
# avc: denied { read } for pid=1234 comm="vendor_hal"
#   scontext=u:r:vendor_hal:s0 tcontext=u:object_r:sysfs:s0 tclass=file

# 查看当前策略中该类型的定义
adb shell sesearch --allow -s vendor_hal -t sysfs

若拒绝发生在平台与厂商边界上,应优先检查 vendor_public 属性是否已声明,以及平台侧是否将该类型标记为 public。若拒绝发生在厂商内部,则检查厂商策略是否完整加载。切忌直接使用 setenforce 0 绕过问题,这会掩盖真正的策略缺陷。

常见误区:把 neverallow 编译错误当成"编译器太严格"而随意删除规则。实际上 neverallow 是 Android 安全模型的红线,删除它等于放弃对危险授权的防护。正确做法是调整策略设计,而不是绕过检查。

六、架构总览

下图展示了 Android SELinux 从策略文件到内核执行的完整链路,以及平台策略与厂商策略在 Treble 边界上的交互关系。

Android SELinux 策略加载与 Treble 兼容架构 策略文件层(按分区切分) system 平台核心策略 vendor HAL / 驱动策略 product 产品定制策略 odm ODM 适配策略 system_ext 系统扩展策略 init / libselinux 合并策略 → 写入 /sys/fs/selinux/load Linux Kernel SELinux LSM 强制访问控制执行 · avc 判定 · 审计日志 Treble 兼容边界 平台侧 public 类型 ←→ 厂商侧 vendor_public 属性 编译期 neverallow 检查 · 运行期策略版本校验 兼容性检查 checkpolicy / sepolicy

从图中可以看出,Android SELinux 的策略加载是一条自下而上的链路:分区策略文件由 init 合并后写入内核,内核 LSM 负责执行判定。而 Treble 兼容边界则横向贯穿平台与厂商两侧,通过 public 与 vendor_public 约束双方的交互范围。理解这条链路和这条边界,是进行 Android SELinux 策略开发与调试的基础。