Skip to content

类型强制

从 type、attribute、allow 和 type_transition 源码,追踪 TE 规则如何构建并驱动运行期访问裁决。

基于android-17.0.0_r1
AndroidSELinuxTEtypedomainsepolicy源码阅读

类型强制 ​

本文面向已经读过 SELinux架构、安全上下文 和 LSM框架 的读者。前文已经解释 context 字符串怎样变成 SID,以及 LSM 怎样把 SELinux hook 接到内核;本文继续追踪策略内部的核心关系:一个 type 如何成为进程 domain 或文件 type,一个 attribute 如何形成可复用集合,一条 allow 怎样进入 policy/avtab,type_transition 怎样决定新进程或新文件的类型,以及 Android 为什么还要用 neverallow 和构建测试限制这些授权。

本文不把 TE 简化成“写一条 allow 就能访问”。读者将沿一条真实案例阅读:servicemanager 通过 add_service/service_manager 相关规则保护 Binder 名称空间,system_server 通过 domain、capability 和对象 type 获得最小权限;然后把这条规则回接到内核 AVC 的 <source SID, target SID, class, permission> 查询。读完后,你应能从一条 denial 反推出 source domain、target type、class 和缺失 permission,并判断问题属于类型声明、宏展开、标签来源、规则授权还是构建约束。

1. TE输入 ​

TE 的访问请求可以写成四元组,而不是旧资料中常说的“单纯三元组”:

输入运行期表示策略来源例子
source typesource SID 对应的 type/domain进程 context、domain transitionsystem_server
target typetarget SID 对应的 typefile/service/property contexts、对象创建activity_service
classsecurity classkernel object class 定义service_manager、file
permissionaccess vector bitclass 权限集合、allow/neverallowfind、add、read

allow source target:class { perms }; 是声明层语法;运行期 SELinux 并不再次解析这行文本,而是使用编译后的 type 编号、class 编号和 permission bit。attribute 也不会在运行期作为一个模糊标签存在,它会在构建或加载阶段展开为多个 type,具体是否保留 attribute 信息取决于编译器选项。

图中的 type 是策略的逻辑实体,SID 是内核运行期实体。一个 denial 中的 scontext=u:r:system_server:s0 和 tcontext=u:object_r:activity_service:s0 仍需去掉 user/role/level 后定位 TE type;但 level 约束或其他 class 规则可能影响最终结果,不能机械删除上下文的其余字段。

2. 类型集合 ​

2.1 type声明 ​

type 声明创建策略命名空间中的类型;逗号后的 attribute 列表把新类型加入一个或多个集合。进程 domain 通常加入 domain,文件类型加入 file_type 或更具体的分区/用途 attribute。

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

相关声明:核心类型 attribute

text
attribute domain;

attribute file_type;

attribute exec_type;

attribute system_file_type;

attribute service_manager_type;

attribute system_server_service;

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

相关规则:init 的 domain/attribute 关系

text
typeattribute init coredomain;
init_daemon_domain(init)

init_daemon_domain(init) 是宏调用,不是 type init, domain; 的替代品;init 的基础类型声明位于其他公共策略输入,宏再补齐从 init 到 init_exec 的执行和转换规则。阅读一个 domain 时,要同时找类型声明、attribute 关联、可执行文件 type 和 transition 宏。

2.2 attribute集合 ​

attribute 的价值是把一组有共同语义的 type 交给一条规则。Android 17 的 attributes 文件还通过 expandattribute 控制某些 attribute 是否在 CIL/二进制策略中展开。

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

相关声明:数据、应用和设备类型集合

text
attribute data_file_type;
expandattribute data_file_type false;

attribute app_data_file_type;
expandattribute app_data_file_type false;

attribute proc_type;
expandattribute proc_type false;

attribute proc_net_type;
expandattribute proc_net_type true;

attribute property_type;
attribute service_manager_type;

expandattribute ... false 不能理解成“attribute 不会工作”。它通常意味着编译器保留 attribute 关系供运行期或工具查询,而不是把所有成员完全替换掉;true 则更适合只用于编译期批量规则的集合。具体输出要结合 CIL 和工具版本验证,不能仅凭布尔值猜测二进制布局。

2.3 规则中的集合 ​

集合可以出现在 source、target 或两侧。system_server 策略中使用 appdomain、coredomain 等 attribute,是为了表达“所有属于该职责集合的 type”,而不是授予某个字符串前缀。

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

相关规则:system_server 对 app domain 的操作

text
allow system_server appdomain:process { getpgid sigkill signal };

allow system_server appdomain:process { signull };

allow system_server appdomain:process { getsched setsched };

这三条规则的 target 不是单一 untrusted_app,而是 appdomain 集合。新增一个 domain 并把它加入 appdomain 后,可能自动继承这些权限;因此 attribute 关联本身也是安全审查边界,不能当成无害分类标签。

2.4 属性减法 ​

Android 策略常用集合减法排除特殊 domain。减法不是运行期 if,而是构建阶段把 source type 集合解析为“attribute 成员减去列出的 type”。

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

相关规则:应用对 data/proc 类型的约束

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 是所有 domain 减去 appdomain 和特定调试例外,target 是 data_file_type 减去允许执行的几类数据文件。读规则时必须先展开集合,否则很容易把 neverallow 误读成“禁止所有 domain 执行所有 data 文件”。

3. allow规则 ​

3.1 基本授权 ​

allow 是运行期授权的主要输入,但它只覆盖指定 source/target/class/permission 组合。没有隐含的“同一 domain 可以访问同前缀文件”规则。

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

相关规则:servicemanager 的 Binder 与服务 type 权限

text
allow servicemanager self:binder set_context_mgr;
allow servicemanager {
  domain
  -init
  -vendor_init
  -hwservicemanager
  -vndservicemanager
}:binder transfer;

allow servicemanager service_contexts_file:file r_file_perms;
allow servicemanager vendor_service_contexts_file:file r_file_perms;

add_service(servicemanager, service_manager_service)

self:binder set_context_mgr 允许 servicemanager 成为 Binder context manager;第二条允许它从多数 domain 接收/转移 Binder 引用,但明确排除 init 类 domain;add_service 是宏,负责把 servicemanager 注册到 service manager 类型集合。三者解决的是不同 object class/permission,不能合并成“servicemanager 有 Binder 权限”。

3.2 Binder宏展开 ​

binder_use 与 binder_call 把常见的 Binder 方向关系封装成多条 allow。宏展开后,client→server 的 call/transfer、server→client 的 transfer 和 fd 使用分别出现。

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

相关宏:binder_use、binder_call

text
define(`binder_use', `
allow $1 servicemanager:binder { call transfer };
allow servicemanager $1:binder { call transfer };
')

define(`binder_call', `
allow $1 $2:binder { call transfer };
allow $2 $1:binder transfer;
allow $1 $2:fd use;
')

宏参数是 type 名或集合表达式,展开后才成为具体 TE 规则。若某个 Binder 调用失败,先确认调用者和服务端 domain,再展开宏中是 call、transfer 还是 fd use 缺失;仅添加一条 binder_call 可能扩大引用转移范围,应以实际数据流为准。

3.3 权限集合宏 ​

r_file_perms、r_dir_perms 等宏只是权限集合的文本别名,不会跨 class 自动转换。r_file_perms 能用于 file,但目录需要 r_dir_perms;把文件宏套到错误 class 会在编译期失败或表达错误权限。

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

相关宏:文件、目录和 socket 权限集合

text
define(`x_file_perms', `{ getattr execute execute_no_trans map }')
define(`r_file_perms', `{ getattr open read ioctl lock map watch watch_reads }')
define(`w_file_perms', `{ open append write lock map }')
define(`rx_file_perms', `{ r_file_perms x_file_perms }')
define(`rw_file_perms', `{ r_file_perms w_file_perms }')

define(`r_dir_perms', `{ open getattr read search ioctl lock watch watch_reads }')
define(`w_dir_perms', `{ open search write add_name remove_name lock }')
define(`rw_dir_perms', `{ r_dir_perms w_dir_perms }')

权限集合会直接影响授权面。例如 rw_file_perms 同时包含 open/read/write/append,并不是“可读写但不允许 metadata”;安全评审时要展开宏查看真实 bit,而不是只看宏名字。

4. 过渡规则 ​

4.1 domain_trans ​

domain_trans(old, exec_type, new) 只授予“可以完成转换”的权限,不自动让每次 exec 都发生转换;domain_auto_trans 在此基础上添加 process type_transition。

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

相关宏:domain_trans、domain_auto_trans

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 };
')

define(`domain_auto_trans', `
domain_trans($1,$2,$3)
type_transition $1 $2:process $3;
')

宏展开后至少涉及 source→executable file 的 execute、source→new process 的 transition、new domain→entrypoint 的 entrypoint,以及自动选择新 process type 的 type_transition。缺任一条,结果可能是 exec 被拒绝、转换不发生,或新 domain 无法把文件作为入口执行。

4.2 init启动案例 ​

Android 17 通过 domain_auto_trans 为 init 启动的守护进程建立自动转换。以 servicemanager 为例:

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

相关规则:init_daemon_domain(servicemanager)

text
typeattribute servicemanager coredomain;
init_daemon_domain(servicemanager)

init_daemon_domain(servicemanager) 展开为 domain_auto_trans(init, servicemanager_exec, servicemanager)。servicemanager_exec 必须由 file_contexts 标注在 /system/bin/servicemanager 上;init 对该 executable 的访问和 process transition 允许后,exec 才能把新 task 放入 servicemanager domain。

源码文件:system/sepolicy/private/file_contexts

相关规则:servicemanager executable 标签

text
/system/bin/servicemanager     u:object_r:servicemanager_exec:s0

这条链把三个 owner 串起来:file_contexts owner 决定文件 target type,TE transition owner 决定新 process type,init 执行路径是消费者。仅写 type servicemanager, domain 而不标记 executable,不能完成启动转换。

4.3 文件类型转换 ​

文件创建时的默认 type 由父目录和文件系统行为决定,file_type_auto_trans 可以增加 domain/dir→new file type 的自动规则。

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

相关宏:file_type_trans、file_type_auto_trans

text
define(`file_type_trans', `
allow $1 $2:dir ra_dir_perms;
allow $1 $3:notdevfile_class_set create_file_perms;
allow $1 $3:dir create_dir_perms;
')

define(`file_type_auto_trans', `
file_type_trans($1, $2, $3)
type_transition $1 $2:dir $3;
type_transition $1 $2:notdevfile_class_set $3;
')

自动标签只决定新对象的 type,不自动授予 domain 对父目录和新 type 的全部权限;宏中的 ra_dir_perms、create_file_perms 和 create_dir_perms 正是为此补上的访问权限。创建失败时要分别检查父目录 add_name、目标文件 create 和新 type 的写权限。

4.4 tmpfs类型 ​

tmpfs_domain(system_server) 为 system_server 在 tmpfs/ashmem 上创建的文件建立专属 type,并授予最小读写映射权限。

源码文件:system/sepolicy/private/system_server.te、system/sepolicy/public/te_macros

相关规则:tmpfs_domain

text
tmpfs_domain(system_server)

define(`tmpfs_domain', `
type_transition $1 tmpfs:file $1_tmpfs;
allow $1 $1_tmpfs:file { read write getattr map };
')

这条规则的 target type 是 system_server_tmpfs,不是泛化的 tmpfs;type transition 发生在对象创建时,后续 file permission 读取的是新 inode SID。遇到 ashmem/tmpfs denial 时,不能只给 system_server tmpfs:file 加权限而忽略自动生成的专属 type。

图中 type transition 只选择新 type/domain,AVC 仍要检查执行文件、process transition 和 entrypoint 权限。过渡规则和 allow 规则是两个层次,不能只验证其中一层。

5. 构建链 ​

5.1 conf阶段 ​

Soong 的 se_policy_conf 模块把 .te、attributes、宏输入排序、插入换行并交给 m4。target_build_variant、Treble、ASAN 等条件会进入 m4 宏,因此同一策略源在不同 product 配置下可能展开不同。

源码文件:system/sepolicy/build/soong/policy.go

相关函数:policyConf.transformPolicyToConf

go
srcs := android.PathsForModuleSrc(ctx, c.properties.Srcs)
sort.SliceStable(srcs, func(x, y int) bool {
    return findPolicyConfOrder(srcs[x].Base()) < findPolicyConfOrder(srcs[y].Base())
})

newlineFile := android.PathForModuleOut(ctx, "newline")
rule.Command().Text("echo").FlagWithOutput("> ", newlineFile)
rule.Temporary(newlineFile)
var srcsWithNewline android.Paths
for _, src := range srcs {
    srcsWithNewline = append(srcsWithNewline, src, newlineFile)
}

rule.Command().Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
    Flag("--fatal-warnings").
    FlagWithArg("-D mls_num_sens=", strconv.Itoa(MlsSens)).
    FlagWithArg("-D mls_num_cats=", strconv.Itoa(c.mlsCats())).
    FlagWithArg("-D target_build_variant=", c.buildVariant(ctx)).
    FlagWithArg("-D target_full_treble=", c.sepolicySplit(ctx)).
    Inputs(srcsWithNewline).
    Text("> ").Output(conf)

输入排序和换行处理是可观察的构建状态。排查宏展开时,应先看生成的 policy.conf,确认 type/attribute/allow 是否实际出现,再继续下游;直接阅读 .te 文件无法发现条件宏或输入顺序造成的变化。

5.2 CIL阶段 ​

se_policy_cil 使用 checkpolicy -C -M -L 把 conf 转成 CIL,默认还调用 secilc 对 CIL 做检查。

源码文件:system/sepolicy/build/soong/policy.go

相关函数:policyCil.compileConfToCil

go
checkpolicyCmd := rule.Command().BuiltTool("checkpolicy").
    Flag("-C").
    Flag("-M").
    Flag("-L").
    FlagWithArg("-c ", strconv.Itoa(PolicyVers)).
    FlagWithOutput("-o ", cil).
    Input(conf)

if proptools.BoolDefault(c.properties.Secilc_check, true) {
    rule.Command().BuiltTool("secilc").
        Flag("-m").
        FlagWithArg("-M ", "true").
        Flag("-G").
        FlagWithArg("-c ", strconv.Itoa(PolicyVers)).
        Text(cil.String()).
        FlagWithArg("-o ", os.DevNull).
        FlagWithArg("-f ", os.DevNull).
        Flag("-v")
}

CIL 阶段是检查 type 引用、class/permission 合法性、宏展开结果和 policy version 的关键边界。-G 会展开并移除自动生成的 attribute;因此调试工具看到的 attribute 结构可能与源 .te 不完全相同。

5.3 avtab消费 ​

内核加载 binary policy 后,TE 规则进入 access vector table。avtab key 按 source type、target type、target class 组织,普通规则不能重复插入相同 key,extended permissions 则有额外处理。

源码文件:kernel/common/security/selinux/ss/avtab.c

相关函数:avtab_hash、avtab_insert

c
static inline u32 avtab_hash(
        const struct avtab_key *keyp, u32 mask)
{
    // ... MurmurHash3混合过程。
    mix(keyp->target_class);
    mix(keyp->target_type);
    mix(keyp->source_type);
    return hash & mask;
}

static int avtab_insert(
        struct avtab *h,
        const struct avtab_key *key,
        const struct avtab_datum *datum)
{
    // ... 在哈希桶中比较source/target/class/specification。
    if (cmp == 0 && !(key->specified & AVTAB_XPERMS))
        return -EEXIST;
    // ... 分配节点并插入。
    return 0;
}

avtab 是策略加载后的索引结构,不是运行期每次访问都重新解析文本。规则冲突、重复声明或 extended permission 的行为要在编译器和 avtab 插入逻辑两侧确认;看到 CIL 中有一行并不等于 binary policy 一定按预期合并。

5.4 AVC查询 ​

运行期 hook 调用 avc_has_perm_noaudit(),以 SID pair 和 class 查找 AVC;cache miss 时计算并插入决策,requested permission 与 allowed bitmask 比较。

源码文件:kernel/common/security/selinux/avc.c

相关函数:avc_has_perm_noaudit、avc_has_perm

c
inline int avc_has_perm_noaudit(
        u32 ssid, u32 tsid, u16 tclass,
        u32 requested, unsigned int flags,
        struct av_decision *avd)
{
    u32 denied;
    struct avc_node *node;

    if (WARN_ON(!requested))
        return -EACCES;

    rcu_read_lock();
    node = avc_lookup(ssid, tsid, tclass);
    if (unlikely(!node)) {
        rcu_read_unlock();
        return avc_perm_nonode(
                ssid, tsid, tclass, requested,
                flags, avd);
    }
    denied = requested & ~node->ae.avd.allowed;
    memcpy(avd, &node->ae.avd, sizeof(*avd));
    rcu_read_unlock();

    if (unlikely(denied))
        return avc_denied(
                ssid, tsid, tclass, requested,
                0, 0, 0, flags, avd);
    return 0;
}

avc_has_perm_noaudit() 只负责查决策和返回结果,avc_has_perm() 再调用 avc_audit()。所以一个 allow 规则的运行期消费者是 AVC access vector,不是某个 Java 服务;审计是结果的另一个消费者。

6. 约束规则 ​

6.1 dontaudit ​

dontaudit 不授予权限,它只影响拒绝事件是否审计。Android 17 的 domain_trans 宏就对 noatsecure 使用 dontaudit,避免已知的安全模式检查产生噪音。

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

相关规则:domain transition 中的 dontaudit

text
dontaudit $1 $3:process noatsecure;

因此“没有 AVC 日志”可能有三种原因:请求在 DAC/其他 hook 提前失败、策略允许访问、策略拒绝但被 dontaudit 静默。调试时必须结合 class/permission 和策略查询,不能只看日志数量。

6.2 neverallow ​

neverallow 是构建期安全不变量。它不作为一条运行期“拒绝规则”替代 allow,而是阻止任何最终策略同时满足 source、target、class 和 permission 的危险组合。

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

相关规则:限制 capability 的 neverallow

text
define(`dac_override_allowed', `{
  apexd
  artd
  casefolding_remover
  dnsmasq
  dumpstate
  init
  installd
  lmkd
  netd
  recovery
  ueventd
  vendor_init
  vold
  zygote
  zygote_next
}')

neverallow ~dac_override_allowed \
    self:global_capability_class_set dac_override;

一个 domain 即使写了 allow my_domain self:capability dac_override;,只要不在允许集合中,最终构建也应失败。neverallow 检查的是策略全局闭包,不能通过在另一个文件重复写 allow 绕过。

6.3 条件宏 ​

策略条件宏根据 build variant、设备能力或 release flag 选择规则。userdebug_or_eng 不是运行期读取 ro.debuggable 的 if,而是 m4 在构建时决定是否输出规则。

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

相关规则:条件化 domain transition

text
userdebug_or_eng(`
  domain_auto_trans(init, logcat_exec, logpersist)
')

同一源码在 user 和 userdebug 构建中可能产生不同 domain transition 和权限集合。复现 denial 时必须记录 product/build variant;否则用 userdebug 生成的 policy 解释 user 设备行为会得到错误结论。

7. Android案例 ​

7.1 servicemanager ​

servicemanager 是一个很好的 TE 案例,因为它同时拥有进程 domain、Binder context manager 能力、service_contexts 文件读取权限和服务名称注册权限。

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

相关规则:完整核心片段

text
typeattribute servicemanager coredomain;
init_daemon_domain(servicemanager)

allow servicemanager self:binder set_context_mgr;
allow servicemanager {
  domain
  -init
  -vendor_init
  -hwservicemanager
  -vndservicemanager
}:binder transfer;

allow servicemanager service_contexts_file:file r_file_perms;
add_service(servicemanager, service_manager_service)

selinux_check_access(servicemanager)

这里至少有四条独立链路:启动转换、成为 Binder context manager、读取服务标签数据库、主动调用 selinux_check_access()。看到 servicemanager 拒绝某个服务名时,不能只检查 add_service;名称 target context、calling SID 和 service_manager permission 仍可能失败。

7.2 system_server ​

system_server 的策略同时覆盖 app domain 进程控制、Zygote fd/进程关系、capability 和大量文件/socket type。它不是“root 所以全能”,而是一个被显式列出权限的 domain。

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

相关规则:进程与 capability 权限

text
allow system_server zygote:fd use;
allow system_server zygote:process sigchld;

allow system_server appdomain:process {
  getpgid sigkill signal signull
};

allow system_server self:global_capability_class_set {
    ipc_lock
    kill
    net_admin
    net_bind_service
    net_broadcast
    net_raw
    sys_boot
    sys_nice
    sys_ptrace
    sys_time
    sys_tty_config
};

allow system_server self:global_capability2_class_set mac_admin;

mac_admin 在此策略中用于 safesetid 管理,并不表示 system_server 获得“管理 SELinux 策略”的通用权限;源码注释明确指出两者不是同义。TE 阅读必须以 class 和 permission 为单位,不能仅凭 capability 名字下结论。

7.3 增量文件系统 ​

system_server 对 incremental/apk data file 的 ioctl 使用 allowxperm 限制命令集合,这是 TE 对普通权限之外的扩展控制。

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

相关规则:增量文件系统 ioctl

text
allow system_server incremental_control_file:file { ioctl r_file_perms };
allowxperm system_server incremental_control_file:file ioctl {
  INCFS_IOCTL_CREATE_FILE
  INCFS_IOCTL_CREATE_MAPPED_FILE
  INCFS_IOCTL_PERMIT_FILL
  INCFS_IOCTL_GET_READ_TIMEOUTS
  INCFS_IOCTL_SET_READ_TIMEOUTS
  INCFS_IOCTL_GET_LAST_READ_ERROR
};

allowxperm 仍然要求普通 file class 的 ioctl 基础权限,然后才允许列出的 ioctl 命令。出现 ioctl denial 时,普通 allow ... ioctl 和 xperm allowlist 是两个检查面。

8. 测试验证 ​

8.1 sepolicy_test ​

Android 17 的 sepolicy_test 不是单元测试某一条 allow,而是把五个分区 file_contexts 和编译后的 precompiled policy 一起交给 sepolicy_tests.py,检查类型属性、分区归属、property owner 等全局不变量。

源码文件:system/sepolicy/Android.bp

相关目标:sepolicy_test

make
java_genrule {
    name: "sepolicy_test",
    srcs: [
        ":plat_file_contexts",
        ":vendor_file_contexts",
        ":system_ext_file_contexts",
        ":product_file_contexts",
        ":odm_file_contexts",
        ":precompiled_sepolicy",
    ],
    tools: ["sepolicy_tests"],
    out: ["sepolicy_test"],
    cmd: "$(location sepolicy_tests) " +
        "-f $(location :plat_file_contexts) " +
        "-f $(location :vendor_file_contexts) " +
        "-f $(location :system_ext_file_contexts) " +
        "-f $(location :product_file_contexts) " +
        "-f $(location :odm_file_contexts) " +
        "-p $(location :precompiled_sepolicy) && " +
        "touch $(out)",
}

输入是合并后的五类 file contexts 和策略 binary,断言由 Python 测试函数执行;它验证策略与分区标签的组合一致性,不证明设备上每个 inode 都已经执行 restorecon,也不证明某个业务 API 的端到端调用成功。

8.2 分区属性测试 ​

TestCoredomainViolations 检查从 /system 启动的 domain 是否具备 coredomain,从 /vendor 启动的 domain 是否错误地拥有 coredomain。

源码文件:system/sepolicy/tests/sepolicy_tests.py

相关测试:TestCoredomainViolations

python
def TestCoredomainViolations(test_policy):
    ret = ""
    violators = []
    for d in test_policy.alldomains:
        domain = test_policy.alldomains[d]
        if domain.fromSystem and "coredomain" not in domain.attributes:
            violators.append(d)
    if len(violators) > 0:
        ret += "The following domain(s) must be associated with the "
        ret += '"coredomain" attribute because they are executed off of '
        ret += "/system:\n"
        ret += " ".join(str(x) for x in sorted(violators)) + "\n"

    violators = []
    for d in test_policy.alldomains:
        domain = test_policy.alldomains[d]
        if domain.fromVendor and "coredomain" in domain.attributes:
            violators.append(d)
    return ret

测试输入来自 policy parser 对编译策略和 file contexts 的分析,断言是 domain 来源与 attribute 的一致性。它反向验证了 attribute 不是文档标签,而是会被工具消费的策略关系;它没有验证每条 allow 的运行期访问结果。

8.3 neverallow边界 ​

se_policy_binary 在非 debuggable 构建中还调用 sepolicy-analyze permissive,检查策略中不应出现的 permissive domain;neverallow 则由 secilc/checkpolicy 相关阶段处理。两类检查都不是运行期 AVC denial。

源码文件:system/sepolicy/build/soong/policy.go

相关函数:policyBinary.GenerateAndroidBuildActions

go
if !ctx.Config().Debuggable() {
    permissiveDomains := pathForModuleOut(ctx, c.stem()+"_permissive")
    cmd := rule.Command().BuiltTool("sepolicy-analyze").
        Input(bin).
        Text("permissive")

    cmd.Text(" > ").Output(permissiveDomains)

    rule.Command().Text("if test").
        FlagWithInput("-s ", permissiveDomains).
        Text("; then echo").
        Flag("-e").
        Text("ERROR: permissive domains not allowed in user builds").
        Text("&& cat ").
        Input(permissiveDomains).
        Text("; exit 1; fi")
}

输入是已生成的 binary policy,user build 的关键断言是 permissive domain 列表为空;失败发生在构建产物阶段,设备不会拿到该策略。调试一条 denial 时,不能把“构建通过”当成“运行期 allow 已验证”。

9. 调试方法 ​

9.1 四元组 ​

bash
# 读取主体 domain、目标对象 type 和当前活动模块。
adb shell 'id -Z'
adb shell 'ps -AZ | grep -E "system_server|servicemanager|zygote"'
adb shell 'ls -lZ /system/bin/servicemanager /dev/socket/zygote'
adb shell 'cat /sys/kernel/security/lsm'

如果已经拿到一条 denial,下一组命令把日志字段映射回 source、target、class 和 permission:

bash
# 从 denial 中提取 source、target、class 和 permission。
adb shell su 0 dmesg | rg 'avc:.*denied'

# 在源码中定位 target type、source domain 和相关 class 权限。
rg -n 'servicemanager|system_server|activity_service|service_manager|binder' \
  system/sepolicy/private system/sepolicy/public

如果日志只有 path 而没有 target type,先查 file_contexts 或对应名称 contexts;如果有 target type 但规则搜索不到,检查 attribute、宏展开和 build variant;如果规则存在仍拒绝,继续检查 DAC、class、xperm、neverallow 和其他 LSM hook。

9.2 展开宏 ​

bash
# 先查看宏定义,再查看调用点,手工展开 source/target/class/perms。
rg -n -A25 -B5 'define\(`(domain_auto_trans|binder_call|r_file_perms)' \
  system/sepolicy/public/te_macros \
  system/sepolicy/public/global_macros
rg -n 'domain_auto_trans\(|binder_call\(|r_file_perms|rw_file_perms' \
  system/sepolicy/private system/sepolicy/public

确认宏调用后,再定位构建系统生成的中间策略文件:

bash
# 查找生成的 policy.conf/CIL;路径随产品和构建系统变化。
find out -path '*sepolicy*' -type f \( -name 'policy.conf' -o -name '*.cil' \) \
  2>/dev/null | head -40

宏展开后的文件是判断实际授权面的直接证据,源文件中的宏名只是入口。不要把 r_file_perms 当作一条不可分解的权限。

9.3 运行测试 ​

bash
# 构建策略全局检查目标,并运行 host 侧策略测试。
m sepolicy_test
atest apex_sepolicy_tests_test policy_test

9.4 最小修复 ​

修复 denial 时按以下顺序定位:

  1. 确认 source domain 来自哪个进程,避免把调用者 UID 当成 domain。
  2. 确认 target type 来自哪个 contexts 数据库,避免对路径或服务名直接写 allow。
  3. 确认 class 与 permission,尤其是 service_manager、binder、fd、file 和 xperm 的区别。
  4. 检查是否已有 attribute/宏可以表达最小关系,避免复制一组过宽权限。
  5. 运行 neverallow、分区属性和 user build permissive 检查,再在 enforcing 设备验证。

10. 闭环复述 ​

  1. 给定 scontext=u:r:system_server:s0、tcontext=u:object_r:activity_service:s0、tclass=service_manager 和 permission find,指出四元组各字段的来源,并说明哪一层会消费它。
  2. 从 init_daemon_domain(servicemanager) 开始,复述 macro 展开、servicemanager_exec 文件标签、process type_transition、entrypoint 权限和新 domain 生效的顺序。
  3. 展开 binder_call(client, server),分别列出 client→server call/transfer、server→client transfer 和 fd use;给定 fd denial 时解释为什么只补 binder call 可能不够。
  4. 解释 attribute 减法和 expandattribute 的差异,并判断把一个新 domain 加入 appdomain 可能会继承哪些现有规则。
  5. 对比 allow、dontaudit、neverallow 和 sepolicy-analyze permissive 的生效时机:哪些影响运行期返回值,哪些只影响日志,哪些在构建阶段阻断产物。

TE 的核心不是记住某一条 allow,而是沿类型关系追踪状态:contexts 决定 source/target type,宏和 attribute 组织授权,构建工具把规则编译成 policy/avtab,内核 AVC 再按 class 与 permission 消费。遇到新 domain、新服务或新文件类型时,先把它放回这条链,再决定需要修改标签、type/attribute、allow、transition 还是构建约束。