类型强制
本文面向已经读过 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 type | source SID 对应的 type/domain | 进程 context、domain transition | system_server |
| target type | target SID 对应的 type | file/service/property contexts、对象创建 | activity_service |
| class | security class | kernel object class 定义 | service_manager、file |
| permission | access vector bit | class 权限集合、allow/neverallow | find、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
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 关系
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
相关声明:数据、应用和设备类型集合
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 的操作
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 类型的约束
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 权限
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
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 权限集合
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
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)
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 标签
/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
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
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
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
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
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
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
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
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
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
相关规则:完整核心片段
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 权限
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
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
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
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
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 四元组
# 读取主体 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:
# 从 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 展开宏
# 先查看宏定义,再查看调用点,手工展开 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确认宏调用后,再定位构建系统生成的中间策略文件:
# 查找生成的 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 运行测试
# 构建策略全局检查目标,并运行 host 侧策略测试。
m sepolicy_test
atest apex_sepolicy_tests_test policy_test9.4 最小修复
修复 denial 时按以下顺序定位:
- 确认 source domain 来自哪个进程,避免把调用者 UID 当成 domain。
- 确认 target type 来自哪个 contexts 数据库,避免对路径或服务名直接写 allow。
- 确认 class 与 permission,尤其是
service_manager、binder、fd、file和 xperm 的区别。 - 检查是否已有 attribute/宏可以表达最小关系,避免复制一组过宽权限。
- 运行 neverallow、分区属性和 user build permissive 检查,再在 enforcing 设备验证。
10. 闭环复述
- 给定
scontext=u:r:system_server:s0、tcontext=u:object_r:activity_service:s0、tclass=service_manager和 permissionfind,指出四元组各字段的来源,并说明哪一层会消费它。 - 从
init_daemon_domain(servicemanager)开始,复述 macro 展开、servicemanager_exec文件标签、processtype_transition、entrypoint 权限和新 domain 生效的顺序。 - 展开
binder_call(client, server),分别列出 client→server call/transfer、server→client transfer 和 fd use;给定 fd denial 时解释为什么只补 binder call 可能不够。 - 解释 attribute 减法和
expandattribute的差异,并判断把一个新 domain 加入appdomain可能会继承哪些现有规则。 - 对比 allow、dontaudit、neverallow 和
sepolicy-analyze permissive的生效时机:哪些影响运行期返回值,哪些只影响日志,哪些在构建阶段阻断产物。
TE 的核心不是记住某一条 allow,而是沿类型关系追踪状态:contexts 决定 source/target type,宏和 attribute 组织授权,构建工具把规则编译成 policy/avtab,内核 AVC 再按 class 与 permission 消费。遇到新 domain、新服务或新文件类型时,先把它放回这条链,再决定需要修改标签、type/attribute、allow、transition 还是构建约束。
