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_files | public/、private/、vendor/ODM 目录 | 带 tag 的路径集合 | se_policy_conf |
| 文本生成 | policyConf.transformPolicyToConf | 排序后的路径、M4 定义 | *.conf | se_policy_cil |
| 语法编译 | policyCil.compileConfToCil | *.conf | CIL | se_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
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
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
# 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
// 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
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
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
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
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
// 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
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
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
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
#####################################
# 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
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 是否为 true | module 属性决定 M4,不是运行时模式 |
| userdebug 规则出现在 user | target_build_variant 与 userdebug_or_eng | 生成了错误 variant 的 .conf |
| 只在 CTS 文本中看到规则 | target_full_treble=cts 和 marker | CTS 专用输入,不等于设备运行策略 |
7.3 可复现命令
# 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
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
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. 源码导航
system/sepolicy/build/soong/build_files.go:scope tag、glob 依赖和输出暴露。system/sepolicy/Android.bp:各分区.conf的srcs组合和变体属性。system/sepolicy/build/soong/policy.go:排序、换行、M4 定义和 CTS neverallow 过滤。system/sepolicy/build/soong/flags.go:build flag 收集与target_flag_宏生成。system/sepolicy/build/soong/selinux.go:board API level 和 module 输出路径。system/sepolicy/public/te_macros:target_recovery、target_full_treble和 build variant 的条件宏。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。
