Skip to content

版本化策略

追踪 Android 17 的 sepolicy 版本号、mapping CIL、target attributize 和跨分区兼容检查,解释平台与旧 vendor 如何共同编译。

基于android-17.0.0_r1
AndroidSELinuxTrebleCIL兼容性源码阅读

版本化策略 ​

本文承接 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

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

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

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

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

make
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

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

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

make
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

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

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 算法失败
mappingbase CIL 解析失败public base、version_policy -m不代表设备已启动
attributizetarget symbol 无法关联base 与 unversioned target不代表 secilc 已合并
compat当前版本产物缺失或误生成shouldSkipBuild、compat srcs不代表运行时必然使用
merge checkunknown type、duplicate、neverallowdependent CIL、policy version不代表 init 已加载
运行时选择mapping 不存在或版本为空init GetVendorMappingVersion不能静默换用其他版本

5.2 命令 ​

sh
# 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,按以下路径复述:

  1. 它进入哪个 public base CIL,以及当前平台生成哪个版本 mapping?
  2. vendor 以 BOARD_SEPOLICY_VERS 指向历史版本时,init 从哪里读取版本文件和 mapping?
  3. vendor unversioned CIL 如何经 target_policy 生成 attributized CIL,为什么要先过滤 mapping 再拼接?
  4. 如果 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. 源码导航 ​

  1. build/make/core/config.mk:平台/供应商版本变量和兼容版本列表。
  2. system/sepolicy/build/soong/sepolicy_vers.go:生成 plat_sepolicy_vers.txt 的 Soong module。
  3. system/sepolicy/build/soong/versioned_policy.go:mapping、target attributize、过滤和 merge check。
  4. system/sepolicy/tools/version_policy.c:CIL database、mapping/attributize 分派和清理路径。
  5. system/sepolicy/build/soong/compat_cil.go:历史 compat CIL 收集与兼容测试输入。
  6. system/sepolicy/build/soong/cil_compat_map.go:跨多版本 mapping half 的组合。
  7. system/sepolicy/Android.bp:platform、system_ext、product、vendor、odm 的版本化 module 图。
  8. system/core/init/selinux.cpp:运行时读取 vendor 版本并选择 mapping 文件。