Skip to content

policy.conf 生成

从 Soong 输入 scope、文件排序和 M4 参数入手,解释 Android 17 各分区 policy.conf 如何生成及如何定位顺序与条件分支问题。

基于android-17.0.0_r1
AndroidSELinuxsepolicypolicy.confM4源码阅读

policy.conf 生成 ​

本文承接 Public Private Policy 对策略可见性的讨论,也会使用 Custom Daemon Domain 中的 domain/exec 示例作为输入背景。本文只研究 se_policy_conf 如何把多个源文件变成 policy.conf:谁收集文件、谁决定 scope、谁排序、谁把构建配置传给 M4,以及如何判断某条规则为什么出现在某个 .conf 中。checkpolicy、CIL 版本化和 init 加载属于相邻专题;这里把它们作为消费者和边界说明,不重复实现细节。

把 policy.conf 理解成“所有策略文件简单拼接”会导致两类误判:第一,vendor_sepolicy.conf 并不包含 platform private;第二,输入文件的文件系统顺序也不是策略语法顺序。Android 17 的 Soong 代码先用带 tag 的依赖选择输入,再用 policyConfOrder 稳定排序,最后把构建变体、Treble、recovery、board API level 和 build flags 转成 M4 定义。读完本文,读者应能从一个生成的 .conf 反查到它的输入 module,并解释 user、userdebug、recovery、CTS 为什么会得到不同文本。

1. 产物边界 ​

1.1 文本的位置 ​

policy.conf 是 M4 的输出,也是 se_policy_cil 调用 checkpolicy -C 的输入。它仍是文本,不是内核能加载的二进制;其中的宏调用已经展开,但 CIL、版本映射和跨分区合并尚未完成。

阶段主要 owner输入输出下游消费者
文件收集se_build_filespublic/、private/、vendor/ODM 目录带 tag 的路径集合se_policy_conf
文本生成policyConf.transformPolicyToConf排序后的路径、M4 定义*.confse_policy_cil
语法编译policyCil.compileConfToCil*.confCILse_versioned_policy 或 se_policy_binary

installable: false 只影响安装位置,不会切断依赖。比如 plat_sepolicy.conf 不安装到镜像,却被 plat_sepolicy.cil 消费;vendor_sepolicy.conf 也不直接安装,而是继续生成未版本化 CIL。

1.2 依赖图 ​

下面这张图回答“一个 .te 文件怎样到达 policy.conf,又在哪个阶段离开文本流水线”。同色节点表示相同职责:紫色是输入 scope,绿色是策略文本,红色是编译消费者。

图中的 scope tags 不是 SELinux 上下文,也不是 CIL attribute;它们是 Soong file-like module 的输出标签。只有被 Android.bp 以 :se_build_files{.<tag>} 引用的路径,才会进入具体 .conf。

2. 输入收集 ​

2.1 文件模式 ​

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

make
se_build_files {
    name: "se_build_files",
    // Patterns are collected first; M4 and checkpolicy run later.
    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",
    ],
}

srcs 描述的是每个策略目录内要查找的文件名模式,而不是一个已经展开的文件列表。公共宏、类型声明、角色和 contexts 文件因此会以相同机制进入收集器;真正的目录由 build_files.go 根据 scope 决定。*.te 是通配模式,attributes 则在排序阶段拥有特殊位置。

2.2 scope标签 ​

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

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[".system_ext_public"] = b.findSrcsInDirs(ctx,
        ctx.DeviceConfig().SystemExtPublicSepolicyDirs()...)
    b.srcs[".system_ext_private"] = b.findSrcsInDirs(ctx,
        ctx.DeviceConfig().SystemExtPrivateSepolicyDirs()...)
    b.srcs[".product_public"] = b.findSrcsInDirs(ctx,
        ctx.Config().ProductPublicSepolicyDirs()...)
    b.srcs[".product_private"] = b.findSrcsInDirs(ctx,
        ctx.Config().ProductPrivateSepolicyDirs()...)
    b.srcs[".vendor"] = b.findSrcsInDirs(ctx,
        ctx.DeviceConfig().VendorSepolicyDirs()...)
    b.srcs[".odm"] = b.findSrcsInDirs(ctx,
        ctx.DeviceConfig().OdmSepolicyDirs()...)
    // Export each scope through :se_build_files{.<tag>}.
    b.setOutputFiles(ctx)
}

findSrcsInDirs 内部使用 GlobWithDeps,因此目录中的新增文件会成为 Soong 依赖;setOutputFiles 把 map 中的 key 变成带 tag 的输出。这里的 owner 是 buildFiles,消费者是 Blueprint 中的策略 module。它没有执行 M4,也没有决定文件之间的语法顺序。

2.3 目录优先级 ​

设备侧路径由配置变量提供。源码 README 明确列出 BOARD_VENDOR_SEPOLICY_DIRS、SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS、SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS、PRODUCT_PUBLIC_SEPOLICY_DIRS、PRODUCT_PRIVATE_SEPOLICY_DIRS 和 BOARD_ODM_SEPOLICY_DIRS。这些目录会进入对应 tag;同一文件名在多个 vendor 目录中出现时,目录列表顺序会影响收集结果。

源码文件:system/sepolicy/README.md

makefile
# BoardConfig.mk supplies additional vendor-owned policy directories.
BOARD_VENDOR_SEPOLICY_DIRS += device/samsung/tuna/sepolicy

# Public/private policy can be extended per system_ext or product partition.
SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS += device/acme/roadrunner-sepolicy/systemext/public
SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS += device/acme/roadrunner-sepolicy/systemext/private
PRODUCT_PUBLIC_SEPOLICY_DIRS += device/acme/roadrunner-sepolicy/product/public
PRODUCT_PRIVATE_SEPOLICY_DIRS += device/acme/roadrunner-sepolicy/product/private
BOARD_ODM_SEPOLICY_DIRS += device/acme/roadrunner-sepolicy/odm

这些变量决定“文件能否被收集”,不决定它最终进入哪个 .conf;后者由 Android.bp 的 srcs 组合决定。例如 .system_ext_private 只会被 system_ext_sepolicy.conf 等需要 system_ext 私有实现的 module 引用,不会因为目录存在就自动出现在 vendor policy。

3. 模块组合 ​

3.1 platform与导出集 ​

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

make
// Public policy is compiled with reqd_mask so incomplete exports parse.
se_policy_conf {
    name: "pub_policy.conf",
    defaults: ["se_policy_conf_flags_defaults"],
    srcs: plat_public_policy +
        system_ext_public_policy +
        product_public_policy +
        reqd_mask_policy,
    vendor: true,
    installable: false,
}

se_policy_conf {
    name: "plat_sepolicy.conf",
    defaults: ["se_policy_conf_flags_defaults"],
    srcs: plat_public_policy +
        plat_private_policy,
    installable: false,
}

se_policy_conf {
    name: "system_ext_sepolicy.conf",
    defaults: ["se_policy_conf_flags_defaults"],
    srcs: plat_public_policy +
        plat_private_policy +
        system_ext_public_policy +
        system_ext_private_policy,
    system_ext_specific: true,
    installable: false,
}

这里有三个不同语义的文本集合:pub_policy.conf 为非 platform policy 提供可导出的 public 输入;plat_sepolicy.conf 是平台当前版本的 public + private 完整策略;system_ext_sepolicy.conf 在平台策略之上再加入 system_ext 的 public + private。vendor: true、system_ext_specific: true 是安装/变体属性,不会自行改变 srcs 的内容。

3.2 vendor与odm ​

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

make
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_conf {
    name: "odm_sepolicy.conf",
    defaults: ["se_policy_conf_flags_defaults"],
    srcs: plat_public_policy +
        system_ext_public_policy +
        product_public_policy +
        reqd_mask_policy +
        vendor_policy +
        [":se_build_files{.odm}"],
    device_specific: true,
    installable: false,
}

vendor 和 odm 的 .conf 都使用 platform、system_ext、product 的 public 输入以及 reqd mask;odm 另外加入 .odm,并且能看到 vendor policy。两者都不包含 platform private、system_ext private 或 product private。这个边界是 Treble 接口的构建实现,而不是某个 M4 宏的约定。

3.3 recovery与CTS ​

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

make
se_policy_conf {
    name: "recovery_sepolicy.conf",
    defaults: ["se_policy_conf_flags_defaults"],
    srcs: plat_public_policy +
        plat_private_policy +
        system_ext_public_policy +
        system_ext_private_policy +
        product_public_policy +
        product_private_policy +
        vendor_policy +
        [":se_build_files{.odm}"],
    target_recovery: true,
    installable: false,
    recovery: true,
}

se_policy_conf {
    name: "general_sepolicy.conf",
    defaults: ["se_policy_conf_flags_defaults"],
    srcs: plat_public_policy +
        plat_private_policy,
    build_variant: "user",
    cts: true,
    exclude_build_test: true,
    only_neverallow_rules: true,
    installable: false,
}

recovery 的输入最宽,因为它要覆盖刷写和恢复环境的实现;general_sepolicy.conf 却不是设备运行策略,它只保留 neverallow 检查需要的文本。target_recovery、cts 和 only_neverallow_rules 会改变 M4 定义与后处理,不能把这两个 module 当作普通 platform policy 的别名。

这张图强调“同一源目录可被多个 .conf 消费,但每个 module 的组合不同”。例如 plat_private 能进入 recovery,却不会进入 vendor;reqd_mask 能进入 public/vendor/odm 的解析输入,之后会在 CIL 导出阶段被过滤。

4. 排序规则 ​

4.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",
}

这不是普通字典序。security_classes 和 access_vectors 必须先于依赖它们的规则;宏文件必须先于宏调用;attributes 和所有 .te 放在同一排序槽;contexts 文件则在策略主体之后。新增一个不在列表中的文件不会自动获得语义优先级,而是落到列表末端。

4.2 稳定排序 ​

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

go
func findPolicyConfOrder(name string) int {
    for idx, pattern := range policyConfOrder {
        // The combined slot covers both attributes and every .te file.
        if pattern == "attributes|*.te" &&
            (name == "attributes" || strings.HasSuffix(name, ".te")) {
            return idx
        } else if pattern == name {
            return idx
        }
    }
    // Unknown names are valid inputs but are placed after known slots.
    return len(policyConfOrder)
}

sort.SliceStable(srcs, func(x, y int) bool {
    return findPolicyConfOrder(srcs[x].Base()) <
        findPolicyConfOrder(srcs[y].Base())
})

sort.SliceStable 只比较排序槽编号;同一槽内保持 Soong 收集到的相对顺序。于是两个 vendor 目录中的 foo.te 不会因为排序被重新按字典序打散,但它们仍可能因为目录列表顺序而改变最终文本。排查顺序问题时要同时查看 policyConfOrder 和设备目录列表,不能只在生成文件里搜索目标规则。

4.3 换行边界 ​

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

go
// Policy files may not all end with a new line. Add a separator between inputs.
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)
}

M4 接收的是 src, newline, src, newline...,不是直接把所有路径拼在一起。这样即使某个设备策略文件没有以 0x0A 结尾,也不会把下一文件的第一行粘到上一文件最后一个 token 上。README 仍要求设备策略文件自身以换行结束,因为这也会让中间产物更容易阅读和复现。

5. M4参数 ​

5.1 模块属性 ​

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

go
func (c *policyConf) buildVariant(ctx android.ModuleContext) string {
    if variant := proptools.String(c.properties.Build_variant); variant != "" {
        return variant
    }
    if ctx.Config().Eng() {
        return "eng"
    }
    if ctx.Config().Debuggable() {
        return "userdebug"
    }
    return "user"
}

func (c *policyConf) sepolicySplit(ctx android.ModuleContext) string {
    if c.cts() {
        return "cts"
    }
    if c.isTargetRecovery() {
        return "false"
    }
    return strconv.FormatBool(true)
}

func (c *policyConf) compatibleProperty(ctx android.ModuleContext) string {
    if c.cts() {
        return "cts"
    }
    if c.isTargetRecovery() {
        return "false"
    }
    return "true"
}

build_variant 优先使用 module 显式属性,所以 general_sepolicy.conf 固定为 user;没有显式属性的 module 才依据 Soong 配置推导 eng/userdebug/user。target_full_treble 和 target_compatible_property 又分别对 CTS、recovery 做特殊取值,M4 宏可以据此保留标记段或关闭某些规则。

5.2 命令构造 ​

源码文件: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_with_asan=", c.withAsan(ctx)).
    FlagWithArg("-D target_with_dexpreopt=", strconv.FormatBool(ctx.DeviceConfig().WithDexpreopt())).
    FlagWithArg("-D target_with_native_coverage=", strconv.FormatBool(ctx.DeviceConfig().ClangCoverageEnabled() || ctx.DeviceConfig().GcovCoverageEnabled())).
    FlagWithArg("-D target_build_variant=", c.buildVariant(ctx)).
    FlagWithArg("-D target_full_treble=", c.sepolicySplit(ctx)).
    FlagWithArg("-D target_compatible_property=", c.compatibleProperty(ctx)).
    FlagWithArg("-D target_recovery=", strconv.FormatBool(c.isTargetRecovery())).
    Flag(boardApiLevelToM4Macro(ctx, c.properties.Board_api_level)).
    Flags(flagsToM4Macros(c.getBuildFlags(ctx))).
    Flag("-s").
    Inputs(srcsWithNewline).
    Text("> ").Output(conf)

这些参数分成三层:设备配置(架构、vendor API level、是否 split)、module 属性(recovery、CTS、显式 build variant)和 se_flags 导出的 build flags。--fatal-warnings 让 M4 warning 成为失败;-s 抑制不必要的空白输出。M4 的输入消费者是 policy.conf,不是最终 CIL,因此可以在文本阶段直接检查宏是否展开。

5.3 自定义定义 ​

源码文件:system/sepolicy/build/soong/flags.go、system/sepolicy/build/soong/selinux.go

go
func flagsToM4Macros(flags map[string]string) []string {
    flagMacros := []string{}
    for _, flag := range android.SortedKeys(flags) {
        // Sorted keys make generated commands reproducible.
        flagMacros = append(flagMacros,
            "-D target_flag_"+flag+"="+flags[flag])
    }
    return flagMacros
}

func boardApiLevelToM4Macro(ctx android.ModuleContext, apiLevel *string) string {
    level := proptools.StringDefault(apiLevel, "current")
    if level == "current" {
        level = ctx.Config().VendorApiLevel()
    }
    return "-D target_board_api_level=" + level
}

BOARD_SEPOLICY_M4DEFS 通过 SepolicyM4Defs() 直接变成 -D name=value;se_flags 则先由 se_flags_collector 读取构建 flag,再统一前缀为 target_flag_...。前者适合设备/BoardConfig 的静态定义,后者适合 release flag 守卫;二者最终都只是在 M4 层提供名字和值,不会修改源文件。

6. 条件展开 ​

6.1 宏与输入 ​

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

yaml
#####################################
# Recovery only
# SELinux rules which apply only to recovery mode
define(`recovery_only', ifelse(target_recovery, `true', $1, ))

#####################################
# Full TREBLE only
define(`full_treble_only', ifelse(target_full_treble, `true', $1,
ifelse(target_full_treble, `cts',
# BEGIN_TREBLE_ONLY -- this marker is used by CTS -- do not modify
$1
# END_TREBLE_ONLY -- this marker is used by CTS -- do not modify
, )))

#####################################
# Userdebug or eng builds
define(`userdebug_or_eng', ifelse(target_build_variant, `eng', $1,
ifelse(target_build_variant, `userdebug', $1,
# SUPPRESSED_BY_USERDEBUG_OR_ENG -- this marker is used by CTS -- do not modify
)))

例如 .te 中调用 recovery_only(...) 时,是否生成规则由 target_recovery 决定,而不是由运行时属性决定;userdebug_or_eng 在 policy.conf 阶段就把 user build 的内容删除。full_treble_only 的 cts 分支保留带 marker 的文本,供 CTS 专门检查 neverallow 片段。这些宏的 owner 是 M4 输入文件,生效时机是文本生成,不是设备启动。

6.2 变量组合 ​

同一份 foo.te 可以在不同 .conf 中得到不同结果:recovery_sepolicy.conf 将 target_recovery=true,general_sepolicy.conf 将 target_build_variant=user 且 target_full_treble=cts,普通 platform module 则通常使用实际 product 的 build variant。调试时应先确认生成它的 module 和参数,再解释文本差异;只比较源文件会漏掉条件展开。

7. 输出与排查 ​

7.1 输出路径 ​

transformPolicyToConf 使用 pathForModuleOut 生成 module 输出。platform 非 recovery 内容使用通用 Soong 中间目录;system_ext、product、vendor、odm 和 recovery 会把 device name 纳入中间路径,避免切换 target 时错误复用设备相关产物。安装属性为 true 的 se_policy_conf 才会把该文件复制到 etc,多数策略 .conf 通过 installable: false 只作为中间输入。

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

go
func pathForModuleOut(ctx android.ModuleContext, paths ...string) android.OutputPath {
    // Core platform policy is device-independent; other variants include DeviceName.
    if ctx.Platform() && !ctx.InstallInRecovery() {
        return android.PathForModuleOut(ctx, paths...).OutputPath
    }
    return android.PathForModuleOut(ctx, ctx.Config().DeviceName()).Join(ctx, paths...)
}

README 给出的 vendor 文本入口是 out/target/product/<device>/obj/ETC/vendor_sepolicy.conf_intermediates/vendor_sepolicy.conf。从这个文件开始检查,能够确认目录收集、排序和 M4 已经完成;如果它缺少某个 domain,应回到 se_build_files tag 和 vendor_sepolicy.conf 的 srcs,而不是先修改 allow 规则。

7.2 失败定位 ​

现象首个检查点原因边界
M4 warning 直接失败--fatal-warnings 与 BOARD_SEPOLICY_M4DEFS定义拼写、宏参数或未预期展开
undefined type/class生成的 .conf 中声明是否出现scope 选错、排序槽未知或文件未被 glob
规则顺序变化policyConfOrder 与 vendor 目录列表稳定排序只保持同槽相对顺序
recovery 规则出现在普通策略target_recovery 是否为 truemodule 属性决定 M4,不是运行时模式
userdebug 规则出现在 usertarget_build_variant 与 userdebug_or_eng生成了错误 variant 的 .conf
只在 CTS 文本中看到规则target_full_treble=cts 和 markerCTS 专用输入,不等于设备运行策略

7.3 可复现命令 ​

sh
# Read-only: locate the module declarations and their source combinations.
rg -n 'se_policy_conf|plat_.*policy|vendor_sepolicy|recovery_sepolicy' \
  system/sepolicy/Android.bp

# Read-only: inspect the ordering and M4 parameter construction.
rg -n 'policyConfOrder|sort\.SliceStable|target_recovery|target_build_variant|fatal-warnings' \
  system/sepolicy/build/soong/policy.go

# Read-only: inspect generated policy.conf files for a selected product.
find out/target/product/<device>/obj/ETC \
  -path '*sepolicy.conf_intermediates/*.conf' -print

# Read-only: compare a domain declaration and its expanded rule context.
rg -n 'my_daemon|my_daemon_exec|userdebug_or_eng|recovery_only' \
  out/target/product/<device>/obj/ETC/*sepolicy.conf_intermediates/*.conf

这些命令的输出分别回答“哪个 module 负责组合”“哪些参数进入 M4”“中间文件实际生成在哪里”“目标 domain 是否被展开”。find 找不到文件只能说明构建产物不存在或路径不同,不能直接推出策略语法错误;需要继续查看 Soong 的 module action 日志。

8. 关系验证 ​

8.1 反向约束 ​

general_sepolicy.conf 通过 only_neverallow_rules: true 进入 sepolicy_filter_neverallow,它验证的是 M4 输出中 neverallow 约束能否作为独立检查输入。这个 module 的存在说明 policy.conf 不只有“给设备运行”的一种消费者:相同的源 scope 可以为 CTS 生成专用文本。

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

go
if proptools.Bool(c.properties.Only_neverallow_rules) {
    // Filter the generated conf in place for the CTS neverallow input.
    rule.Command().BuiltTool("sepolicy_filter_neverallow").
        Text(conf.String()). // input
        Text(conf.String())   // output
}

8.2 文件内容断言 ​

源码文件:system/sepolicy/tools/sepolicy_filter_neverallow.py

python
def markers_match(marker1: str, marker2: str) -> bool:
    """Checks whether two marker lines delimit the same block."""
    combined = f"{marker1}\n{marker2}"
    pattern1 = r"^\s*# BEGIN_([A-Za-z0-9_]+) .*\n\s*# END_\1\b .*$"
    pattern2 = r"^\s*# END_([A-Za-z0-9_]+) .*\n\s*# BEGIN_\1\b .*$"
    return bool(re.match(pattern1, combined) or re.match(pattern2, combined))

in_neverallow_rule = False
outputs = []
for line in blob.strip().split('\n'):
    # CTS markers are retained even though ordinary comments are removed.
    if re.search(r"#.*-- this marker is used by CTS --", line):
        if len(outputs) > 0 and markers_match(outputs[-1], line):
            outputs.pop()
        else:
            outputs.append(line.rstrip())
        continue

    line = re.sub(r"#.*", "", line)
    if line.strip() == "":
        continue
    if line.lstrip().startswith("neverallow"):
        in_neverallow_rule = True
    if in_neverallow_rule:
        outputs.append(line.rstrip())
        if line.endswith(';'):
            in_neverallow_rule = False

脚本的输入和输出都是 *.conf 文本:它保留 CTS marker 和完整的 neverallow 语句,删除普通注释与无关策略。相邻的 BEGIN/END marker 如果中间没有语句,会被成对删除。这个消费者只服务 general_sepolicy.conf 的 CTS neverallow 检查,不会生成设备运行策略,也不会替代 checkpolicy 的完整语法检查。

9. 源码导航 ​

  1. system/sepolicy/build/soong/build_files.go:scope tag、glob 依赖和输出暴露。
  2. system/sepolicy/Android.bp:各分区 .conf 的 srcs 组合和变体属性。
  3. system/sepolicy/build/soong/policy.go:排序、换行、M4 定义和 CTS neverallow 过滤。
  4. system/sepolicy/build/soong/flags.go:build flag 收集与 target_flag_ 宏生成。
  5. system/sepolicy/build/soong/selinux.go:board API level 和 module 输出路径。
  6. system/sepolicy/public/te_macros:target_recovery、target_full_treble 和 build variant 的条件宏。
  7. system/sepolicy/README.md:设备策略目录变量、换行要求和中间产物定位。

可以用一个真实的 my_daemon.te 做复盘:先判断它被 .plat_private、.plat_public 还是 .vendor 收集;再列出它会进入的 .conf;随后根据 policyConfOrder 定位它与 attributes、te_macros 的相对位置;最后分别预测 user、userdebug、recovery 和 CTS 文本中哪些宏调用会留下内容。若生成的 vendor .conf 出现 platform private 类型,优先检查 Android.bp 的 srcs 组合;若规则只在 recovery 文本出现,检查的应是 target_recovery,而不是运行时 AVC。