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 public | vendor 可引用的 type/attribute | API 稳定与历史 mapping | Treble mapping test 失败 |
| platform private | allow/neverallow 和内部 type | 不对 vendor 暴露 | vendor 引用时 undefined type |
| vendor/odm | 设备实现规则与 target version | 只依赖 public API | attributize/merge 失败 |
| mapping/compat | 历史 API 与行为桥接 | 对指定旧版本有效 | OTA 后旧 vendor 不兼容 |
| init | 版本选择、文件组合和 secilc | 加载一个完整 binary | 启动阶段 FATAL |
2. API边界
2.1 private默认
源码文件:system/sepolicy/docs/public_private_compat.md
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
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
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
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
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
下面保留两个测试的真实集合计算和断言循环;失败后的长错误说明字符串不影响判定,已在摘录中明确省略。
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
(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
(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
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
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
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
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
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 使用时,按顺序处理:
- 确认 vendor 的真实调用方,避免无必要扩大 public API;
- 按 board API N 在 public/private 做双定义和 M4 guard;
- 更新 N-1 compat:全新对象进 ignore,旧标签细分则做 mapping;
- 更新/生成 prebuilt API snapshot;
- 运行 freeze、Treble mapping、compat binary 和完整 policy 构建;
- 用旧 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 type | public API/input scope | type 是否只在 private |
| 新 public type mapping test 失败 | platform API owner | N-1 mapping/ignore |
| freeze test 失败 | public snapshot owner | 当前与 prebuilt CIL 差异 |
| compat binary 失败 | mapping/compat/历史输入 | 对应版本全部 CIL |
| init missing vendor CIL | vendor image | 安装 module 与分区文件 |
| init missing mapping | system image/version | plat_sepolicy_vers.txt 对应路径 |
| precompiled hash mismatch | system/vendor 组合 | 正常回退是否能 secilc |
| runtime AVC only on old vendor | compat behavior | 旧标签 mapping 与 compat allow |
9. 源码导航
system/sepolicy/docs/public_private_compat.md:public/private 放置、board API 和 mapping 规则。system/sepolicy/Android.bp:public/vendor/odm CIL、mapping、freeze 和安装依赖。system/sepolicy/build/soong/versioned_policy.go:mapping 与 target attributize。system/sepolicy/build/soong/cil_compat_map.go:多段 mapping 合成。system/sepolicy/build/soong/compat_cil.go:compat 文件收集和 per-version binary 测试。system/sepolicy/tests/treble_sepolicy_tests.py:public type 新增/删除断言。system/sepolicy/private/compat/:历史 mapping、ignore 和 compat 行为。system/sepolicy/prebuilts/api/:冻结的 public/private API snapshot。system/core/init/selinux.cpp:运行时版本选择、required/optional 输入和 secilc 合并。
