Android SELinux
第一部分:核心理论与基础概念
1.1 为什么需要 SELinux?从 DAC 到 MAC
在深入 SELinux 的细节之前,我们必须先回答一个根本性的问题:Android(以及Linux)系统原本就有权限控制,为什么还需要引入 SELinux 这一套复杂的安全机制?
要理解这一点,我们必须对比两种根本不同的访问控制模型:DAC 和 MAC。
1.1.1 DAC(自主访问控制)的舒适区与致命缺陷
1. 什么是 DAC?
DAC 是 Discretionary Access Control 的缩写,意为"自主访问控制"。它是传统 Linux/Unix 系统(以及早期 Android 版本)默认的安全模型。
在 DAC 模型中,一个文件或资源的访问权限由文件的所有者(User) 自主决定。系统通过经典的 rwx(读、写、执行)权限位来控制访问。
- 判断依据 :进程的 UID (用户ID)和 GID(组ID)。
- 管理方式 :用户或管理员可以通过
chmod和chown命令自由地修改文件权限。
2. DAC 模型的权限检查流程
下面这张时序图清晰地描绘了当一个 root 进程尝试访问一个设备文件时,DAC 是如何"放行"的。

3. 核心风险:"Root is God"
DAC 模型最大的设计缺陷在于:它无法约束超级用户(root,UID=0)。
在 DAC 的规则下:
- 对于非 root 进程 :如果一个进程的 UID 不是 0,它想访问一个文件,系统会严格检查权限位。比如文件属主是
root,权限是rw-------,那么这个非 root 进程就会被无情地拒绝。 - 对于 root 进程 :系统检查到
UID == 0,无论文件的权限位是什么(哪怕是000),系统都会无条件放行,赋予最高权限。
为什么这是致命的?
在现代操作系统尤其是 Android 系统中,攻击者的终极目标通常是"提权"(Privilege Escalation)。他们利用系统服务或内核的某个漏洞,让一个普通的、受限的应用进程(如浏览器或聊天软件)获得 root 身份执行代码的能力。
一旦攻击者拿到了一个 root 权限的 shell 或进程,在纯 DAC 环境下,他可以:
- 读取所有用户的私人数据(联系人、短信、照片)。
- 修改系统核心文件,植入后门。
- 直接读写硬件设备(如摄像头、麦克风)。
Example1 :在早期 Android 版本中,如果 App 无法打开 /dev/xxx 设备,开发者往往会直接在 init.rc 或脚本里写一句 chmod 777 /dev/xxx。在 DAC 体系下,这确实解决了"权限不够"的问题,但同时也把设备的大门向所有恶意软件敞开了。
1.1.2 MAC(强制访问控制)与 SELinux 的应对之道
为了解决 DAC "Root 特权过大,无法细粒度约束" 的问题,NSA(美国国家安全局)主导开发了 SELinux(Security-Enhanced Linux),并将其贡献给开源社区。Android 从 4.3 版本开始,正式引入了 SEAndroid(SELinux in Android)。
1. 什么是 MAC?
MAC 是 Mandatory Access Control 的缩写,意为"强制访问控制"。
2. 核心思想:最小权限原则
SELinux 设计的灵魂是 最小权限原则。
每个进程(主体)只被授予完成其任务所必需的最小权限,不多也不少。
这意味着,即使一个进程是 root 身份运行的,SELinux 也会在旁边冷冷地加上一句:"等等,先让我看看你的策略文件(Policy)里有没有这一条规则。"
3. 生动的比喻:公司门禁系统
结合《11 SELinux理论基础》文档中的比喻,我们可以这样理解:
- DAC 模型(旧门禁) :相当于公司只有一个大门。你只要有一张"CEO 金卡"(Root 权限),你就可以在整栋大楼里畅通无阻。你可以进财务室拿钱,进机房拔网线,进 HR 办公室看所有人的工资单。只要你是 CEO,没人拦你。
- MAC 模型(SELinux 新门禁) :相当于公司在每个办公室门口都安装了独立的门禁读卡器 。
- CEO 金卡(Root 权限)现在只是你进大门的凭证。
- 当你走到研发部 门口时,门禁系统会读取你的工牌信息,然后查询后台的规章制度数据库(SELinux Policy)。
- 如果数据库里没有写"CEO 可以进入研发部代码服务器机房"这一条,就算你是 CEO,门也打不开。
4. MAC 如何对抗 Root 攻击?
假设一个恶意软件利用漏洞成功以 root 身份运行了一个进程,企图读取 /data/data/com.android.providers.contacts/databases/contacts2.db(联系人数据库)。
- 在纯 DAC 下 :进程是
root,文件属主也是root→ 放行。 - 在 SELinux (MAC) 下 :
- 恶意进程的 SELinux 上下文是
u:r:untrusted_app:s0(假设它是一个普通应用域)。 - 联系人数据库文件的上下文是
u:object_r:contacts_data_file:s0。 - SELinux 策略数据库中并没有
allow untrusted_app contacts_data_file:file read;这条规则。 - 结果:即使进程有
root身份,SELinux 安全模块(LSM)依然会拦截请求,返回权限拒绝(EACCES)。
- 恶意进程的 SELinux 上下文是
1.2 SELinux 访问控制模型详解
在理解了"为什么需要 MAC"之后,我们需要深入 SELinux 的内部运行机制,看看它是如何在每一次操作发生时介入并做出判断的。
1.2.1 两大核心组件:主体与客体
在 SELinux 的语言体系中,所有与安全相关的操作都可以归结为 主体 (Subject) 对 客体 (Object) 的访问。
| 组件 | 定义 | 在 Android 中的典型实例 |
|---|---|---|
| 主体 (Subject) | 访问操作的主动发起方。在操作系统中,主体几乎总是指进程。 | init 进程、adbd 守护进程、system_server、任何第三方 App 的进程。 |
| 客体 (Object) | 被访问的资源。它既可以是一个具体的文件,也可以是一个抽象的服务或通信通道。 | 普通文件、目录、设备节点 (/dev/*)、Socket、Binder 服务、系统属性 (ro.*)、进程间通信对象等。 |
思考:为什么需要区分主体和客体?
因为在 SELinux 的策略语言 (TE) 中,我们需要精确地描述:"哪一个"主体,对"哪一类"客体,拥有"哪些"操作许可。 如果不做区分,策略语法将无法描述。
1.2.2 MAC 权限检查的完整流程
让我们结合下面的时序图,拆解当一个进程试图访问一个文件时,系统内部发生了什么。

流程分步解析:
- 发起请求 :主体(一个运行在
init域的进程)通过系统调用(如open)请求访问客体(一个类型为device的字符设备文件)。 - 拦截并查询 :系统调用穿过 Linux 内核的 VFS 层,最终触发了 LSM (Linux Security Modules) 的钩子函数。LSM 调用 SELinux 安全服务器 (Security Server) 进行权限裁决。
- 策略匹配 :SELinux 安全服务器拿着本次访问的全部上下文信息:
scontext=init、tcontext=device、class=chr_file、perm=write,去访问缓存或加载在内核中的 策略数据库 (Policy)。 - 命中规则 :策略数据库检查是否存在一条或多条 TE 规则 (Type Enforcement) ,例如
allow init device:chr_file write;。 - 返回裁决 :策略数据库返回
allow(允许)或deny(拒绝)给安全服务器。 - 执行或拒绝 :
- 若允许,LSM 放行系统调用,进程成功获得文件描述符,读写操作正常执行。
- 若拒绝,LSM 拦截系统调用,直接向进程返回
EACCES(Permission Denied) 错误。同时,内核会生成一条 AVC (Access Vector Cache) 日志,记录下这次失败的访问尝试。这条日志是开发者调试 SELinux 问题的核心线索。
1.2.3 关键知识点补充:AVC 日志
从上图可以看到,每当发生"权限拒绝"时,系统都会记录日志。在 Android 中,我们通常通过 logcat 来捕获它。
bash
logcat | grep "avc"
一条典型的 AVC 日志如下:
text
avc: denied { read write } for pid=1234 comm="myprocess" name="mydevice" dev="tmpfs" scontext=u:r:myprocess_dt:s0 tcontext=u:object_r:mydevice_t:s0 tclass=chr_file permissive=0
denied { read write }: 被拒绝的操作。scontext: 主体上下文。tcontext: 客体上下文。tclass: 客体类别。permissive=0/1: 当前是强制模式(0)还是宽容模式(1)。
1.3 深入理解 SELinux Context (安全上下文)
如果说"主体"和"客体"是舞台上的演员,那么 SELinux Context (安全上下文) 就是贴在他们身上的唯一身份标签。在 SELinux 的世界里,一切皆标签。
1.3.1 标签的定义与标准格式
SELinux 上下文是一个由冒号分隔的字符串,标准格式为:
text
user:role:type:mls_level
在 Android 的 SELinux 实现中,这个格式被进一步简化并赋予了特定的含义:
| 字段 | 全称 | 在 Android 中的实际作用 |
|---|---|---|
| user | SELinux User | 在 Android 中,该字段几乎总是 u (代表 unconfined 或通用用户)。Android 不依赖于 SELinux 的用户划分来做权限隔离(而是依靠 Linux UID)。 |
| role | Role-Based Access Control | 在 Android 中,该字段主要用于区分上下文类别: - r :用于进程(主体) 的角色。 - object_r :用于文件/资源(客体) 的角色。 |
| type | Type Enforcement (TE) | 这是 SELinux 安全策略中唯一真正核心的字段。 所有的 allow 规则都是基于 type 来编写的。 |
| mls_level | Multi-Level Security | 在 Android 中,该字段被固定为 s0,代表最低敏感级别。Android 没有启用复杂的多级安全分类。 |
因此,在 Android 的日常开发和调试中,你可以将 SELinux 上下文简单地理解为:"它的类型 (Type) 是什么"。
1.3.2 主体上下文 vs. 客体上下文:一眼识别
通过 role 字段的不同,我们可以非常轻松地判断一个上下文标签是贴在一个进程(主体)上,还是贴在一个文件(客体)上。
- 主体 (进程) 的上下文 :
u:r:进程类型:s0- 特点:
role字段是r。 - 示例:
u:r:init:s0(init 进程)u:r:adbd:s0(ADB 守护进程)u:r:system_server:s0(Android 系统服务大管家)u:r:untrusted_app:s0(普通第三方应用进程)
- 特点:
- 客体 (文件/资源) 的上下文 :
u:object_r:文件类型:s0- 特点:
role字段是object_r。 - 示例:
u:object_r:system_file:s0(大多数/system分区下的文件)u:object_r:vendor_file:s0(/vendor分区下的文件)u:object_r:device:s0(通用的设备节点类型)u:object_r:app_data_file:s0(应用私有数据目录下的文件)
- 特点:
1.3.3 实战命令:如何查看 Android 系统中的上下文
理论结合实践,以下是在 Android 设备上查看各种对象安全上下文的常用命令。
1. 查看进程的安全上下文
使用 ps -AZ 命令。-Z 选项会显示 SELinux 上下文列。
bash
# 进入 adb shell
adb shell
# 查看所有进程的上下文
ps -AZ
# 过滤查看特定进程,例如 system_server
ps -AZ | grep system_server
输出示例解读:
bash
emulator_x86_64:/ # ps -AZ | grep system_server
u:r:system_server:s0 system 486 289 15212636 302348 do_epoll_wait 0 S system_server
- 标签
u:r:system_server:s0确认了system_server进程运行在名为system_server的 SELinux 域中。
2. 查看文件的扩展属性(安全上下文)
使用 ls -Z 命令。它会显示文件的 SELinux 上下文。
bash
# 查看 /vendor/bin/ 目录下的文件及其标签
ls -laZ /vendor/bin/
# 查看某个特定设备节点的标签
ls -lZ /dev/input/event0
输出示例解读:
bash
emulator_x86_64:/ # ls -lZ /dev/input/event0
crw-rw---- 1 root input u:object_r:input_device:s0 13, 64 2026-04-20 01:45 /dev/input/event0
- 标签
u:object_r:input_device:s0表明该文件在文件系统上被打上了input_device这个类型标签。
3. 查看系统属性的安全上下文
使用 getprop -Z 命令。
bash
# 查看属性的上下文
getprop -Z ro.build.type
输出示例解读:
bash
emulator_x86_64:/ # getprop -Z ro.build.type
u:object_r:build_prop:s0
- 这表示
ro.build.type这个属性的类型是build_prop。
通过熟练掌握这些查看命令,开发者可以快速验证:
- 我的进程是否运行在了我期望的域中?
- 我创建的文件是否被系统正确打上了标签?
- 如果标签不对,问题可能出在
file_contexts的定义或restorecon的执行上。
1.4 安全策略文件的定义与存储
在前面的章节中,我们反复提到了一个关键概念:"打标签"。无论是进程(主体)还是文件(客体),都必须拥有一个正确的 SELinux 安全上下文,SELinux 策略才能发挥作用。
那么问题来了:系统是如何知道哪个文件应该打上哪个标签的?这个"标签映射表"保存在哪里?
答案就在一系列的 *_contexts 文件中。这些文件是 SELinux 策略的 "静态映射字典",它们定义了从具体的资源(文件路径、属性名、服务名)到 SELinux 类型的对应关系。
在 Android 的 AOSP 源码中,这些文件通常位于 system/sepolicy/ 目录及其子目录下。
| 文件名 | 作用描述 |
|---|---|
file_contexts |
为普通文件、目录、设备节点等定义默认安全上下文。 |
genfs_contexts |
为 proc、sysfs、tmpfs 等虚拟或内存文件系统定义上下文。 |
property_contexts |
为 Android 系统属性(ro., persist. 等)定义上下文。 |
service_contexts |
为 Android Binder Service 定义上下文。 |
seapp_contexts |
为 APK 应用进程定义上下文(基于签名、UID 等)。 |
1.4.1 file_contexts:普通文件与目录的标签字典
作用 :为 Android 文件系统上的所有普通文件、目录、设备节点指定默认的 SELinux 安全上下文。
格式:
text
[正则表达式路径] [安全上下文]
示例 (节选自 system/sepolicy/private/file_contexts):
text
/system_dlkm(/.*)? u:object_r:system_dlkm_file:s0
/dev/adf[0-9]* u:object_r:graphics_device:s0
/data/vendor(/.*)? u:object_r:vendor_data_file:s0
工作机制:
- 在系统编译时,
file_contexts文件会被编译进plat_file_contexts或vendor_file_contexts中。 - 在系统启动或调用
restorecon命令时,init进程或restorecon工具会读取这些编译后的文件,根据路径正则匹配,为文件或目录设置xattr(扩展属性)中的 SELinux 标签。
查看系统中生效的 file_contexts:
bash
adb shell cat /system/etc/selinux/plat_file_contexts
adb shell cat /vendor/etc/selinux/vendor_file_contexts
1.4.2 genfs_contexts:虚拟文件系统的标签字典
作用 :为那些不存在于物理磁盘 上的文件系统(如 /proc、/sys、/dev、tmpfs)中的文件定义安全上下文。
为什么需要单独的 genfs_contexts?
因为 /proc 和 /sys 下的文件是内核动态生成的,它们没有持久化的 xattr 存储空间。因此,SELinux 必须通过一个内存中的映射表来为这些文件在生成时"即时打标签"。
格式:
text
genfscon [文件系统类型] [路径] [安全上下文]
示例 (节选自 system/sepolicy/private/genfs_contexts):
text
genfscon proc / u:object_r:proc:s0
genfscon sysfs / u:object_r:sysfs:s0
genfscon debugfs /tracing/instances u:object_r:debugfs_tracing_instances:s0
genfscon binder /binder u:object_r:binder_device:s0
工作机制:
- 内核在挂载这些虚拟文件系统时,SELinux 模块会查询
genfscon规则,并在每个文件/目录被创建时,自动赋予其预定义的上下文。
1.4.3 property_contexts:系统属性的标签字典
作用 :为 Android 的系统属性(System Properties)指定安全上下文。
背景 :Android 系统属性(通过 getprop/setprop 访问)是一个全局共享的键值对存储。某些敏感属性(如 ro.boot.selinux)只能被特定进程修改或读取。SELinux 通过为属性打标签,并在 TE 策略中限制对该标签的访问来实现细粒度控制。
格式:
text
[属性名前缀] [安全上下文]
示例 (节选自 system/sepolicy/private/property_contexts):
text
ro.ril. u:object_r:radio_prop:s0
sys. u:object_r:system_prop:s0
dhcp. u:object_r:dhcp_prop:s0
debug. u:object_r:debug_prop:s0
查看属性标签:
bash
adb shell getprop -Z ro.build.type
# 输出: u:object_r:build_prop:s0
1.4.4 service_contexts:Binder 服务的标签字典
作用 :为 Android 系统中注册的 Binder 服务 指定安全上下文。
背景 :在 Android 中,几乎所有的系统服务(如 activity、package、power)都是通过 Binder 机制提供的。当一个客户端进程想要调用某个服务时,SELinux 需要知道该服务的 SELinux 类型,以便检查 binder_call 权限。
格式:
text
[服务名] [安全上下文]
示例 (节选自 system/sepolicy/private/service_contexts):
text
activity u:object_r:activity_service:s0
package u:object_r:package_service:s0
power u:object_r:power_service:s0
vold u:object_r:vold_service:s0
工作机制:
- 当服务通过
ServiceManager.addService()注册时,SELinux 会根据service_contexts为其绑定一个上下文。 - 当客户端通过
ServiceManager.getService()获取服务并调用时,SELinux 会使用这个上下文来进行权限检查。
1.4.5 seapp_contexts:应用进程的标签字典
作用 :为 Android 应用程序(APK)的进程 动态分配 SELinux 域。
背景 :与 C/C++ 编写的守护进程不同,APK 应用并没有一个固定的可执行文件来让我们提前打标签。应用进程的 SELinux 域是在应用启动时,由 zygote 进程根据 应用的签名、UID、是否预装、是否特权 等多个维度动态计算出来的。
格式(逻辑描述):
text
isSystemServer=true domain=system_server
user=system seinfo=platform domain=platform_app
user=_app isPrivApp=true domain=priv_app
user=_app domain=untrusted_app
实际文件示例 (节选自 system/sepolicy/private/seapp_contexts):
text
# Input selectors:
# isSystemServer (boolean)
# isEphemeralApp (boolean)
# user (string)
# seinfo (string)
# name (string)
# isPrivApp (boolean)
# minTargetSdkVersion (unsigned integer)
# fromRunAs (boolean)
# isIsolatedComputeApp (boolean)
# isSdkSandboxNext (boolean)
... ...
#
# Outputs:
# domain (string)
# type (string)
# levelFrom (string; one of none, all, app, or user)
# level (string)
user=_app seinfo=platform name=com.android.traceur domain=traceur_app type=app_data_file levelFrom=all
user=system seinfo=platform domain=system_app type=system_app_data_file
user=system seinfo=platform isPrivApp=true name=com.android.DeviceAsWebcam domain=device_as_webcam type=system_app_data_file levelFrom=all
user=bluetooth seinfo=bluetooth domain=bluetooth type=bluetooth_data_file
工作机制:
Zygote在 fork 出新的应用进程后,会收集该应用的各种元数据(UID、签名信息等)。Zygote将这些信息传递给libselinux库。libselinux遍历seapp_contexts中的规则,从上到下进行匹配,第一个匹配成功的规则决定了新进程的 SELinux 域 (domain) 和该应用私有数据目录的 类型 (type)。
1.4.6 总结:五张"地图"协同工作
这五类 *_contexts 文件共同构成了 SELinux 的 "命名服务" 。它们将具体的、开发者容易理解的名字(如路径 /dev/device、属性名 persist.vendor.camera、服务名 vold)映射为 SELinux 策略引擎能够理解和执行的 安全类型 (Type)。

第二部分:TE 策略文件编写与语法实战
SELinux 的强大之处在于其灵活而精确的策略描述能力。所有的权限授予与拒绝,都通过一种叫做 TE(Type Enforcement,类型强制) 的语言来编写。TE 策略文件(通常以 .te 为后缀)是开发者与 SELinux 安全服务器对话的"合同条款"。
2.1 核心语法:allow 规则
allow 语句是 TE 策略中最基本、使用频率最高的语句。它的作用非常直接:授予权限。
2.1.1 基本格式
text
allow <主体类型> <客体类型>:<客体类别> <许可权限集>;
让我们逐一拆解这四个核心组成部分:
| 组成部分 | 含义 | 说明 |
|---|---|---|
| 主体类型 | Source Type (scontext) |
发起操作的进程所属的 SELinux 类型(域) 。例如 vold、init、system_server。 |
| 客体类型 | Target Type (tcontext) |
被访问的资源所属的 SELinux 类型 。例如 file_contexts_file、block_device、system_data_file。 |
| 客体类别 | Object Class | 被访问资源的类别。SELinux 需要知道这是一个普通文件、目录、Socket 还是字符设备,因为不同类别支持的操作是不同的。 |
| 许可权限集 | Permission Set | 一个或多个被允许的操作名称,用空格分隔并包裹在花括号 { } 中,或者使用预定义的宏。 |
2.1.2 深入理解客体类别 (Object Class)
SELinux 对资源的抽象非常细致。你不能简单地对一个"文件"授权,你必须明确告诉内核:这个客体是 file(普通文件)、dir(目录)、chr_file(字符设备)还是 blk_file(块设备)。
在 Android 策略中,最常见的客体类别包括:
| 类别 (Class) | 描述 | 典型权限 (Permissions) |
|---|---|---|
file |
普通文件 | read, write, open, getattr, setattr, execute, link, unlink |
dir |
目录 | read, write, open, getattr, search, add_name, remove_name, rmdir |
chr_file |
字符设备文件 | read, write, open, getattr, ioctl |
blk_file |
块设备文件 | read, write, open, getattr, ioctl |
lnk_file |
符号链接文件 | read, getattr |
sock_file |
UNIX Domain Socket 文件 | read, write, open, getattr |
tcp_socket / udp_socket |
网络 Socket | create, bind, connect, listen, accept, read, write |
process |
进程本身 | fork, sigkill, signal, setsched, transition |
binder |
Android Binder IPC | call, transfer, impersonate |
property |
系统属性 | read, write, set |
思考 :为什么同样都是"写入",
dir的写入和file的写入不同?对于
dir,"写入"意味着在该目录下创建或删除文件 (需要add_name、remove_name权限),而file的"写入"意味着修改文件内容(需要write权限)。SELinux 将这两种操作严格区分,以实现更细粒度的控制。
2.1.3 示例解析(来自 vold.te)
让我们以 Android 存储管理守护进程 vold 的真实策略为例,逐行解析。
示例 1:允许 vold 读取 file_contexts 文件
selinux
# 允许 vold 进程读取 file_contexts 文件
allow vold file_contexts_file:file r_file_perms;
| 组成要素 | 实际值 | 详细说明 |
|---|---|---|
| 主体类型 | vold |
指运行在 vold 域中的进程。 |
| 客体类型 | file_contexts_file |
指 SELinux 文件上下文配置文件(如 /system/etc/selinux/plat_file_contexts)的类型。 |
| 客体类别 | file |
指明 file_contexts_file 是一个普通文件。 |
| 许可权限集 | r_file_perms |
这是一个预定义宏,代表一组只读权限。 |
宏展开:r_file_perms 的背后
在 system/sepolicy/public/te_macros 文件中,r_file_perms 被定义为:
selinux
define(`r_file_perms', `{ getattr open read ioctl lock map watch watch_reads }'
因此,上述 allow 语句实际上等价于:
selinux
allow vold file_contexts_file:file { getattr open read ioctl lock map watch watch_reads };
这意味着 vold 进程可以打开、获取属性、读取、执行 ioctl 以及锁定 file_contexts_file 类型的文件。
示例 2:允许 vold 在 block_device 目录中创建子目录
selinux
# 允许 vold 进程对 block_device 目录拥有创建子目录的权限
allow vold block_device:dir create_dir_perms;
| 组成要素 | 实际值 | 详细说明 |
|---|---|---|
| 主体类型 | vold |
同上。 |
| 客体类型 | block_device |
这是一个属性(Attribute),代表所有块设备相关的目录。 |
| 客体类别 | dir |
指明操作的对象是目录。 |
| 许可权限集 | create_dir_perms |
另一个预定义宏,代表在目录下创建子项所需的权限。 |
宏展开:create_dir_perms 的背后
在 system/sepolicy/public/global_macros 中:
selinux
define(`create_file_perms', `{ create rename setattr unlink rw_file_perms }')
展开后:
selinux
allow vold block_device:dir { create rename setattr unlink rw_file_perms };
-
create:在目录中创建子文件/子目录。 -
rename:重命名目录内的子项 -
setattr:修改目录属性。 -
rw_file_perms包括r_dir_perms和w_dir_perms:selinuxdefine(`r_dir_perms', `{ open getattr read search ioctl lock watch watch_reads }') define(`w_dir_perms', `{ open search write add_name remove_name lock }')
2.1.4 常用预定义宏速查表
为了方便开发者,AOSP 在 te_macros 中定义了大量常用的权限组合宏。掌握这些宏可以大幅提升策略编写的效率和可读性。
| 宏名称 | 展开后的权限集 | 典型用途 |
|---|---|---|
r_file_perms |
{ getattr open read ioctl lock map watch watch_reads } |
只读普通文件。 |
w_file_perms |
{ open append write lock map } |
只写(或追加)普通文件。 |
rw_file_perms |
{ r_file_perms w_file_perms } |
读写普通文件。 |
x_file_perms |
{ getattr execute execute_no_trans map } |
不改变自身的安全上下文执行一个可执行文件 |
rx_file_perms |
{ r_file_perms x_file_perms } |
读取并执行普通文件(如 .so 库)。 |
create_file_perms |
{ create rename setattr unlink rw_file_perms } |
创建并写入普通文件。 |
create_dir_perms |
{ create reparent rename rmdir setattr rw_dir_perms } |
创建子目录。 |
r_dir_perms |
{ open getattr read search ioctl lock watch watch_reads } |
遍历并读取目录内容。 |
w_dir_perms |
{ open search write add_name remove_name lock } |
创建、删除或重命名文件/子目录 |
rw_dir_perms |
{ r_dir_perms w_dir_perms } | 在目录中创建/删除文件,并遍历目录。 |
2.1.5 编写 allow 规则的实战思维
当你在开发过程中遇到 avc: denied 日志时,添加 allow 规则是一个严谨的过程,而非随意堆砌权限。
- 读取日志,确定四要素 :
scontext→ 主体类型tcontext→ 客体类型tclass→ 客体类别denied { ... }→ 缺失的权限
- 选择最小权限集 :
- 如果日志显示
denied { read },不要直接给rw_file_perms。只给r_file_perms或更精确的{ open read getattr }。
- 如果日志显示
- 考虑使用宏 :
- 检查所需权限是否恰好对应某个预定义宏。如果是,直接使用宏,代码更简洁。
- 添加注释 :
- 在每条
allow规则上方添加注释,说明为什么需要这个权限。这对于后续维护至关重要。2.1.6
- 在每条
2.1.6 使用 audit2allow 快速生成初始策略
在手动分析 AVC 日志并编写规则之前,有一个非常高效的辅助工具可以帮你快速"翻译"日志为 TE 语句:audit2allow。
操作步骤:
-
清空旧日志,保证抓取内容纯净
bashadb shell logcat -c -
复现权限拒绝问题(运行你的程序或触发相关操作)
-
抓取 AVC 日志并保存到文件
bashadb shell logcat | grep "avc" > avc.txt -
使用
audit2allow将日志转换为 TE 规则bashaudit2allow -i avc.txt > hello.te -
查看生成的策略文件
bashcat hello.te
示例输出 (hello.te 内容):
text
#============= sedemo_dt ==============
allow sedemo_dt devpts:chr_file { read write };
allow sedemo_dt sedemo_dev_t:file { open read write };
重要提示:
audit2allow生成的策略仅供参考和快速验证 ,它只是机械地将日志"翻译"成规则,不会理解业务语义。- 切勿直接将输出结果贴进正式策略文件! 你应该:
- 分析生成的每一条
allow规则是否合理。 - 检查是否可以使用预定义宏 (如
rw_file_perms)替代展开的权限列表。 - 删除重复或不必要的规则。
- 添加清晰的注释说明授权原因。
- 确保遵循最小权限原则。
- 分析生成的每一条
完整调试闭环流程:
text
1. 抓取 AVC 日志 (logcat | grep "avc")
↓
2. 分析四要素 (scontext, tcontext, tclass, denied)
↓
3. (可选) 使用 audit2allow 生成参考规则
↓
4. 手工编写/优化 allow 规则 (最小权限 + 宏)
↓
5. 添加注释并整合到 .te 文件
↓
6. 编译策略 → 推送 → 验证
2.2 高级语法:属性与域转换
在掌握了基础的 allow 规则之后,面对复杂的系统策略,你很快会发现两个痛点:
- 需要对几十个不同的类型授予相同的权限,重复代码太多。
- 系统启动时,
init进程(u:r:init:s0)如何将服务进程切换到其专属的受限域(如u:r:vold:s0)?
解决这两个问题的利器,就是 属性(Attribute) 和 域转换(Domain Transition)。
2.2.1 宏 (Macro):简要回顾
宏是 SELinux 策略中的"代码片段",用于封装一组常用权限,避免重复书写,提高可读性。
selinux
# 定义宏 (位于 global_macros 或 te_macros)
define(`r_file_perms', `{ getattr open read ioctl lock map watch watch_reads }')
# 使用宏
allow myprocess myfile:file r_file_perms;
本节不再赘述宏的详细展开,后续我们将聚焦于属性和域转换。
2.2.2 属性 (Attribute):批量授权的"标签组"
1. 什么是属性?
属性是一组 SELinux 类型(Type)的集合名称。你可以把属性理解为一个"标签组",将多个具体的类型归入同一个组,然后只需要对这一整个组授权,组内所有类型都会自动获得该权限。
2. 定义和使用属性
属性通常在 attributes 文件中声明,然后在各个 .te 文件中通过 type attribute 将具体类型关联到属性上。
selinux
# All services declared as part of an HAL
attribute hal_service_type;
# All domains used for apps.
attribute appdomain;
ubuntu@ubuntu1804:~/aosp/system/sepolicy/public$ grep "type untrusted_app, domain;" ./* -rn
./untrusted_app.te:21:type untrusted_app, domain;
ubuntu@ubuntu1804:~/aosp/system/sepolicy/public$ grep "type platform_app, domain;" ./* -rn
./platform_app.te:5:type platform_app, domain;
ubuntu@ubuntu1804:~/aosp/system/sepolicy/public$ grep "type system_data_file," ./* -rn
./file.te:307:type system_data_file, file_type, data_file_type, core_data_file_type;
3. 对属性进行批量授权
一旦类型被关联到属性,我们就可以在策略中对属性 授权,其效果等同于对该属性下所有类型授权。
selinux
# 允许所有应用域读取所有数据文件类型的目录
allow domain data_file_type:dir r_dir_perms;
这条规则一次性覆盖了:其他所有 domain 与 data_file_type 的组合。
4. 实战意义
属性机制是实现 最小权限原则 和 策略可扩展性 的基石。当 Google 新增一种应用类型(例如 isolated_app)时,只需将其关联到 appdomain 属性,它就会自动继承所有针对 appdomain 授权的规则,无需修改任何现有 allow 语句。
2.2.3 域转换 (Domain Transition):进程的"安全变身"
这是 SELinux 最精妙、也最容易令人困惑的机制。它回答了一个根本问题:一个运行在高权限域(如 init)的进程,如何启动一个运行在受限域(如 vold)的子进程?
1. 问题场景
在 Android 启动过程中,init 进程是所有用户空间进程的祖先。如果 init 直接 fork() 并 exec() 执行 /vendor/bin/vold,按照 Linux 的继承逻辑,新进程会继承父进程 init 的 SELinux 上下文(u:r:init:s0)。但这显然违背了"最小权限原则"------存储守护进程 vold 不应该拥有 init 的全部能力。
SELinux 必须提供一种机制,让新进程在执行特定可执行文件时,自动切换 到一个预定义的、受限的域。这就是 域转换。
2. 域转换的三要素
要成功实现一次域转换,必须满足三个条件:
| 要素 | 说明 | 对应规则 |
|---|---|---|
| 入口文件类型 | 被执行的可执行文件必须具有一个特定的 SELinux 类型(通常以 _exec 结尾)。 |
在 file_contexts 中定义:/vendor/bin/vold u:object_r:vold_exec:s0 |
| 执行权限 | 旧域(父进程)必须拥有对该入口文件的 execute 权限。 |
allow init vold_exec:file execute; |
| 转换规则 | 必须有一条明确的 type_transition 规则,告诉 SELinux:当旧域执行该入口文件时,新进程应切换到目标域。 |
type_transition init vold_exec:process vold; |
3. 域转换过程图解

4. 实战:使用 domain_auto_trans 宏简化转换
手动编写三要素略显繁琐,AOSP 提供了高级宏 domain_auto_trans,一行搞定。
宏定义(位于 te_macros):
bash
#####################################
# domain_auto_trans(olddomain, type, newdomain)
# Automatically transition from olddomain to newdomain
# upon executing a file labeled with type.
#
define(`domain_auto_trans', `
# Allow the necessary permissions.
domain_trans($1,$2,$3)
# Make the transition occur by default.
type_transition $1 $2:process $3;
')
在 sedemo.te 中的使用示例:
bash
# 定义目标域类型和入口文件类型
type sedemo_dt, domain;
type sedemo_dt_exec, exec_type, vendor_file_type, file_type;
# 允许从 shell 域执行 sedemo 时自动转换到 sedemo_dt 域
domain_auto_trans(shell, sedemo_dt_exec, sedemo_dt)
参数说明:
$1=shell:旧域(父进程的域)$2=sedemo_dt_exec:入口文件类型$3=sedemo_dt:目标域(新进程的域)
这条宏展开后等价于:
bash
# 1. 授予执行权限和基本转换权限
allow shell sedemo_dt_exec:file { getattr open read execute };
allow shell sedemo_dt:process transition;
# 2. 允许目标域继承必要的文件描述符和环境
dontaudit shell sedemo_dt:process noatsecure;
# 3. 设置类型转换规则
type_transition shell sedemo_dt_exec:process sedemo_dt;
5. 域转换生效的前提:进程入口点
需要特别注意的是,域转换只在 exec() 系统调用发生时才会触发。这意味着:
- 通过
fork()产生的子进程会原样继承父进程的域,不发生转换。 - 只有紧跟着调用
exec()加载新的可执行文件时,内核才会检查type_transition规则并执行切换。
因此,启动脚本(如 init.rc)中配置的服务,在 init 执行 execve() 启动它们时,就会自然触发域转换。
2.2.4 小结
| 机制 | 核心作用 | 关键语法/宏 |
|---|---|---|
| 宏 | 封装常用权限,提高可读性 | define(), r_file_perms, create_file_perms |
| 属性 | 将多个类型分组,实现批量授权 | attribute, typeattribute |
| 域转换 | 控制进程在执行新程序时切换 SELinux 域 | domain_auto_trans(), type_transition |
第三部分:实战演练 ------ 为自定义进程 sedemo 添加 SELinux 策略
3.1 场景描述
我们有一个 C 程序 sedemo,它需要打开并写入 /dev/sedemo_dev 设备节点。本演练将完整演示如何为其配置 SELinux 策略,使其在强制模式下正常运行。
3.2 核心步骤速览
| 步骤 | 关键操作 | 涉及文件与命令 |
|---|---|---|
| 1 | 定义设备节点类型 | devnode.te type sedemo_dev_t, dev_type; |
| 2 | 定义进程域与入口文件类型 | sedemo.te type sedemo_dt, domain; type sedemo_dt_exec, exec_type, vendor_file_type, file_type; domain_auto_trans(shell, sedemo_dt_exec, sedemo_dt) |
| 3 | 绑定安全上下文 | file_contexts /dev/sedemo_dev u:object_r:sedemo_dev_t:s0 /vendor/bin/sedemo u:object_r:sedemo_dt_exec:s0 |
| 4 | 集成策略到编译 | BoardConfig.mk BOARD_SEPOLICY_DIRS += device/hello/sedemo/sepolicy |
| 5 | 编译并推送 | make selinux_policy adb push out/.../vendor/etc/selinux /vendor/etc/ adb push out/.../vendor/bin/sedemo /vendor/bin/ |
| 6 | 宽容模式下验证功能 | setenforce 0 touch /dev/sedemo_dev restorecon /dev/sedemo_dev /vendor/bin/sedemo |
| 7 | 强制模式下捕获 AVC 日志 | setenforce 1 `logcat |
| 8 | 生成并添加权限规则 | audit2allow -i avc.txt 将输出的 allow 规则手动添加到 sedemo.te |
| 9 | 重新编译并验证 | 重复步骤 5-7,直至无 AVC 拒绝日志 |
3.3 AVC 日志分析与策略生成
典型拒绝日志:
text
04-19 22:39:12.668 2079 2079 I sedemo : type=1400 audit(0.0:25): avc: denied { read write } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:sedemo_dt:s0 tcontext=u:object_r:devpts:s0 tclass=chr_file permissive=1
04-19 22:39:12.672 2079 2079 I sedemo : type=1400 audit(0.0:26): avc: denied { read write } for name="sedemo_dev" dev="tmpfs" ino=673 scontext=u:r:sedemo_dt:s0 tcontext=u:object_r:sedemo_dev_t:s0 tclass=file permissive=1
04-19 22:39:12.672 2079 2079 I sedemo : type=1400 audit(0.0:27): avc: denied { open } for path="/dev/sedemo_dev" dev="tmpfs" ino=673 scontext=u:r:sedemo_dt:s0 tcontext=u:object_r:sedemo_dev_t:s0 tclass=file permissive=1
使用 audit2allow 生成修复规则:
bash
audit2allow -i avc.txt > hello.te
输出:
bash
#============= sedemo_dt ==============
allow sedemo_dt devpts:chr_file { read write };
allow sedemo_dt sedemo_dev_t:file { open read write };
将该规则加入 sedemo.te 并重新编译策略即可。
3.4 关键调试技巧
restorecon:使file_contexts中的标签定义立即生效。su域陷阱 :userdebug/eng版本中adb shell默认运行在su域,权限极大,会掩盖 SELinux 问题。务必确认进程已转换到自己的受限域(用ps -AZ检查)。runcon:手动以指定上下文运行程序,用于测试域转换。
bash
runcon u:r:sedemo_dt:s0 /vendor/bin/sedemo
dt:s0 tcontext=u:object_r:sedemo_dev_t:s0 tclass=file permissive=1
04-19 22:39:12.672 2079 2079 I sedemo : type=1400 audit(0.0:27): avc: denied { open } for path="/dev/sedemo_dev" dev="tmpfs" ino=673 scontext=u:r:sedemo_dt:s0 tcontext=u:object_r:sedemo_dev_t:s0 tclass=file permissive=1
**使用 `audit2allow` 生成修复规则**:
```bash
audit2allow -i avc.txt > hello.te
输出:
bash
#============= sedemo_dt ==============
allow sedemo_dt devpts:chr_file { read write };
allow sedemo_dt sedemo_dev_t:file { open read write };
将该规则加入 sedemo.te 并重新编译策略即可。
3.4 关键调试技巧
restorecon:使file_contexts中的标签定义立即生效。su域陷阱 :userdebug/eng版本中adb shell默认运行在su域,权限极大,会掩盖 SELinux 问题。务必确认进程已转换到自己的受限域(用ps -AZ检查)。runcon:手动以指定上下文运行程序,用于测试域转换。
bash
runcon u:r:sedemo_dt:s0 /vendor/bin/sedemo