Policy Compilation
本文面向已经读过 Public Private Policy 和 Custom Daemon Domain 的读者。前者解释 public/private 与分区接口,后者提供一个可追踪的策略输入;本文把这两个局部知识接到构建和启动主线:Soong 如何收集 .te 与 contexts 文件,se_policy_conf 如何按语法顺序运行 M4,se_policy_cil 如何调用 checkpolicy 和 secilc,se_versioned_policy 如何处理 vendor 兼容,最后 init 如何把多个 CIL 合成内核可加载的 policy。尚未阅读过前置文章时,正文中的目录、scope、CIL 和 domain 名称也会在第一次出现处解释。
文章不把中间文件名称当作流水线本身。每个产物都对应不同 owner 和消费者:policy.conf 供 checkpolicy 解析,raw CIL 供过滤/版本化,versioned CIL 供跨分区合并,二进制 policy 才由 init 交给 kernel。任何一个阶段通过都不能推出下一阶段或运行时访问一定成功。
1. 产物关系
1.1 文件与消费者
| 产物 | 生成者 | 消费者 | 典型失败 |
|---|---|---|---|
*.conf | se_policy_conf + M4 | checkpolicy | 宏、顺序、条件错误 |
| raw CIL | checkpolicy -C | filter/version tool | policy 语义或类型错误 |
| versioned CIL | version_policy | vendor/platform merge | API mapping 缺失 |
| binary policy | se_policy_binary/secilc | init/kernel | CIL 合并、neverallow、policy version |
1.2 主线图
2. 源文件收集
2.1 se_build_files
源码文件:system/sepolicy/Android.bp、system/sepolicy/build/soong/build_files.go
se_build_files {
name: "se_build_files",
// Collection patterns only; M4/checkpolicy run in later modules.
srcs: [
"security_classes", "initial_sids", "access_vectors",
"global_macros", "neverallow_macros", "mls_macros",
"mls_decl", "mls", "policy_capabilities", "te_macros",
"ioctl_defines", "ioctl_macros", "nlmsg_defines", "nlmsg_macros",
"attributes", "*.te", "roles_decl", "roles", "users",
"initial_sid_contexts", "fs_use", "genfs_contexts", "port_contexts",
],
}下面的 Go 实现把这些源目录收集成带 scope tag 的输出;Android.bp 只声明收集器输入。
func (b *buildFiles) GenerateAndroidBuildActions(ctx android.ModuleContext) {
b.srcs = make(map[string]android.Paths)
b.srcs[".reqd_mask"] = b.findSrcsInDirs(ctx,
filepath.Join("system", "sepolicy", "reqd_mask"))
b.srcs[".plat_public"] = b.findSrcsInDirs(ctx,
filepath.Join("system", "sepolicy", "public"))
b.srcs[".plat_private"] = b.findSrcsInDirs(ctx,
filepath.Join("system", "sepolicy", "private"))
b.srcs[".plat_vendor"] = b.findSrcsInDirs(ctx,
filepath.Join("system", "sepolicy", "vendor"))
b.srcs[".vendor"] = b.findSrcsInDirs(ctx,
ctx.DeviceConfig().VendorSepolicyDirs()...)
b.srcs[".odm"] = b.findSrcsInDirs(ctx,
ctx.DeviceConfig().OdmSepolicyDirs()...)
// Tags are consumed by :se_build_files{.<tag>} in Android.bp.
b.setOutputFiles(ctx)
}findSrcsInDirs 通过 GlobWithDeps 建立 Soong 依赖,setOutputFiles 再把每个 tag 暴露给 Blueprint 的带 tag 依赖。se_build_files 模块只收集路径,不运行 M4 或 checkpolicy;*.te、attributes、contexts 和 vendor/device 目录在这里获得不同 tag,后续 policy module 选择哪些 tag,才决定某文件能否进入 platform 或 vendor policy。prebuilts/api/* 还会生成带版本后缀的 public/private tag,供兼容测试使用。
2.2 输入集合
源码文件:system/sepolicy/Android.bp
plat_public_policy = [":se_build_files{.plat_public}"]
plat_private_policy = [":se_build_files{.plat_private}"]
system_ext_public_policy = [":se_build_files{.system_ext_public}"]
product_public_policy = [":se_build_files{.product_public}"]
reqd_mask_policy = [":se_build_files{.reqd_mask}"]
vendor_policy = [
// Vendor receives platform vendor glue plus device-owned policy.
":se_build_files{.plat_vendor}",
":se_build_files{.vendor}",
":ashmem_build_file",
]plat_sepolicy.conf 的 srcs 是 plat_public_policy + plat_private_policy;vendor_sepolicy.conf 则组合 plat_public_policy + system_ext_public_policy + product_public_policy + reqd_mask_policy + vendor_policy。所以 platform private 不会直接进入 vendor 输入,而 reqd mask 只提供让 public policy 能够被 checkpolicy 解析的最小声明,随后会在 raw CIL 阶段过滤。把一个 .te 文件放入错误目录,可能造成 undefined type,也可能把内部规则错误导出。
2.3 模块图
源码文件:system/sepolicy/Android.bp
se_policy_conf {
name: "plat_sepolicy.conf",
defaults: ["se_policy_conf_flags_defaults"],
srcs: plat_public_policy +
plat_private_policy,
installable: false,
}
se_policy_cil {
name: "plat_sepolicy.cil",
src: ":plat_sepolicy.conf",
additional_cil_files: [":sepolicy_technical_debt{.plat_private}"],
}
se_policy_conf {
name: "vendor_sepolicy.conf",
defaults: ["se_policy_conf_flags_defaults"],
srcs: plat_public_policy +
system_ext_public_policy +
product_public_policy +
reqd_mask_policy +
vendor_policy,
vendor: true,
installable: false,
}
se_policy_cil {
name: "vendor_sepolicy.unversioned.cil",
src: ":vendor_sepolicy.conf",
filter_out: [":reqd_policy_mask.cil"],
secilc_check: false, // se_versioned_policy performs the merge check
vendor: true,
installable: false,
}这里有一个容易漏掉的所有权转移:plat_sepolicy.cil 是 platform 的完整策略,直接由 se_policy_cil 生成;vendor 先生成 vendor_sepolicy.unversioned.cil,明确关闭本模块的 secilc 检查,再交给 se_versioned_policy 做版本化和依赖合并。installable: false 只表示中间产物不直接安装到分区,不表示它不会被后续 module 消费。
3. policy.conf
3.1 固定顺序
源码文件:system/sepolicy/build/soong/policy.go
var policyConfOrder = []string{
"flagging_macros",
"security_classes",
"initial_sids",
"access_vectors",
"global_macros",
"neverallow_macros",
"mls_macros",
"mls_decl",
"mls",
"policy_capabilities",
"te_macros",
"ioctl_defines",
"ioctl_macros",
"nlmsg_defines",
"nlmsg_macros",
"attributes|*.te",
"roles_decl", "roles", "users",
"initial_sid_contexts", "fs_use",
"genfs_contexts", "port_contexts",
}checkpolicy 的语法依赖顺序:class/vector 先于规则,宏先于调用,attribute 先于 .te,contexts 声明在策略主体之后。Soong 会对输入 stable sort;文件系统枚举顺序不是可靠策略顺序。
3.2 M4变量
源码文件:system/sepolicy/build/soong/policy.go
rule.Command().Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
Flag("--fatal-warnings").
FlagForEachArg("-D ", ctx.DeviceConfig().SepolicyM4Defs()).
FlagWithArg("-D mls_num_sens=", strconv.Itoa(MlsSens)).
FlagWithArg("-D mls_num_cats=", strconv.Itoa(c.mlsCats())).
FlagWithArg("-D target_arch=", ctx.DeviceConfig().DeviceArch()).
FlagWithArg("-D target_build_variant=", c.buildVariant(ctx)).
FlagWithArg("-D target_full_treble=", c.sepolicySplit(ctx)).
FlagWithArg("-D target_recovery=", strconv.FormatBool(c.isTargetRecovery())).
Inputs(srcsWithNewline).
Text("> ").Output(conf)M4 变量把 build variant、架构、MLS category、Treble、recovery 和设备自定义定义注入策略。userdebug_or_eng()、recovery_only()、full_treble_only() 的结果在这里决定;同一源码在不同 target 可能产生不同 policy.conf。
3.3 换行与删节
源码文件:system/sepolicy/build/soong/policy.go
newlineFile := android.PathForModuleOut(ctx, "newline")
rule.Command().Text("echo").FlagWithOutput("> ", newlineFile)
rule.Temporary(newlineFile)
var srcsWithNewline android.Paths
for _, src := range srcs {
// Prevent adjacent files from merging when the source lacks a final LF.
srcsWithNewline = append(srcsWithNewline, src, newlineFile)
}每个输入之间插入换行,避免前一个文件的最后一行和下一个文件首行拼接。Only_neverallow_rules 只用于生成 CTS 检查输入;它不是正常运行策略的替代品。
3.4 policy.conf链
源码文件:system/sepolicy/build/soong/policy.go
func (c *policyConf) transformPolicyToConf(ctx android.ModuleContext) android.OutputPath {
conf := pathForModuleOut(ctx, c.stem())
srcs := android.PathsForModuleSrc(ctx, c.properties.Srcs)
sort.SliceStable(srcs, func(x, y int) bool {
return findPolicyConfOrder(srcs[x].Base()) <
findPolicyConfOrder(srcs[y].Base())
})
// M4 receives the ordered files plus a separator file for each input.
// The RuleBuilder command writes the resulting text to conf.
return conf
}该函数的 owner 是 Soong policyConf module,消费者是 se_policy_cil;返回路径只是中间产物,不表示已通过 checkpolicy。
4. CIL编译
4.1 checkpolicy调用
源码文件:system/sepolicy/build/soong/policy.go
checkpolicyCmd := rule.Command().BuiltTool("checkpolicy").
Flag("-C").
Flag("-M").
Flag("-L").
FlagWithArg("-c ", strconv.Itoa(PolicyVers)).
FlagWithOutput("-o ", cil).
Input(conf)-C 生成 CIL,-M 启用 MLS,-L 保留 allow 规则行标记,-c 30 使用 Android 17 的 policy version。checkpolicy 负责把 policy.conf 语法和声明解析为 CIL,但不会完成所有分区 CIL 的最终合并。
4.2 filter与附加CIL
源码文件:system/sepolicy/build/soong/policy.go
if len(c.properties.Filter_out) > 0 {
rule.Command().BuiltTool("build_sepolicy").
Text("filter_out").
Flag("-f").
Inputs(android.PathsForModuleSrc(ctx, c.properties.Filter_out)).
FlagWithOutput("-t ", cil)
}
if len(c.properties.Additional_cil_files) > 0 {
rule.Command().Text("cat").
Inputs(android.PathsForModuleSrc(ctx, c.properties.Additional_cil_files)).
Text(">> ").Output(cil)
}public policy 会过滤 reqd mask 等辅助内容;technical debt 等不能由 policy language 表达的 CIL 可在输出末尾追加。过滤发生在 raw CIL 之后,不能把过滤前的声明当作最终设备接口。
4.3 secilc检查
源码文件:system/sepolicy/build/soong/policy.go
secilcCmd := rule.Command().BuiltTool("secilc").
Flag("-m").
FlagWithArg("-M ", "true").
Flag("-G").
FlagWithArg("-c ", strconv.Itoa(PolicyVers)).
Inputs(android.PathsForModuleSrc(ctx, c.properties.Filter_out)).
Text(cil.String()).
FlagWithArg("-o ", os.DevNull).
FlagWithArg("-f ", os.DevNull).
Flag("-v")secilc 这里输出到 /dev/null,用途是提前检查 CIL 与 filter inputs 的合并;真正可加载二进制由 se_policy_binary 另一个 module 生成。Ignore_neverallow 只在明确配置或环境开关下添加 -N,不应作为生产修复。
4.4 兼容构建脚本
Soong module 之外,源码树还保留 build_sepolicy.py build_cil 这条 host 工具路径。它把 raw CIL、版本化和依赖合并检查串成一个命令,常用于兼容产物或独立调试;不要把它和 se_policy_cil 的 Soong action 当成两套不同的策略语义。
源码文件:system/sepolicy/build/build_sepolicy.py
def do_build_cil(args):
# checkpolicy emits a temporary raw CIL before filtering and versioning.
input_file_name = os.path.splitext(args.input_policy_conf)[0]
raw_cil_file = input_file_name + '_raw.cil'
checkpolicy_cmd = [args.checkpolicy_env]
checkpolicy_cmd += [os.path.join(args.android_host_path, 'checkpolicy'),
'-C', '-M', '-c', args.policy_vers,
'-o', raw_cil_file, args.input_policy_conf]
run_host_command(' '.join(checkpolicy_cmd), shell=True)
file_utils.filter_out([args.reqd_mask], raw_cil_file)
output_file = args.output_cil or (input_file_name + '.cil')
run_host_command([os.path.join(args.android_host_path, 'version_policy'),
'-b', args.base_policy, '-t', raw_cil_file,
'-n', args.treble_sepolicy_vers, '-o', output_file])
if args.filter_out_files:
file_utils.filter_out(args.filter_out_files, output_file)
if args.dependent_cils:
merge_cmd = [os.path.join(args.android_host_path, 'secilc'),
'-m', '-M', 'true', '-G', '-N', '-c', args.policy_vers]
merge_cmd += args.dependent_cils
merge_cmd += [output_file, '-o', '/dev/null', '-f', '/dev/null']
run_host_command(merge_cmd)输入是 policy.conf、reqd mask、base public CIL 和目标版本;中间状态是带 _raw.cil 后缀的未版本化文件;消费者是 version_policy 与最后的 secilc 合并检查。run_host_command 使用 check_call,任一 host 工具返回非零就退出,因此“raw CIL 已生成”不等于“版本化 CIL 可与依赖策略合并”。
5. 版本化与合并
5.1 versioned policy
源码文件:system/sepolicy/build/soong/versioned_policy.go
version := proptools.StringDefault(m.properties.Version, "current")
if version == "current" {
version = ctx.DeviceConfig().PlatformSepolicyVersion()
} else if version == "vendor" {
version = ctx.DeviceConfig().BoardSepolicyVers()
}
if proptools.Bool(m.properties.Mapping) &&
proptools.String(m.properties.Target_policy) != "" {
ctx.ModuleErrorf("Can't set both mapping and target_policy")
}mapping=true 生成 public API mapping;target_policy 则把目标策略按 base 版本标注。两种模式互斥,版本由 platform 或 board sepolicy version 决定。
5.2 mapping与target
源码文件:system/sepolicy/build/soong/versioned_policy.go
if proptools.Bool(m.properties.Mapping) {
rule.Command().BuiltTool("version_policy").
FlagWithInput("-b ", android.PathForModuleSrc(ctx, *m.properties.Base)).
FlagWithArg("-n ", version).
FlagWithOutput("-o ", out).
Flag("-m")
} else if target := proptools.String(m.properties.Target_policy); target != "" {
mapping := pathForModuleOut(ctx, stem+".mapping.cil")
rule.Command().BuiltTool("version_policy").
FlagWithInput("-b ", android.PathForModuleSrc(ctx, *m.properties.Base)).
FlagWithArg("-n ", version).
FlagWithOutput("-o ", mapping).
Flag("-m")
attributized := pathForModuleOut(ctx, stem+".attributized.cil")
rule.Command().BuiltTool("version_policy").
FlagWithInput("-b ", android.PathForModuleSrc(ctx, *m.properties.Base)).
FlagWithArg("-n ", version).
FlagWithInput("-t ", android.PathForModuleSrc(ctx, target)).
FlagWithOutput("-o ", attributized)
// Remove declarations already supplied by the mapping before concatenation.
rule.Command().BuiltTool("build_sepolicy").
Text("filter_out").
Flag("-f").
Input(mapping).
FlagWithOutput("-t ", attributized)
rule.Command().Text("cat").
Input(mapping).
Input(attributized).
Text("> ").
Output(out)
}
if len(m.properties.Filter_out) > 0 {
rule.Command().BuiltTool("build_sepolicy").
Text("filter_out").
Flag("-f").
Inputs(android.PathsForModuleSrc(ctx, m.properties.Filter_out)).
FlagWithOutput("-t ", out)
}
if len(m.properties.Dependent_cils) > 0 {
// Merge-check the versioned output without producing another binary.
rule.Command().BuiltTool("secilc").
Flag("-m").
FlagWithArg("-M ", "true").
Flag("-G").
Flag("-N").
FlagWithArg("-c ", strconv.Itoa(PolicyVers)).
Inputs(android.PathsForModuleSrc(ctx, m.properties.Dependent_cils)).
Text(out.String()).
FlagWithArg("-o ", os.DevNull).
FlagWithArg("-f ", os.DevNull)
}mapping 的消费者是旧 vendor/platform policy;它不是“把 private type 导出”的工具。只有 public base 中的 type/attribute 才进入兼容 API。target 模式先生成 mapping,再把 target policy attributize,过滤重复声明后拼成一个可供下游合并的 CIL;顺序反过来会让同名声明在 secilc 阶段冲突。公共的 filter_out 和 Dependent_cils 分支随后再次处理输出:前者删掉不应导出的 CIL,后者让 secilc 在 /dev/null 上验证跨分区可合并性。
5.3 分区合并
源码文件:system/core/init/selinux.cpp
std::vector<const char*> compile_args {
"/system/bin/secilc",
use_userdebug_policy ? *userdebug_plat_sepolicy : plat_policy_cil_file,
"-m", "-M", "true", "-G", "-N", "-v",
"-c", version_as_string.c_str(),
plat_mapping_file.c_str(),
"-o", compiled_sepolicy,
"-f", "/sys/fs/selinux/null",
};
if (!vendor_policy_cil_file.empty()) {
compile_args.push_back(vendor_policy_cil_file.c_str());
}init 在设备启动时把 platform、mapping、system_ext/product、vendor 和可选 odm CIL 交给 secilc,输出临时二进制文件。-N 在这里是加载合并路径的固定选择,neverallow 检查应在构建阶段完成。
5.4 userdebug分支
源码文件:system/core/init/selinux.cpp
std::optional<const char*> GetUserdebugPlatformPolicyFile() {
// An unlocked debuggable device may opt into the debug ramdisk policy.
const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
if (force_debuggable_env && "true"s == force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
const std::vector<const char*> debug_policy_candidates = {
#if INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT == 1
"/system_ext/etc/selinux/userdebug_plat_sepolicy.cil",
#endif
kDebugRamdiskSEPolicy,
};
for (const char* debug_policy : debug_policy_candidates) {
if (access(debug_policy, F_OK) == 0) {
return debug_policy;
}
}
}
return std::nullopt;
}只有 INIT_FORCE_DEBUGGABLE=true 且设备已解锁时,init 才会选择 userdebug platform CIL;否则 OpenSplitPolicy 使用普通 plat_sepolicy.cil。这解释了为什么同一源码树生成的 userdebug 策略不能直接替代 user 设备上的 vendor 预编译策略:输入集合本身已经不同。
5.5 预编译策略
源码文件:system/core/init/selinux.cpp
if (!use_userdebug_policy) {
if (auto res = FindPrecompiledSplitPolicy(); res.ok()) {
unique_fd fd(open(res->c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
if (fd != -1) {
policy_file->fd = std::move(fd);
policy_file->path = std::move(*res);
return true;
}
}
}
LOG(INFO) << "Compiling SELinux policy";userdebug policy 不能直接复用 vendor 预编译策略;hash 不匹配或文件缺失时,init 回退到 secilc 编译。预编译命中是性能路径,不改变 public/private 的源码边界。
下面的时序只回答一个运行时问题:启动时究竟何时读取预编译文件,何时创建 /dev/sepolicy.XXXXXX 并调用 secilc,失败又由谁返回。它对应 OpenSplitPolicy 的决策顺序,不替代上面的构建依赖图。
这里的 alt 分支说明了两个不同的失败责任:预编译不匹配只是选择 fallback,并不代表启动失败;fallback 的 secilc 非零会让 OpenSplitPolicy 返回 false,随后 ReadPolicy 无法继续;即使拿到二进制,security_load_policy 仍可能因内核拒绝而触发 FATAL。
6. 二进制输出
6.1 se_policy_binary
源码文件:system/sepolicy/build/soong/policy.go
secilcCmd := rule.Command().BuiltTool("secilc").
Flag("-m").
FlagWithArg("-M ", "true").
Flag("-G").
FlagWithArg("-c ", strconv.Itoa(PolicyVers)).
Inputs(android.PathsForModuleSrc(ctx, c.properties.Srcs)).
FlagWithOutput("-o ", bin).
FlagWithArg("-f ", os.DevNull)该 module 的输入是已经生成的 CIL 集合,输出是二进制 sepolicy。若用户 build 产生 permissive domain,module 还会调用 sepolicy-analyze permissive 并使构建失败,除非 domain 在 allowlist 中。
源码文件:system/sepolicy/build/soong/policy.go
if !ctx.Config().Debuggable() {
permissiveDomains := pathForModuleOut(ctx, c.stem()+"_permissive")
cmd := rule.Command().BuiltTool("sepolicy-analyze").
Input(bin).
Text("permissive")
// User builds may name an explicit allowlist; all other domains are fatal.
if len(c.properties.Permissive_domains_on_user_builds) != 0 {
cmd.Text("| { grep -Fxv")
for _, d := range c.properties.Permissive_domains_on_user_builds {
cmd.FlagWithArg("-e ", proptools.ShellEscape(d))
}
cmd.Text(" || true; }")
}
cmd.Text(" > ").Output(permissiveDomains)
}检查发生在 secilc 产出临时二进制之后、复制到最终输出之前;因此它消费的是编译器实际理解的 domain 集合,而不是 .te 文本中的字符串搜索。ctx.Config().Debuggable() 为 true 时跳过该限制,这也是 userdebug/eng 与 user 构建结果可能不同的一个具体原因。
6.2 内核加载
源码文件:system/core/init/selinux.cpp
static void LoadSelinuxPolicy(std::string& policy) {
// security_load_policy transfers the monolithic result to the kernel.
if (security_load_policy(policy.data(), policy.size()) < 0) {
PLOG(FATAL) << "SELinux: Could not load policy";
}
}二进制 policy 的消费者是 kernel security server;加载失败是启动致命错误,不是某个 domain 的 AVC。
7. 失败与证据
7.1 阶段分类
| 失败 | owner | 证据 |
|---|---|---|
| M4 fatal warning | policyConf | conf 生成日志 |
| undefined type/class | checkpolicy | policy.conf 行号 |
| neverallow violation | checkpolicy/secilc | 规则和冲突 type |
| mapping missing | version_policy/Treble test | mapping CIL |
| CIL merge error | secilc | dependent CIL 列表 |
| binary load error | init/kernel | kmsg/security_load_policy |
| runtime AVC | kernel | scontext/tcontext/tclass/permission |
7.2 诊断命令
# Locate source and generated policy stages.
rg -n 'se_policy_conf|se_policy_cil|se_policy_binary|se_versioned_policy' \
system/sepolicy/Android.bp system/sepolicy/build/soong
# Inspect generated policy.conf and CIL outputs.
find out/soong/.intermediates/system/sepolicy \
-name '*.conf' -o -name '*.cil' -o -name '*_policy'
# Query final policy rules for a source domain.
sesearch -A -s my_daemon -c file
sesearch -A -s my_daemon -c binder
# Inspect kernel policy loading and runtime AVC errors.
adb shell 'dmesg | grep -E "SELinux|avc: denied|Could not load policy"'命令按阶段回答“源码进了哪个 module”“中间产物是否生成”“最终规则是否存在”和“设备是否实际拒绝”。它们不能证明所有条件分支都被覆盖,也不能把 permissive 下的日志当作 enforcing 行为证明。
源码树的 README 还给出一个更稳定的构建产物入口:out/target/product/<device>/obj/ETC/vendor_sepolicy.conf_intermediates/vendor_sepolicy.conf。先查看这个 conf,再沿同一 product 目录查找 vendor_sepolicy.unversioned.cil、vendor_sepolicy.cil 和最终 binary,可以把“输入没被收集”“M4/checkpolicy 失败”“版本化失败”区分开,而不是只看最后一条 secilc 错误。
7.3 反向测试
源码文件:system/sepolicy/tests/treble_sepolicy_tests.py
def TestNoUnmappedNewTypes(base_pub_policy, old_pub_policy, mapping):
# A newly exported public type must be represented in the compatibility map.
newt = base_pub_policy.types - old_pub_policy.types
violators = []
for n in newt:
if mapping.rTypeattributesets.get(n) is None:
violators.append(n)
if len(violators) > 0:
return "SELinux: public types without compatibility mapping: " + \
" ".join(str(x) for x in sorted(violators))
return ""
def TestNoUnmappedRmTypes(base_pub_policy, old_pub_policy, mapping):
# A removed old public type still needs a declaration for old vendor policy.
rmt = old_pub_policy.types - base_pub_policy.types
violators = [o for o in rmt
if o in mapping.pubtypes and o not in mapping.types]
return "SELinux: removed public types without mapping: " + \
" ".join(str(x) for x in sorted(violators)) if violators else ""
def TestTrebleCompatMapping(base_pub_policy, old_pub_policy, mapping):
return (TestNoUnmappedNewTypes(base_pub_policy, old_pub_policy, mapping) +
TestNoUnmappedRmTypes(base_pub_policy, old_pub_policy, mapping))输入是当前 public CIL、旧 public CIL 和 mapping CIL;第一个断言覆盖“新增 public type 必须有映射”,第二个覆盖“删除的旧 public type 若仍是旧接口,也必须有声明”。do_main 只有在测试返回非空字符串时才以非零状态退出,因此它能阻止兼容映射缺失,却不证明 private allow、secilc 合并或设备启动一定成功。
8. 源码导航
system/sepolicy/build/soong/build_files.go:输入目录和 scope tag。system/sepolicy/build/soong/policy.go:policy.conf、M4、checkpolicy、secilc 和 binary module。system/sepolicy/build/soong/versioned_policy.go:mapping、target policy 和 dependent CIL。system/sepolicy/build/build_sepolicy.py:兼容/legacy 构建工具的 build_cil 和 filter_out。system/sepolicy/Android.bp:platform、vendor、recovery 和 base policy module 图。system/core/init/selinux.cpp:预编译命中、分区 CIL 合并和 kernel load。system/sepolicy/tests/treble_sepolicy_tests.py:public API 新增/删除映射检查。
用一个新增 my_daemon type 做练习:先确认它被 .plat_private、.vendor 还是 .plat_public 收集;再检查 policy.conf 是否按 attributes|*.te 顺序出现;随后查看 raw CIL、filter/version 输出和最终 binary。若删掉 my_daemon_exec、把 allow 放进错误分区、漏掉 mapping 或只修改 vendor CIL,应能分别预测 transition 失败、undefined type、兼容测试失败和 secilc merge 失败。
