TE规则语法
本文面向已经读过 类型强制、客体类别与权限 和 SELinux运行模式 的读者。前文解释了 TE 规则怎样进入 avtab/AVC,本篇专注策略作者实际书写和排错的语法:source/target/class/permission 集合如何展开,self、attribute、减法、补集和宏分别在哪一阶段处理,allow、auditallow、dontaudit 与 neverallow 为什么不能互换。
本文只使用 Android 17 的真实策略与构建代码。由于当前本地 checkout 没有同步独立的 SELinux compiler project,本文不虚构 parser grammar 产生式,而是以 system/sepolicy 中可编译的规则、Soong m4/checkpolicy/secilc 命令和 kernel policy consumer 建立语义。读完后,你应能手工展开一条宏规则、识别集合的实际成员、判断规则影响访问还是只影响审计,并根据错误发生在 m4、checkpolicy、neverallow test 还是运行期 AVC 选择入口。
1. 规则骨架
四类 AV rule 共享同一骨架:
rule_kind source_set target_set:class_set permission_set;| 位置 | 单值 | 集合/属性 | 特殊表达 |
|---|---|---|---|
| rule kind | allow | 不适用 | auditallow、dontaudit、neverallow |
| source | system_server | { domain -init }、appdomain | *、补集表达式 |
| target | zygote | { file_type -exec_type } | self、* |
| class | file | { file lnk_file }、class 宏 | class 必须拥有相应 permission |
| permission | read | { open read map }、r_file_perms | *、~{ ... } |
语法被 m4 宏预处理后才交给 checkpolicy。r_file_perms、userdebug_or_eng(...)、binder_call(...) 并不是 SELinux parser 原生关键字;它们先展开成真正的 rule/source/target/class/permission token。
2. allow语法
2.1 单值规则
最小 allow 由一个 source type、一个 target type、一个 class 和一个 permission 构成。
源码文件:system/sepolicy/private/domain.te
allow domain init:process sigchld;
allow domain self:fd use;
allow domain proc:dir r_dir_perms;第一条 source 是 domain attribute,target 是具体 init,class 是 process,permission 是 sigchld。第二条 self 在每个 source type 展开后代表与 source 相同的 target type,而不是一个名为 self 的 policy type。
2.2 Permission集合
花括号只用于分组 token,本身不引入顺序。下面规则把多项 process permission 合并进相同 source/target/class 的 access vector。
allow domain self:process {
fork
sigchld
sigkill
sigstop
signull
signal
getsched
setsched
getsession
getpgid
getcap
setcap
getattr
setrlimit
};每个 permission 必须存在于 process class。把 search 写进 process permission 集会在编译阶段失败,因为 search 属于 dir class。
2.3 Class集合
class 也可以使用集合。Android 的 domain 基础规则同时授权自身 fifo/file 两类对象使用同一权限宏:
allow domain self:{ fifo_file file } rw_file_perms;这种写法只有当权限集合对每个 class 都合法时才可用。fifo_file 与 file 都继承 common file,因此 rw_file_perms 有共同语义;不能把 service_manager 混入同一 class 集合。
2.4 Source减法
source 集合可从 attribute 中排除 type。下面规则让除 artd_subprocess_type 成员外的 domain 对自身 process 使用 setpgid:
allow { domain -artd_subprocess_type } self:process setpgid;减法计算的是 type 集合。若一个 type 同时属于 domain 和 artd_subprocess_type,它被排除;若新增 type 加入 domain 但不加入排除 attribute,它会自动继承权限。
2.5 权限宏
源码文件:system/sepolicy/public/global_macros
define(`r_file_perms',
`{ getattr open read ioctl lock map watch watch_reads }')
define(`w_file_perms',
`{ open append write lock map }')
define(`rw_file_perms',
`{ r_file_perms w_file_perms }')
define(`r_dir_perms',
`{ open getattr read search ioctl lock watch watch_reads }')嵌套宏由 m4 递归展开,rw_file_perms 最终是去重后的 permission 名集合。策略评审必须展开宏;宏名“rw”不能说明是否包含 create、setattr 或 unlink,本例实际都不包含。
3. auditallow
3.1 不授予权限
auditallow 只把 permission bit 加入 av_decision.auditallow。访问还必须被 allow/conditional allow 留在 allowed 中。
源码文件:system/sepolicy/private/system_app.te
auditallow system_app net_radio_prop:property_service set;
auditallow system_app usb_control_prop:property_service set;
auditallow system_app usb_prop:property_service set;这些规则要求在允许的 property set 发生时产生审计,不能单独让 system_app 设置属性。若没有对应 allow,访问仍拒绝,审计类型按 deny 路径决定。
3.2 真实兼容规则
源码文件:system/sepolicy/private/untrusted_app_25.te
auditallow untrusted_app_25 app_data_file:file {
execute
execute_no_trans
};
auditallow untrusted_app_25 ashmem_device:chr_file open;
auditallow untrusted_app_25 self:netlink_route_socket nlmsg_getneigh;这些 auditallow 记录旧 target SDK 兼容域的敏感行为。它们的 source 是特定兼容 domain,不会自动覆盖新版本 untrusted_app。
4. dontaudit
4.1 静默拒绝
dontaudit 不改变 allowed,而是影响 auditdeny 掩码。请求在 enforcing 下仍然失败,只是不一定生成 AVC denial。
源码文件:system/sepolicy/private/traced_perf.te
dontaudit traced_perf domain:dir {
search
getattr
open
};
dontaudit traced_perf domain:process signal;traced_perf 探测多个进程目录或尝试信号时,预期失败被静默。遇到功能失败但没有日志,应使用策略查询和调用链确认,不能因为没有 AVC 就补一条 allow。
4.2 宏中的dontaudit
源码文件:system/sepolicy/public/te_macros
define(`domain_trans', `
allow $1 $2:file { getattr open read execute map };
allow $1 $3:process transition;
allow $3 $2:file { entrypoint open read execute getattr map };
ifelse($1, `init', `', `allow $3 $1:process sigchld;')
dontaudit $1 $3:process noatsecure;
allow $1 $3:process { siginh rlimitinh };
')调用 domain_auto_trans(a,b,c) 会间接生成 dontaudit。只在调用文件中搜索 dontaudit 会漏掉宏展开结果。
5. neverallow
5.1 编译期断言
neverallow 与 allow 共享集合语法,但它不进入运行期 access vector。它检查最终策略是否存在与禁止集合相交的 allow。
源码文件:system/sepolicy/private/traced_perf.te
neverallow traced_perf app_data_file_type:file *;* 表示该 class 的全部 permissions。即使另一个策略文件通过 attribute 或宏间接授予 traced_perf 某个 app data file permission,构建检查也应发现交集。
5.2 集合与补集
源码文件:system/sepolicy/private/domain.te
neverallow {
domain
-appdomain
userdebug_or_eng(`-overlay_remounter')
} {
data_file_type
-apex_art_data_file
-dalvikcache_data_file
-system_data_file
-apk_data_file
}:file no_x_file_perms;source 和 target 都是 attribute 减法,permission 是宏。判断某个 allow 是否冲突必须依次展开 build variant、attribute 成员、排除项和 no_x_file_perms。
5.3 Permission补集
neverallow 宏还可使用 permission 补集表达“除明确允许项外全部禁止”。
源码文件:system/sepolicy/public/neverallow_macros
define(`no_w_file_perms',
`{ append create link unlink relabelfrom rename setattr write }')
define(`no_rw_file_perms',
`{ no_w_file_perms open read ioctl lock watch watch_mount watch_sb watch_with_perm watch_reads }')
define(`no_x_file_perms',
`{ execute execute_no_trans }')宏本身是显式 permission 集,不是 ~ 补集;策略也可以直接写 ~{ ... } 表达 class permission universe 的补集。两者审查方式不同,前者随宏维护,后者随 class 新增 permission 自动扩大。
6. 运行期语义
6.1 访问向量
内核 security server 遍历 source/target type 与 attribute 组合,按 rule kind 写入三个不同 bitmask。
源码文件:kernel/common/security/selinux/ss/services.c
相关函数:context_struct_compute_av
avd->allowed = 0;
avd->auditallow = 0;
avd->auditdeny = 0xffffffff;
for (node = avtab_search_node(
&policydb->te_avtab, &avkey);
node;
node = avtab_search_node_next(
node, avkey.specified)) {
if (node->key.specified == AVTAB_ALLOWED)
avd->allowed |= node->datum.u.data;
else if (node->key.specified == AVTAB_AUDITALLOW)
avd->auditallow |= node->datum.u.data;
else if (node->key.specified == AVTAB_AUDITDENY)
avd->auditdeny &= node->datum.u.data;
else if (xperms &&
(node->key.specified & AVTAB_XPERMS))
services_compute_xperms_drivers(
xperms, node);
}allow 使用 OR 累积:任意匹配规则都能增加 permission。auditallow 也使用 OR,增加允许审计。dontaudit 编译为 auditdeny 数据并通过 AND 收紧默认全 1 掩码,抑制对应 denial 审计。
6.2 Allow与审计
AVC 先调用 avc_has_perm_noaudit() 得到访问结果和 decision,再由 avc_audit() 根据 allowed/auditallow/auditdeny 决定日志。
源码文件:kernel/common/security/selinux/avc.c
int avc_has_perm(
u32 ssid, u32 tsid, u16 tclass,
u32 requested,
struct common_audit_data *auditdata)
{
struct av_decision avd;
int rc, rc2;
rc = avc_has_perm_noaudit(
ssid, tsid, tclass,
requested, 0, &avd);
rc2 = avc_audit(
ssid, tsid, tclass,
requested, &avd, rc, auditdata);
if (rc2)
return rc2;
return rc;
}因此审计失败本身也可能成为最终返回错误;auditallow/dontaudit 不是完全独立于访问调用链的日志装饰。
6.3 neverallow缺席
运行期 context_struct_compute_av() 没有 neverallow 分支,因为合法 binary policy 在生成前已经完成断言检查。设备出现 AVC denial 不表示某条 neverallow 正在动态拒绝;默认拒绝来自 requested bit 不在 allowed 中。
7. 构建检查
7.1 m4与conf
Soong 先把属性、.te、roles 等输入排序,再用 m4 展开条件和宏。语法错误可能发生在 m4 阶段,也可能在展开后的 policy.conf 被 checkpolicy 发现。
源码文件:system/sepolicy/build/soong/policy.go
rule.Command().Tool(
ctx.Config().PrebuiltBuildTool(ctx, "m4")).
Flag("--fatal-warnings").
FlagWithArg("-D target_build_variant=",
c.buildVariant(ctx)).
FlagWithArg("-D target_full_treble=",
c.sepolicySplit(ctx)).
Inputs(srcsWithNewline).
Text("> ").Output(conf)如果宏引号、逗号或括号不配对,m4 可能先失败;如果展开后缺少分号、type/class/permission 不存在,则 checkpolicy/secilc 失败。
7.2 neverallow双检查
se_neverallow_test 创建两份 user policy.conf:一份包含 build-test rules 交给 checkpolicy,另一份排除 build-test rules 后交给 sepolicy-analyze neverallow。
源码文件:system/sepolicy/build/soong/sepolicy_neverallow.go
相关函数:neverallowTestModule.GenerateAndroidBuildActions
binaryPolicy := pathForModuleOut(ctx, "policy")
rule.Command().BuiltTool("checkpolicy").
Flag("-M").
FlagWithArg("-c ", strconv.Itoa(PolicyVers)).
FlagWithOutput("-o ", binaryPolicy).
Input(checkpolicyConfPath)
rule.Build("neverallow_checkpolicy",
"Neverallow check: "+ctx.ModuleName())
rule = android.NewRuleBuilder(pctx, ctx)
rule.Command().BuiltTool("sepolicy-analyze").
Input(binaryPolicy).
Text("neverallow").
Flag("-w").
FlagWithInput("-f ", sepolicyAnalyzeConfPath).
Text("|| (echo").
Flag("-e").
Text("sepolicy-analyze failed").
Text("; exit 1)")两条检查覆盖 compiler 直接 neverallow 和分析工具对最终 binary/conf 的交叉验证。SELINUX_IGNORE_NEVERALLOWS 可以让模块只生成空 timestamp,但这属于显式绕过配置,不能作为发布策略的正常完成路径。
8. 常见错误
| 错误 | 失败阶段 | 定位方向 |
|---|---|---|
| 缺少分号、括号不匹配 | m4/checkpolicy | 查看展开后的 conf 与首个报错位置 |
| source/target type 不存在 | checkpolicy/secilc | 查 type 声明与分区 public/private 边界 |
| class 不存在 | checkpolicy或policy映射 | 查 security_classes 与 kernel/userspace owner |
| permission 不属于 class | checkpolicy | 查 access_vectors,不要凭名字猜 |
| 宏展开权限过宽 | neverallow | 展开 attribute、减法、permission宏 |
| allow存在仍拒绝 | 运行期 | 检查标签、class、constraint、MLS、xperm、其他LSM |
| 无日志但功能失败 | 运行期 | 检查 dontaudit、DAC、提前返回、日志限流 |
| auditallow无日志 | 运行期 | 先确认对应访问被allow且实际执行 |
9. 反向测试
9.1 Expanded TE查询
sepolicy_tests.py 通过 QueryExpandedTERule() 查询 attribute 和宏展开后的真实规则,再汇总 permission 集合。这是验证“源策略看似有宏”是否真正授予权限的反向入口。
源码文件:system/sepolicy/tests/sepolicy_tests.py
相关测试:TestIsolatedComputeAllowedPropertySubset
allowedPerms = {"getattr", "open", "read", "map"}
for typeName in test_policy.pol.QueryTypeAttribute(
Type="isolated_compute_allowed_property_type",
IsAttr=True):
grantedPerms = set()
for rule in test_policy.pol.QueryExpandedTERule(
scontext={"untrusted_app"},
tcontext={typeName},
tclass={"file"}):
grantedPerms.update(rule.perms)
missingPerms = allowedPerms.difference(grantedPerms)
if missingPerms:
violatingTypes.append(
f"{typeName} (missing: {', '.join(missingPerms)})")输入是编译策略、source domain、target attribute 成员和 file class;断言是每个目标 type 都具有完整读取权限集合。它验证 allow/attribute 展开,不验证文件实际标签和 DAC。
9.2 Build flag测试
Soong 单元测试构造 true、false 和缺失 release flags,断言只有存在的 flags 被稳定转换成 m4 宏。它反向验证条件规则输入不会把缺失 flag 默认为某个值。
源码文件:system/sepolicy/build/soong/selinux_test.go
相关测试:TestFlagCollector
actual := flagsToM4Macros(collectorData.BuildFlags)
expected := []string{
"-D target_flag_RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT=true",
"-D target_flag_RELEASE_FLAGS_BAR=true",
"-D target_flag_RELEASE_FLAGS_FOO1=false",
}
if !reflect.DeepEqual(actual, expected) {
t.Errorf("M4 macros were not exported correctly"+
"\nactual: %v"+
"\nexpected: %v",
actual, expected)
}该测试证明条件宏的输入集合与顺序,未编译完整 TE policy,也不证明任意 userdebug_or_eng 分支的业务权限正确。
10. 源码导航
先找真实规则,再展开它引用的 attribute 与宏:
# 搜索四类AV规则及其所在策略文件。
rg -n '^(allow|auditallow|dontaudit|neverallow) ' \
system/sepolicy/public system/sepolicy/private找到规则调用点后,再读取它依赖的权限宏、transition 宏和 neverallow 集合:
# 查看权限宏、neverallow宏与宏调用点。
rg -n 'define\(`(r_file_perms|domain_trans|no_x_file_perms)' \
system/sepolicy/public/global_macros \
system/sepolicy/public/te_macros \
system/sepolicy/public/neverallow_macros确认 source/target/class/permission 后,再检查内核编译结果的消费方式:
# 查看allow、auditallow和dontaudit如何进入av_decision。
rg -n 'AVTAB_ALLOWED|AVTAB_AUDITALLOW|AVTAB_AUDITDENY|auditallow|auditdeny' \
kernel/common/security/selinux/ss/services.c \
kernel/common/security/selinux/avc.cneverallow 问题应从构建模块和展开后的 conf 定位:
# 查看neverallow双检查与生成的中间策略。
rg -n 'se_neverallow_test|neverallow_checkpolicy|neverallow_sepolicy-analyze' \
system/sepolicy/build/soong/sepolicy_neverallow.go \
system/sepolicy/Android.bp
find out -path '*sepolicy*' -type f -name '*.conf' 2>/dev/null | head -40运行策略测试时,使用完整构建环境而不是只解析单个 .te 文件:
# 构建neverallow和全局策略测试目标。
m sepolicy_neverallows sepolicy_test
atest policy_test apex_sepolicy_tests_test设备侧诊断只用于观察最终策略行为:
# 提取运行期四元组,再回到编译策略查询。
adb shell su 0 dmesg | rg 'avc:.*(denied|granted)'
adb shell 'id -Z; getenforce'11. 闭环复述
- 手工拆解
allow { domain -artd_subprocess_type } self:process setpgid;的 source、target、class 和 permission,并说明新增 domain 何时会自动继承它。 - 展开
rw_file_perms,列出实际 permission;再说明为什么该宏不能直接用于service_managerclass。 - 对比
allow与auditallow:给定只有 auditallow 的规则,说明 access result 和日志路径;给定 allow 但无 auditallow,说明默认允许日志行为。 - 对比
dontaudit与 permissive:前者怎样修改 auditdeny,后者怎样修改 denial 返回值;说明二者组合为何可能既放行又无日志。 - 展开一条包含 attribute 减法和 permission 宏的 neverallow,说明 checkpolicy 与
sepolicy-analyze分别消费哪份输入,为什么 neverallow 不是运行期 deny rule。 - 给定“allow 已存在仍 AVC denied”,列出标签错误、class 错误、constraint/MLS、xperm、条件分支和其他 LSM 六个继续检查点。
TE 规则语法的难点不在四个关键字,而在展开后的集合和生效阶段。策略源码经过 m4、attribute 展开和 compiler 检查后,allow/auditallow/dontaudit 才成为运行期 access vector;neverallow 则在策略生成前阻止危险授权。只有沿这条链阅读,才能避免把日志规则当授权、把构建断言当运行期拒绝,或用一个过宽 allow 掩盖错误标签。
