Skip to content

TE规则语法

用 Android 真实策略、宏展开、内核 access vector 和 neverallow 构建测试解释四类 TE 规则的语法与生效边界。

基于android-17.0.0_r1
AndroidSELinuxTEallowneverallowsepolicy源码阅读

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 共享同一骨架:

text
rule_kind source_set target_set:class_set permission_set;
位置单值集合/属性特殊表达
rule kindallow不适用auditallow、dontaudit、neverallow
sourcesystem_server{ domain -init }、appdomain*、补集表达式
targetzygote{ file_type -exec_type }self、*
classfile{ file lnk_file }、class 宏class 必须拥有相应 permission
permissionread{ 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

text
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。

text
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 两类对象使用同一权限宏:

text
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:

text
allow { domain -artd_subprocess_type } self:process setpgid;

减法计算的是 type 集合。若一个 type 同时属于 domain 和 artd_subprocess_type,它被排除;若新增 type 加入 domain 但不加入排除 attribute,它会自动继承权限。

2.5 权限宏 ​

源码文件:system/sepolicy/public/global_macros

text
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

text
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

text
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

text
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

text
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

text
neverallow traced_perf app_data_file_type:file *;

* 表示该 class 的全部 permissions。即使另一个策略文件通过 attribute 或宏间接授予 traced_perf 某个 app data file permission,构建检查也应发现交集。

5.2 集合与补集 ​

源码文件:system/sepolicy/private/domain.te

text
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

text
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

c
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

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

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

go
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 不属于 classcheckpolicy查 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

python
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

go
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 与宏:

bash
# 搜索四类AV规则及其所在策略文件。
rg -n '^(allow|auditallow|dontaudit|neverallow) ' \
  system/sepolicy/public system/sepolicy/private

找到规则调用点后,再读取它依赖的权限宏、transition 宏和 neverallow 集合:

bash
# 查看权限宏、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 后,再检查内核编译结果的消费方式:

bash
# 查看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.c

neverallow 问题应从构建模块和展开后的 conf 定位:

bash
# 查看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 文件:

bash
# 构建neverallow和全局策略测试目标。
m sepolicy_neverallows sepolicy_test
atest policy_test apex_sepolicy_tests_test

设备侧诊断只用于观察最终策略行为:

bash
# 提取运行期四元组,再回到编译策略查询。
adb shell su 0 dmesg | rg 'avc:.*(denied|granted)'
adb shell 'id -Z; getenforce'

11. 闭环复述 ​

  1. 手工拆解 allow { domain -artd_subprocess_type } self:process setpgid; 的 source、target、class 和 permission,并说明新增 domain 何时会自动继承它。
  2. 展开 rw_file_perms,列出实际 permission;再说明为什么该宏不能直接用于 service_manager class。
  3. 对比 allow 与 auditallow:给定只有 auditallow 的规则,说明 access result 和日志路径;给定 allow 但无 auditallow,说明默认允许日志行为。
  4. 对比 dontaudit 与 permissive:前者怎样修改 auditdeny,后者怎样修改 denial 返回值;说明二者组合为何可能既放行又无日志。
  5. 展开一条包含 attribute 减法和 permission 宏的 neverallow,说明 checkpolicy 与 sepolicy-analyze 分别消费哪份输入,为什么 neverallow 不是运行期 deny rule。
  6. 给定“allow 已存在仍 AVC denied”,列出标签错误、class 错误、constraint/MLS、xperm、条件分支和其他 LSM 六个继续检查点。

TE 规则语法的难点不在四个关键字,而在展开后的集合和生效阶段。策略源码经过 m4、attribute 展开和 compiler 检查后,allow/auditallow/dontaudit 才成为运行期 access vector;neverallow 则在策略生成前阻止危险授权。只有沿这条链阅读,才能避免把日志规则当授权、把构建断言当运行期拒绝,或用一个过宽 allow 掩盖错误标签。