Skip to content

Treble 与 sepolicy

从 public/private API、vendor 目标版本、mapping/compat、构建测试到 init 合并,解释 Treble 下 sepolicy 的跨镜像责任边界。

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

Treble 与 sepolicy ​

本文面向已经读过 Public Private Policy、版本化策略 和 内核加载流程 的读者。前三篇分别解释目录、版本化工具和启动加载;本文把它们放回 Treble 的系统升级问题:当新的 system 镜像遇到较旧 vendor 镜像时,谁可以引用哪些 type,谁维护 mapping/compat,构建测试验证什么,init 又如何选择输入。

Treble sepolicy 的核心不是把一个大文件机械拆成 platform/vendor 两份,而是建立一个稳定 API 和可组合产物协议。platform public 是非 platform policy 可以依赖的接口;platform private 是实现;vendor/odm policy 只针对声明的 board sepolicy version;mapping 把历史 API 名称连接到当前类型;compat 补充仅靠映射无法保存的行为。最终所有 CIL 仍由 secilc 编译成一个内核 policy。

1. 升级问题 ​

1.1 镜像组合 ​

考虑 system-only OTA:system、system_ext 或 product 发生升级,vendor 仍来自设备出厂版本。新 platform policy 可能新增、删除或重标记 public type;旧 vendor CIL 仍引用旧 API 名称。没有 mapping 和兼容规则时,结果可能是 secilc unknown type、服务无法访问新标签,或 platform 私有实现被错误暴露给 vendor。

1.2 责任表 ​

参与方拥有的状态对外承诺失败表现
platform publicvendor 可引用的 type/attributeAPI 稳定与历史 mappingTreble mapping test 失败
platform privateallow/neverallow 和内部 type不对 vendor 暴露vendor 引用时 undefined type
vendor/odm设备实现规则与 target version只依赖 public APIattributize/merge 失败
mapping/compat历史 API 与行为桥接对指定旧版本有效OTA 后旧 vendor 不兼容
init版本选择、文件组合和 secilc加载一个完整 binary启动阶段 FATAL

2. API边界 ​

2.1 private默认 ​

源码文件:system/sepolicy/docs/public_private_compat.md

text
Private by Default: All new types and attributes MUST be placed in private/
unless vendor access is strictly required.

Rule Restriction: All allow, neverallow, and dontaudit rules MUST be in
private/. The public/ directory should only contain type/attribute definitions
and necessary typeattribute statements.

public 是 API 声明面,不是“platform 规则公共库”。把 allow 放进 public 会让接口和实现混合,也会使 vendor-facing snapshot 难以冻结。新 type 默认进入 private,只有明确存在 vendor 消费者时才进入 public。

2.2 board API双定义 ​

同一文档还规定 trunk-stable 的 N 逻辑:准备在 board API N 对 vendor 暴露的新 type,需要 public 侧用 starting_at_board_api(N, ...),private 侧用 until_board_api(N, ...) 保存当前构建可见性。这让 platform 自己在 API 冻结前可使用 type,又不会提前把未冻结 API 暴露给旧 vendor。

源码文件:system/sepolicy/docs/public_private_compat.md

text
When a new type is intended for vendor use at Board API Level N:
- Define it in public/ guarded by starting_at_board_api(N, ...).
- Define the exact same declaration in private/ guarded by
  until_board_api(N, ...).

这里的消费者是 M4 构建变体和 board API level,而不是运行时 init。两个声明必须完全一致,否则同一 type 在 API 切换前后可能拥有不同 attributes。

2.3 public输入集合 ​

源码文件: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}"]

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,
}

pub_policy.conf 聚合 system、system_ext、product 的 public API,并临时加入 reqd mask 让 checkpolicy 能解析不完整导出集;mask 会在 CIL 阶段过滤。platform private 从未进入该导出集合。

3. Vendor视角 ​

3.1 输入限制 ​

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

make
vendor_policy = [
    ":se_build_files{.plat_vendor}",
    ":se_build_files{.vendor}",
    ":ashmem_build_file",
]

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,
}

vendor 编译输入只有 platform/system_ext/product public,再加 platform 提供的 vendor glue 与设备策略目录。若 vendor .te 引用 platform private type,vendor_sepolicy.conf 中不会有声明,错误会在 checkpolicy 或后续 secilc 暴露,而不是运行时悄悄放行。

3.2 target版本化 ​

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

make
se_policy_cil {
    name: "vendor_sepolicy.unversioned.cil",
    src: ":vendor_sepolicy.conf",
    filter_out: [":reqd_policy_mask.cil"],
    secilc_check: false,
    vendor: true,
    installable: false,
}

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,
}

unversioned CIL 不是可直接安装的最终 vendor policy。se_versioned_policy 以 pub_policy.cil 为 API base,根据 BOARD_SEPOLICY_VERS attributize target,并用 dependent CIL 做合并检查。这个模块把“vendor 能编译”提升为“vendor 能与当前 platform 组合”。

3.3 ODM依赖 ​

odm 在 vendor 之上继续追加设备细分策略,因此 odm_sepolicy.cil 的 dependent CIL 包含 vendor_sepolicy.cil。这形成 platform → vendor → odm 的可见性方向;odm 可以依赖 vendor 产物,vendor 不应反向依赖 odm。

4. Mapping与compat ​

4.1 新增与删除 ​

源码文件:system/sepolicy/tests/treble_sepolicy_tests.py

下面保留两个测试的真实集合计算和断言循环;失败后的长错误说明字符串不影响判定,已在摘录中明确省略。

python
def TestNoUnmappedNewTypes(base_pub_policy, old_pub_policy, mapping):
    newt = base_pub_policy.types - old_pub_policy.types
    ret = ""
    violators = []

    for n in newt:
        if mapping.rTypeattributesets.get(n) is None:
            violators.append(n)
    if len(violators) > 0:
        # The source appends the detailed mapping-file error and violator list.
        ret += " ".join(str(x) for x in sorted(violators)) + "\n\n"
    return ret

def TestNoUnmappedRmTypes(base_pub_policy, old_pub_policy, mapping):
    rmt = old_pub_policy.types - base_pub_policy.types
    ret = ""
    violators = []
    for o in rmt:
        if o in mapping.pubtypes and o not in mapping.types:
            violators.append(o)
    if len(violators) > 0:
        # The source appends the detailed compatibility error and violator list.
        ret += " ".join(str(x) for x in sorted(violators)) + "\n\n"
    return ret

第一个测试要求新增 public type 在 mapping 中出现;第二个要求被移除的旧 public type 仍有兼容声明。输入是当前 public CIL、旧 public CIL 和合并后的 mapping,不检查 platform private allow,也不执行设备启动。

4.2 ignore与compat ​

源码文件:system/sepolicy/private/compat/202504/202504.ignore.cil

text
(type new_objects)
(typeattribute new_objects)
(typeattributeset new_objects
  ( new_objects
    aisealhostservice
    crosvm_vu_fs
    hal_npu_scheduling_service
    native_serial_service
    system_server_wrapfd
  ))

该文件的真实列表更长,此处保留结构和部分成员。*.ignore.cil 将完全新增、没有旧版本等价物的 type 放入 new_objects,使兼容测试知道它们无需映射到旧对象。若新 type 是旧 generic label 的细分,则应在 <version>.cil 中建立映射,而不是简单忽略。

源码文件:system/sepolicy/private/compat/34.0/34.0.compat.cil

text
(type vendor_hidraw_device)
(typeattributeset dev_type (vendor_hidraw_device))

(allow system_server vendor_hidraw_device
    (dir (open getattr read search ioctl lock watch watch_reads)))
(allow system_server vendor_hidraw_device
    (chr_file (getattr open read ioctl lock map watch watch_reads append write)))

(allow domain ashmem_libcutils_device (chr_file (open)))

compat CIL 补充仅靠 type mapping 无法表达的旧行为。例如旧 vendor 仍使用旧 hidraw label 时,当前 system 为旧 label 复制必要访问;这不是把 platform private API 导出,而是为指定历史版本提供兼容行为。

4.3 mapping组合 ​

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

go
type cilCompatMapProperties struct {
    // Top half maps x->y; bottom half maps y->z; output maps x->z.
    Top_half *string `android:"path"`
    Bottom_half []string `android:"path"`
    Stem *string
    Version *string
}

func (c *cilCompatMap) GenerateAndroidBuildActions(ctx android.ModuleContext) {
    if c.shouldSkipBuild(ctx) {
        return
    }
    srcFiles := android.PathsForModuleSrc(ctx, c.properties.Bottom_half)
    for _, src := range srcFiles {
        if src.Ext() != ".cil" {
            ctx.PropertyErrorf("bottom_half", "%s has to be a .cil file.", src.String())
        }
    }
    bottomHalf := android.PathForModuleGen(ctx, "bottom_half")
    ctx.Build(pctx, android.BuildParams{
        Rule: android.CatRule, Output: bottomHalf, Inputs: srcFiles,
    })
    // A valid top half is combined with bottomHalf by combine_maps.
}

历史 mapping 可以链式组合:x→y 与 y→z 合成 x→z。当前版本与 module target 相同时直接跳过,bottom half 非 CIL 会在 property 校验失败。组合输出安装到 partition 的 etc/selinux/mapping,供更老 vendor 跨越多个 platform 版本。

5. 构建验证 ​

5.1 兼容编译 ​

源码文件: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(":system_ext_%s.compat.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))
    }
    ctx.CreateModule(policyBinaryFactory,
        &nameProperties{Name: proptools.StringPtr(ver + "_compat_test")},
        &policyBinaryProperties{Srcs: srcs,
            Ignore_neverallow: proptools.BoolPtr(true),
            Installable: proptools.BoolPtr(false)})
}

测试为每个兼容版本动态创建一个 se_policy_binary,把当前 policy、历史 mapping/compat 和对应 versioned public policy 一起交给 secilc。当版本等于 board target 时还加入真实 vendor/odm。它证明 CIL 集合可合并,但因 Ignore_neverallow=true,不承担生产 neverallow 合规结论。

5.2 freeze test ​

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

make
FREEZE_TEST_BOARD_API_LEVEL = "202604"

se_freeze_test {
    name: "se_freeze_test",
    board_api_level: FREEZE_TEST_BOARD_API_LEVEL,
    current_cil: ":base_plat_pub_policy_for_freeze_test.cil",
    prebuilt_cil: ":" + FREEZE_TEST_BOARD_API_LEVEL + "_plat_pub_policy.cil",
}

freeze test 比较当前 platform public CIL 与已冻结 prebuilt API。它在 board API freeze 条件开启时参与 system policy required 列表,防止已经发布的 public API 被静默修改。这个测试关注接口快照,不检查 vendor 业务规则是否正确。

6. 运行时合并 ​

6.1 版本选择 ​

源码文件:system/core/init/selinux.cpp

cpp
std::string vend_plat_vers;
if (!GetVendorMappingVersion(&vend_plat_vers)) {
    return false;
}
std::string plat_mapping_file(
        "/system/etc/selinux/mapping/" + vend_plat_vers + ".cil");

std::string plat_compat_cil_file(
        "/system/etc/selinux/mapping/" + vend_plat_vers + ".compat.cil");
if (access(plat_compat_cil_file.c_str(), F_OK) == -1) {
    plat_compat_cil_file.clear();
}

init 以 vendor 镜像声明的 plat_sepolicy_vers.txt 选择 system mapping 和 optional compat;不是用当前 platform version 猜测。版本文件缺失或为空会让 OpenSplitPolicy 返回 false,启动不能静默切换到另一个 mapping。

6.2 必需与可选 ​

源码文件:system/core/init/selinux.cpp

cpp
std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");
if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {
    LOG(ERROR) << "Missing " << vendor_policy_cil_file;
    return false;
}

std::string plat_pub_versioned_cil_file(
        "/vendor/etc/selinux/plat_pub_versioned.cil");
if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
    LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;
    return false;
}

std::string odm_policy_cil_file("/odm/etc/selinux/odm_sepolicy.cil");
if (access(odm_policy_cil_file.c_str(), F_OK) == -1) {
    odm_policy_cil_file.clear();
}

vendor CIL 与 plat_pub_versioned.cil 是必需输入;odm 是可选。system_ext/product 的 policy/mapping 也按文件存在性加入。required/optional 的差异决定了缺文件是 FATAL 还是跳过,排查 OTA 启动失败时必须先分类。

6.3 预编译一致性 ​

init 只有在 platform、system_ext、product 的 policy+mapping hash 都与 vendor/odm 预编译策略旁的 hash 相等时才复用 binary。system-only OTA 造成 hash 不匹配时,正常行为是回退到 runtime secilc,而不是直接加载旧预编译 policy。

7. 变更实践 ​

7.1 新增public type ​

一个 type 需要 vendor 使用时,按顺序处理:

  1. 确认 vendor 的真实调用方,避免无必要扩大 public API;
  2. 按 board API N 在 public/private 做双定义和 M4 guard;
  3. 更新 N-1 compat:全新对象进 ignore,旧标签细分则做 mapping;
  4. 更新/生成 prebuilt API snapshot;
  5. 运行 freeze、Treble mapping、compat binary 和完整 policy 构建;
  6. 用旧 vendor/new system 组合验证启动与业务访问。

7.2 删除或重命名 ​

删除 public type 不能只删源码声明。旧 vendor 仍可能引用旧名称,必须在 compat mapping 中保留 declaration 或映射到当前类型;若行为也发生变化,增加 compat CIL。TestNoUnmappedRmTypes 会阻止没有声明的删除,但不会替你判断新旧行为是否安全等价。

7.3 扩展分区 ​

system_ext 和 product 也可以导出 public type,并各自安装 mapping;若这些分区可独立更新,其维护者负责历史 base mapping。product mapping 会过滤 platform 和 system_ext mapping,避免重复 API。不能把所有伙伴策略都塞进 platform public 规避维护责任。

8. 诊断矩阵 ​

失败owner首个检查点
vendor undefined typepublic API/input scopetype 是否只在 private
新 public type mapping test 失败platform API ownerN-1 mapping/ignore
freeze test 失败public snapshot owner当前与 prebuilt CIL 差异
compat binary 失败mapping/compat/历史输入对应版本全部 CIL
init missing vendor CILvendor image安装 module 与分区文件
init missing mappingsystem image/versionplat_sepolicy_vers.txt 对应路径
precompiled hash mismatchsystem/vendor 组合正常回退是否能 secilc
runtime AVC only on old vendorcompat behavior旧标签 mapping 与 compat allow

9. 源码导航 ​

  1. system/sepolicy/docs/public_private_compat.md:public/private 放置、board API 和 mapping 规则。
  2. system/sepolicy/Android.bp:public/vendor/odm CIL、mapping、freeze 和安装依赖。
  3. system/sepolicy/build/soong/versioned_policy.go:mapping 与 target attributize。
  4. system/sepolicy/build/soong/cil_compat_map.go:多段 mapping 合成。
  5. system/sepolicy/build/soong/compat_cil.go:compat 文件收集和 per-version binary 测试。
  6. system/sepolicy/tests/treble_sepolicy_tests.py:public type 新增/删除断言。
  7. system/sepolicy/private/compat/:历史 mapping、ignore 和 compat 行为。
  8. system/sepolicy/prebuilts/api/:冻结的 public/private API snapshot。
  9. system/core/init/selinux.cpp:运行时版本选择、required/optional 输入和 secilc 合并。