Skip to content

Policy Compilation

追踪 Android 17 SELinux 从策略文件收集、M4 展开到 CIL、版本化、分区合并和内核二进制加载。

基于android-17.0.0_r1
AndroidSELinuxsepolicycheckpolicysecilc源码阅读

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 文件与消费者 ​

产物生成者消费者典型失败
*.confse_policy_conf + M4checkpolicy宏、顺序、条件错误
raw CILcheckpolicy -Cfilter/version toolpolicy 语义或类型错误
versioned CILversion_policyvendor/platform mergeAPI mapping 缺失
binary policyse_policy_binary/secilcinit/kernelCIL 合并、neverallow、policy version

1.2 主线图 ​

2. 源文件收集 ​

2.1 se_build_files ​

源码文件:system/sepolicy/Android.bp、system/sepolicy/build/soong/build_files.go

make
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 只声明收集器输入。

go
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

make
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

make
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

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

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

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

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

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

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

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

python
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

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

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

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

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

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

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

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

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 warningpolicyConfconf 生成日志
undefined type/classcheckpolicypolicy.conf 行号
neverallow violationcheckpolicy/secilc规则和冲突 type
mapping missingversion_policy/Treble testmapping CIL
CIL merge errorsecilcdependent CIL 列表
binary load errorinit/kernelkmsg/security_load_policy
runtime AVCkernelscontext/tcontext/tclass/permission

7.2 诊断命令 ​

sh
# 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

python
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. 源码导航 ​

  1. system/sepolicy/build/soong/build_files.go:输入目录和 scope tag。
  2. system/sepolicy/build/soong/policy.go:policy.conf、M4、checkpolicy、secilc 和 binary module。
  3. system/sepolicy/build/soong/versioned_policy.go:mapping、target policy 和 dependent CIL。
  4. system/sepolicy/build/build_sepolicy.py:兼容/legacy 构建工具的 build_cil 和 filter_out。
  5. system/sepolicy/Android.bp:platform、vendor、recovery 和 base policy module 图。
  6. system/core/init/selinux.cpp:预编译命中、分区 CIL 合并和 kernel load。
  7. 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 失败。