版本化策略
本文承接 CIL 中间语言 对 CIL 节点和 secilc 输入的介绍,也会回到 Public Private Policy 解释为什么只有 public policy 能成为跨分区接口。本文只解决一个问题:当 system、system_ext、product 与 vendor 的策略版本不同时,Android 17 如何生成 mapping、如何给 vendor/odm policy 做 attributize,以及 init 如何选择版本文件。policy.conf 的 M4 排序属于上一篇,不在这里重复。
版本化策略维护两种关系:旧 public type/attribute 到当前 platform 的兼容名字,以及非 platform policy 应该针对哪个 platform API。前者由 mapping CIL 表达,后者由 target_policy attributize;两者都由 version_policy 读取 CIL AST 后生成。
1. 兼容问题
1.1 版本变量
源码文件:build/make/core/config.mk
# PLATFORM_SEPOLICY_VERSION follows the board API level (YYYYMM).
PLATFORM_SEPOLICY_VERSION := $(BOARD_API_LEVEL)
BOARD_SEPOLICY_VERS := $(PLATFORM_SEPOLICY_VERSION)
.KATI_READONLY := PLATFORM_SEPOLICY_VERSION BOARD_SEPOLICY_VERS
# Older versions remain supported by compatibility artifacts.
PLATFORM_SEPOLICY_COMPAT_VERSIONS := \
31.0 \
32.0 \
33.0 \
34.0 \
PLATFORM_SEPOLICY_COMPAT_VERSIONS += $(foreach ver,\
202404 \
202504 \
202604 \
,$(if $(filter true,$(call math_gt,$(PLATFORM_SEPOLICY_VERSION),$(ver))),$(ver)))平台版本来自 BOARD_API_LEVEL;vendor 版本默认跟随平台,但产品可以给 BOARD_SEPOLICY_VERS 更旧的目标。兼容版本列表只决定历史产物是否参与构建,不表示所有历史策略会同时加载。
1.2 版本文件
源码文件:system/sepolicy/build/soong/sepolicy_vers.go
type sepolicyVersProperties struct {
// "platform" reads PLATFORM_SEPOLICY_VERSION; "vendor" reads BOARD_SEPOLICY_VERS.
Version *string
Stem *string
Installable *bool
}
func (v *sepolicyVers) GenerateAndroidBuildActions(ctx android.ModuleContext) {
var ver string
switch proptools.String(v.properties.Version) {
case "platform":
ver = ctx.DeviceConfig().PlatformSepolicyVersion()
case "vendor":
ver = ctx.DeviceConfig().BoardSepolicyVers()
default:
ctx.PropertyErrorf("version", "should be either platform or vendor")
}
out := android.PathForModuleGen(ctx, v.stem())
// The generated text is installed under <partition>/etc/selinux.
rule := android.NewRuleBuilder(pctx, ctx)
rule.Command().Text("echo").Text(ver).Text("> ").Output(out)
rule.Build("sepolicy_vers", v.Name())
}owner 是 sepolicy_vers Soong module,消费者是 init 的 GetVendorMappingVersion。版本属性错误会在构建阶段停止;文件生成成功只说明字符串可安装,不证明对应 mapping CIL 存在。
1.3 依赖图
mapping 与 attributized target 是两个输出,不是同一个文件的别名。mapping 面向旧接口,target CIL 面向 vendor/odm 的具体规则;二者都必须和当前 platform CIL 一起交给 secilc。
2. mapping生成
2.1 Soong属性
源码文件:system/sepolicy/build/soong/versioned_policy.go
type versionedPolicyProperties struct {
// Base public CIL defines the compatibility API surface.
Base *string
Version *string
Mapping *bool
Target_policy *string
Filter_out []string
Dependent_cils []string
}
func (m *versionedPolicy) GenerateAndroidBuildActions(ctx android.ModuleContext) {
version := proptools.StringDefault(m.properties.Version, "current")
if version == "current" {
version = ctx.DeviceConfig().PlatformSepolicyVersion()
} else if version == "vendor" {
version = ctx.DeviceConfig().BoardSepolicyVers()
}
// Mapping and target_policy are mutually exclusive.
if proptools.Bool(m.properties.Mapping) && proptools.String(m.properties.Target_policy) != "" {
ctx.ModuleErrorf("Can't set both mapping and target_policy")
return
}Base 必须是 public CIL,因为它定义可兼容接口;Version 决定版本 attribute 的后缀;Dependent_cils 不改变 mapping 内容,只在输出后触发合并检查。
2.2 mapping模式
源码文件:system/sepolicy/build/soong/versioned_policy.go
if proptools.Bool(m.properties.Mapping) {
// -m creates mapping attributes from the base public policy.
rule.Command().BuiltTool("version_policy").
FlagWithInput("-b ", android.PathForModuleSrc(ctx, *m.properties.Base)).
FlagWithArg("-n ", version).
FlagWithOutput("-o ", out).
Flag("-m")
} else {
ctx.ModuleErrorf("Either mapping or target_policy must be set")
return
}plat_mapping_file 的 base 是 plat_pub_policy.cil,输出安装到 /system/etc/selinux/mapping。mapping 只覆盖 public base;platform private type 不会因为存在于完整 platform policy 就自动导出。
2.3 分区映射
源码文件:system/sepolicy/Android.bp
se_versioned_policy {
name: "plat_mapping_file",
base: ":plat_pub_policy.cil",
mapping: true,
version: "current",
relative_install_path: "mapping",
}
se_versioned_policy {
name: "system_ext_mapping_file",
base: ":system_ext_pub_policy.cil",
mapping: true,
version: "current",
filter_out: [":plat_mapping_file"],
relative_install_path: "mapping",
system_ext_specific: true,
}
se_versioned_policy {
name: "product_mapping_file",
base: ":pub_policy.cil",
mapping: true,
version: "current",
filter_out: [":plat_mapping_file", ":system_ext_mapping_file"],
relative_install_path: "mapping",
product_specific: true,
}system_ext 过滤 platform mapping,product 再过滤前两者,避免同一 public type 的多个版本 attribute 重复安装。filter_out 是生成后的 CIL 处理,不是对源目录做排除。
3. target策略
3.1 attributize流程
源码文件:system/sepolicy/build/soong/versioned_policy.go
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)
// Drop mapping declarations before concatenating the two outputs.
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)target 模式产生 mapping、attributized target 和最终拼接文件三个状态。先生成 mapping,再给 target policy 做 attributize,过滤重复声明后按 mapping→target 拼接;任一步失败都不会生成最终 output。
3.2 工具实现
源码文件:system/sepolicy/tools/version_policy.c
if (mapping) {
rc = cil_android_attrib_mapping(&out_db, base_db, num);
if (rc != SEPOL_OK)
goto exit;
} else {
// Target CIL is parsed before it is associated with the base API.
rc = read_cil_file(&out_db, tgt_policy);
if (rc != SEPOL_OK) {
goto exit;
}
rc = cil_android_attributize(out_db, base_db, num);
if (rc != SEPOL_OK) {
goto exit;
}
}
rc = cil_write_build_ast(output_file, out_db);base_db 与 out_db 是两个 CIL database;mapping 模式只读取 base,attributize 模式同时需要 base 和 target。输出是重新序列化的 CIL AST,不是二进制。统一的 goto exit 会释放参数字符串和两个 database,解析失败不会把半成品当作有效 mapping。
3.3 vendor与odm
源码文件:system/sepolicy/Android.bp
se_versioned_policy {
name: "vendor_sepolicy.cil",
base: ":pub_policy.cil",
target_policy: ":vendor_sepolicy.unversioned.cil",
version: "vendor",
dependent_cils: [
":plat_sepolicy.cil", ":system_ext_sepolicy.cil",
":product_sepolicy.cil", ":plat_pub_versioned.cil",
":plat_mapping_file",
],
filter_out: [":plat_pub_versioned.cil"],
vendor: true,
}
se_versioned_policy {
name: "odm_sepolicy.cil",
base: ":pub_policy.cil",
target_policy: ":odm_sepolicy.unversioned.cil",
version: "vendor",
dependent_cils: [
":plat_sepolicy.cil", ":system_ext_sepolicy.cil",
":product_sepolicy.cil", ":plat_pub_versioned.cil",
":plat_mapping_file", ":vendor_sepolicy.cil",
],
filter_out: [":plat_pub_versioned.cil"],
device_specific: true,
}odm 额外依赖 vendor CIL;两者都用 pub_policy.cil 作为 base,确保非 platform policy 只依赖导出的接口。dependent_cils 的 secilc 检查把单文件合法性提升到跨分区合并合法性。
4. 兼容文件
4.1 compat收集
源码文件:system/sepolicy/build/soong/compat_cil.go
func (c *compatCil) shouldSkipBuild(ctx android.ModuleContext) bool {
// Current platform does not need a backwards-compatibility fragment.
return proptools.String(c.properties.Version) ==
ctx.DeviceConfig().PlatformSepolicyVersion()
}
func (c *compatCil) GenerateAndroidBuildActions(ctx android.ModuleContext) {
if c.ProductSpecific() || c.SocSpecific() || c.DeviceSpecific() {
ctx.ModuleErrorf("Compat cil files only support system and system_ext partitions")
}
if c.shouldSkipBuild(ctx) {
return
}
srcPaths := c.expandSeSources(ctx)
out := android.PathForModuleGen(ctx, c.Name())
ctx.Build(pctx, android.BuildParams{
Rule: android.CatRule,
Inputs: srcPaths,
Output: out,
Description: "Combining compat cil for " + c.Name(),
})
}se_compat_cil 只是收集并拼接历史 compat CIL,且只允许 system/system_ext;当前版本与 module 版本相同时直接跳过。它和 mapping 输出不同:compat 保存额外的向后兼容 allow/声明,mapping 保存 API 类型关系。
4.2 兼容输入
源码文件:system/sepolicy/build/soong/compat_cil.go
func (f *compatTestModule) createCompatTestModule(ctx android.LoadHookContext, ver string) {
srcs := []string{
":plat_sepolicy.cil", ":system_ext_sepolicy.cil",
":product_sepolicy.cil", fmt.Sprintf(":plat_%s.cil", ver),
fmt.Sprintf(":%s.compat.cil", ver),
fmt.Sprintf(":system_ext_%s.cil", ver),
fmt.Sprintf(":product_%s.cil", ver),
}
if ver == ctx.DeviceConfig().BoardSepolicyVers() {
srcs = append(srcs, ":plat_pub_versioned.cil",
":vendor_sepolicy.cil", ":odm_sepolicy.cil")
} else {
srcs = append(srcs, fmt.Sprintf(":%s_plat_pub_versioned.cil", ver))
}
compatTestName := fmt.Sprintf("%s_compat_test", ver)
ctx.CreateModule(policyBinaryFactory, &nameProperties{
Name: proptools.StringPtr(compatTestName),
}, &policyBinaryProperties{
Srcs: srcs,
Ignore_neverallow: proptools.BoolPtr(true),
Installable: proptools.BoolPtr(false),
})
}测试输入包含当前 platform/system_ext/product、历史 platform CIL、compat CIL,以及在目标版本等于 board 版本时的 vendor/odm CIL。Ignore_neverallow 让测试专注兼容合并,不能被理解为生产策略建议。
5. 失败定位
5.1 错误边界
| 阶段 | 常见错误 | 首个检查点 | 不能外推 |
|---|---|---|---|
| 版本字符串 | property error、空版本文件 | sepolicy_vers.go、plat_sepolicy_vers.txt | 不代表 mapping 算法失败 |
| mapping | base CIL 解析失败 | public base、version_policy -m | 不代表设备已启动 |
| attributize | target symbol 无法关联 | base 与 unversioned target | 不代表 secilc 已合并 |
| compat | 当前版本产物缺失或误生成 | shouldSkipBuild、compat srcs | 不代表运行时必然使用 |
| merge check | unknown type、duplicate、neverallow | dependent CIL、policy version | 不代表 init 已加载 |
| 运行时选择 | mapping 不存在或版本为空 | init GetVendorMappingVersion | 不能静默换用其他版本 |
5.2 命令
# Read-only: inspect version variables and versioned Soong modules.
rg -n 'PLATFORM_SEPOLICY_VERSION|BOARD_SEPOLICY_VERS|se_versioned_policy|sepolicy_vers' \
build/make/core/config.mk system/sepolicy/Android.bp \
system/sepolicy/build/soong
# Read-only: inspect mapping and compatibility declarations.
rg -n '^\(typeattributeset |^\(expandtypeattribute|^\(typeattribute ' \
system/sepolicy/prebuilts/api system/sepolicy/private/compat --glob '*.cil'
# Read-only: locate generated versioned outputs for one product.
find out/target/product/<device>/obj/ETC \
-path '*sepolicy*intermediates/*.cil' -print
# Read-only on a device: confirm the selector and load errors.
adb shell 'cat /vendor/etc/selinux/plat_sepolicy_vers.txt'
adb shell 'dmesg | grep -E "SELinux|sepolicy|mapping|avc: denied"'第一组命令确认版本 owner,第二组确认 mapping 是否声明了目标 API,第三组把 module 与产物对应,第四组确认设备选择的版本。文件存在不等于兼容成立,仍需看 dependent CIL 的 secilc 检查和实际加载路径。
6. 读码练习
给定一个新增 public type my_public_type,按以下路径复述:
- 它进入哪个 public base CIL,以及当前平台生成哪个版本 mapping?
- vendor 以
BOARD_SEPOLICY_VERS指向历史版本时,init 从哪里读取版本文件和 mapping? - vendor unversioned CIL 如何经
target_policy生成 attributized CIL,为什么要先过滤 mapping 再拼接? - 如果 vendor CIL 依赖 platform private type,错误会在
version_policy还是最终secilc暴露?依据是什么?
再阅读 private/compat/34.0/34.0.compat.cil 中的一个兼容 allow,说明它为何必须和历史 CIL、当前 platform CIL 一起进入 compat test,以及 Ignore_neverallow 对测试结论的限制。
7. 源码导航
build/make/core/config.mk:平台/供应商版本变量和兼容版本列表。system/sepolicy/build/soong/sepolicy_vers.go:生成plat_sepolicy_vers.txt的 Soong module。system/sepolicy/build/soong/versioned_policy.go:mapping、target attributize、过滤和 merge check。system/sepolicy/tools/version_policy.c:CIL database、mapping/attributize 分派和清理路径。system/sepolicy/build/soong/compat_cil.go:历史 compat CIL 收集与兼容测试输入。system/sepolicy/build/soong/cil_compat_map.go:跨多版本 mapping half 的组合。system/sepolicy/Android.bp:platform、system_ext、product、vendor、odm 的版本化 module 图。system/core/init/selinux.cpp:运行时读取 vendor 版本并选择 mapping 文件。
