Skip to content

SELinux 源码地图

以访问裁决、修改对象和故障现象为索引,串联 Android SELinux 的标签、domain、内核、构建、调试与兼容源码。

基于android-17.0.0_r1
AndroidSELinux源码导航知识图谱调试源码阅读

SELinux 源码地图 ​

本文不是前 54 篇的缩写,也不会把所有名词堆成一张大图。它有三个用途:拿到一条 AVC 时快速找到真正的 owner;准备修改标签、domain、服务名字空间或 public API 时找到完整消费者;阅读源码时知道在哪一层停止,避免从局部函数外推整个系统。本文使用的每个主线入口都来自 Android 17 与对应 GKI 内核源码。

如果你只需要某个对象的机制,请直接使用后文的“问题路由”和系列索引;如果要建立完整模型,从“访问生命周期”开始。完整模型只有一条:身份和标签在用户空间产生,策略在构建期编译,init 将 binary 安装进内核,LSM hook 在运行期提交 source/target/class/permission,security server 结合 TE、MLS、constraint 和 permissive 状态给出结果,audit 再把结果还原成日志。

1. 访问生命周期 ​

1.1 六个owner ​

阶段owner主要状态典型消费者
进程身份Zygote/init/seappdomain、MLS category内核 task SID
对象标签contexts/restorecon/genfstarget type/contextinode、service、property 等对象 SID
策略构建Soong/checkpolicy/secilcCIL、mapping、binaryinit/kernel
策略安装init + selinuxfs当前 policydb、enforcingLSM hooks/AVC
访问裁决LSM hook + security serverssid、tsid、class、permissionsyscall/Binder/property/service
反向证据audit/tests/query toolsdenial、policydb 规则、断言开发者与构建系统

一个修复必须落在正确 owner。进程 domain 错误不能靠给旧 domain 加 allow;文件 target type 错误应先改 context/restorecon;ServiceManager 名字没有 context 时不存在可查询的 target type;已有 allow 仍拒绝时应查 MLS、condition、DAC 或查询的 policy 来源。

1.2 端到端时序 ​

2. 源码锚点 ​

2.1 应用进程context ​

源码文件:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

cpp
const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr;

if (selinux_android_setcontext(uid, is_system_server,
                               se_info_ptr, nice_name_ptr) == -1) {
    fail_fn(CREATE_ERROR(
        "selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed",
        uid, is_system_server, se_info_ptr, nice_name_ptr));
}

这是应用 source context 的关键用户态入口。libselinux 使用 UID、seinfo、名称和编译后的 seapp_contexts 选择 domain/level;失败会终止 specialization。看到 app domain 或 category 异常,应先沿这里回查 package seinfo 和 seapp 规则,而不是从内核 hook 开始猜。

2.2 名字对象context ​

源码文件:frameworks/native/cmds/servicemanager/Access.cpp

cpp
bool Access::actionAllowedFromLookup(const CallingContext& sctx,
                                     const std::string& name,
                                     const char* perm) {
    char* tctx = nullptr;
    if (selabel_lookup(getSehandle(), &tctx, name.c_str(),
                       SELABEL_CTX_ANDROID_SERVICE) != 0) {
        LOG(ERROR) << "SELinux: No match for " << name
                   << " in service_contexts.\n";
        return false;
    }
    bool allowed = actionAllowed(sctx, tctx, perm, name);
    freecon(tctx);
    return allowed;
}

ServiceManager 先用服务名查 service_contexts,再做 service_manager:add/find/list。没有 context 和有 context 但没 allow 是两个不同失败层。property、hwservice、vndservice 也遵循“名字 → context → access check”的模型,但名字空间和管理进程不同。

2.3 内核访问入口 ​

源码文件:kernel/common/security/selinux/avc.c

c
int avc_has_perm(u32 ssid, u32 tsid, u16 tclass,
                 u32 requested, struct common_audit_data *auditdata)
{
    struct av_decision avd;
    int rc, rc2;

    rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd);
    rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata);
    if (rc2)
        return rc2;
    return rc;
}

内核最终只关心 SID、class 和 permission bit。路径、服务名、属性名等辅助信息通过 common_audit_data 进入日志,不直接参与 TE key。AVC 判定和审计分开,所以“没有日志”不等于“没有检查”。

2.4 policy构建 ​

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

go
checkpolicyCmd := rule.Command().BuiltTool("checkpolicy").
    Flag("-C").
    Flag("-M").
    Flag("-L").
    FlagWithArg("-c ", strconv.Itoa(PolicyVers)).
    FlagWithOutput("-o ", cil).
    Input(conf)

secilcCmd := rule.Command().BuiltTool("secilc").
    Flag("-m").
    FlagWithArg("-M ", "true").
    Flag("-G").
    FlagWithArg("-c ", strconv.Itoa(PolicyVers)).
    Inputs(android.PathsForModuleSrc(ctx, c.properties.Srcs)).
    FlagWithOutput("-o ", bin)

第一段把 M4 后的 policy.conf 转成 CIL,第二段把多个 CIL 合成 binary。查询 .te 只能确认源意图,查询 CIL 能确认中间表示,查询 binary 才能确认编译器实际产物;运行时还要确认内核加载的是哪份 binary。

2.5 policy安装 ​

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

cpp
static void LoadSelinuxPolicy(std::string& policy) {
    LOG(INFO) << "Loading SELinux policy";
    set_selinuxmnt("/sys/fs/selinux");
    if (security_load_policy(policy.data(), policy.size()) < 0) {
        PLOG(FATAL) << "SELinux:  Could not load policy";
    }
}

binary 的消费者是内核 security server。加载失败是启动级错误,不是某个 domain 的 denial。split policy、mapping、genfs 和预编译 hash 的复杂性都在调用前收敛为一个内存字符串。

2.6 测试与查询 ​

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

python
def QueryTERule(self, **kwargs):
    if len(self.__Rules) == 0:
        self.__InitTERules()
    if "scontext" in kwargs and len(kwargs["scontext"]) > 0:
        kwargs["scontext"] = self.ResolveTypeAttribute(kwargs["scontext"])
    if "tcontext" in kwargs and len(kwargs["tcontext"]) > 0:
        tcontext = set()
        for tctx in kwargs["tcontext"]:
            tcontext |= self.ResolveTypeAttribute(tctx)
        kwargs["tcontext"] = tcontext
    for rule in self.__Rules:
        if self.__TERuleMatch(rule, **kwargs):
            yield rule

AOSP 查询器消费 binary policydb,并可展开 type/attribute。测试结果是某个 policy snapshot 的结构事实;要证明设备行为,还需结合对象当前 label、全局/per-domain permissive、MLS constraint 和实际触发路径。

3. 修改影响图 ​

3.1 进程改动 ​

新增 daemon 至少涉及 domain、exec type、file_contexts、init service 和 transition;应用 domain 还涉及 seinfo/seapp/UID/target SDK。只创建 type 不会自动发生 transition,只修改 seclabel 也可能绕过正常模型。

3.2 对象改动 ​

磁盘文件由 file_contexts/xattr/restorecon,proc/sysfs 由 genfs,property/service 由名字 contexts,Binder target 由对端 process SID。先识别对象类型,再选择生产者;这是整个系列最重要的排错规则。

3.3 API改动 ​

vendor 要引用的新 type 必须经过 public/private board API 规则、mapping/ignore/compat、prebuilt snapshot 和 Treble 测试。APEX 更新若需要新 domain/type,也必须配套 platform policy,不能在包内动态追加 TE。

4. 问题路由 ​

4.1 按日志字段 ​

现象先看负责文章
scontext domain 不符合预期transition、seapp、init serviceType Transition、Seapp Contexts、Init Domain
tcontext=device/tmpfs/system_data_file标签是否过于 genericFile Contexts、Denial 排查手册
tclass=property_serviceproperty name→context→setProperty Contexts
tclass=service_managerservice_contexts lookup 与 add/findService Contexts
tclass=bindercall/transfer/impersonate 双方 domainTE宏库、Denial 排查手册
tclass=file 且 TE allow 已存在MLS category、DAC、查询 policyMLS 多级安全、策略查询工具
permissive=1全局模式和 binary permissive_mapPermissive 域调试
没有日志但访问失败dontaudit、DAC、调用路径AVC Denial 日志

4.2 按构建错误 ​

错误第一责任层入口
M4 warning/undefined macropolicy.conf 文本生成M4预处理、policy.conf 生成
checkpolicy undefined type/classscope、声明、排序Policy Compilation
secilc unknown type/duplicateCIL 集合、mapping、分区边界CIL 中间语言、版本化策略
neverallow violation架构边界或过宽规则Neverallow、sepolicy-analyze 工具
user build permissive domainbinary permissive_mapPermissive 域调试
public API freeze/mapping 失败Treble API 维护Treble 与 sepolicy
APEX unknown/generic labelpayload file_contexts 与预定义 typeAPEX 与 SELinux

4.3 按启动错误 ​

日志阶段owner入口
first-stage mount 失败fs_mgr/vendor ramdisk内核加载流程
Compiling SELinux policy 后 secilc 失败split CIL/mapping/compatselinux.cpp 详解
Could not load policykernel policydb/parserGKI 与 SELinux
restorecon init 失败init exec label/transitionInit Domain
仅旧 vendor 组合出现 AVCTreble compat/genfs versionTreble 与 sepolicy、GKI 与 SELinux

5. 系列索引 ​

5.1 基础模型 ​

从零建立术语和内核位置时,按以下顺序:

  1. SELinux架构:进程、用户空间工具、内核 security server 的边界。
  2. DAC与MAC:为什么 Unix mode 和 SELinux 要分层排查。
  3. LSM框架:hook 如何进入 SELinux。
  4. 安全上下文:user:role:type:level。
  5. 类型强制:source/target/class/permission。
  6. RBAC角色:role 与 Android 固定模型。
  7. 客体类别与权限:class 决定 permission bit 语义。
  8. SELinux运行模式:enforcing/permissive 的全局结果。

5.2 策略语言 ​

准备阅读或修改 .te 时:

  1. TE规则语法
  2. Type与Attribute
  3. Type Transition
  4. TE宏库
  5. Global Macros
  6. M4预处理
  7. Neverallow
  8. Constrain约束
  9. Boolean条件策略
  10. 策略语法工作台

前五篇解决声明、规则、宏和 transition;后五篇解决构建条件、全局约束和从需求选择规则。不要用速查篇替代具体机制篇。

5.3 Android上下文 ​

根据 target 对象类型选择:

5.4 进程domain ​

按进程 owner 进入:

这些文章的共同问题是“进程怎样进入该 domain、拥有什么资源、和谁通信”;不同点是启动 owner、分区和失败清理,不能互换模板。

5.5 编译与加载 ​

按产物阶段阅读:

  1. Policy Compilation:所有阶段、owner 和消费者地图。
  2. policy.conf 生成:scope、排序、M4 和 build variant。
  3. CIL 中间语言:checkpolicy 输出、filter、secilc 输入。
  4. 版本化策略:mapping、target attributize、compat。
  5. 内核加载流程:first stage、snapuserd、load/enforce/exec。
  6. selinux.cpp 详解:日志、hash、overlay、分区补挂和辅助函数。

定位构建错误时从 1 进入,再按失败产物下钻;定位启动错误时从 5 进入,只有需要函数级细节时再读 6。

5.6 调试与排查 ​

按证据逐层收敛:

  1. AVC Denial 日志:内核字段生成与 permissive 结果。
  2. audit2allow 工具:外部候选生成器的边界。
  3. 策略查询工具:binary policy 查询和 attribute/genfs 展开。
  4. Permissive 域调试:全局/per-domain 状态和 user build 约束。
  5. sepolicy-analyze 工具:permissive、attribute、neverallow、dups 等 policydb 分析。
  6. Denial 排查手册:按 target producer 选择修复层。

5.7 高级边界 ​

高级篇不是入门必读;当问题涉及独立镜像更新、同 type 跨应用隔离、模块化 payload 或内核对象版本时才进入。

6. 源码目录地图 ​

6.1 system/sepolicy ​

text
system/sepolicy/
├── public/                 vendor-facing declarations and macros
├── private/                platform implementation rules and constraints
├── vendor/                 platform-provided vendor glue
├── reqd_mask/              minimal declarations for exported policy parsing
├── compat/                 historical mapping/compat/genfs CIL
├── prebuilts/api/          frozen public/private snapshots
├── apex/                   APEX payload file_contexts
├── build/soong/            policy/context/version build modules
├── tools/                  checkers, version_policy, sepolicy-analyze
├── tests/                  policydb wrapper and structural tests
└── Android.bp              module graph and installed artifacts

进入目录前先问“这是接口、实现、历史兼容、构建工具还是测试”。同名 type 在 public/private/prebuilt 中出现可能是 API 生命周期要求,不一定是重复错误。

6.2 init与framework ​

text
system/core/init/
├── first_stage_init.cpp    early mount and exec selinux_setup
├── selinux.cpp             policy open/compile/load/enforce/restorecon
├── property_service.cpp    property context and access checks
└── service*.cpp            service parsing and vendor-version compatibility

frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
frameworks/native/cmds/servicemanager/Access.cpp

init 负责启动与属性,Zygote 负责 app process context,servicemanager 负责服务名 context。三者都调用 libselinux,但输入对象和 class 完全不同。

6.3 内核裁决层 ​

text
kernel/common/security/selinux/
├── hooks.c                 LSM operation to class/permission mapping
├── avc.c                   cache, enforcement and audit
├── selinuxfs.c             load/enforce/policy capability files
└── ss/
    ├── policydb.c          binary parser
    ├── services.c          TE/constraint/security decisions
    ├── mls.c               MLS context and range operations
    └── context.h           internal user/role/type/range structure

看到 syscall/Binder/文件访问检查,从 hooks.c 找 class/permission;看到 denial 输出,从 avc.c;看到已有 allow 仍拒绝,从 services.c 的 constraint/MLS;看到策略加载格式错误,从 policydb.c/selinuxfs.c。

7. 综合案例 ​

7.1 Vendor Daemon ​

需求:vendor daemon 由 init 启动,读取专用配置、设置 vendor property、注册 Binder 服务,并被 system_server 调用。

必须形成的链路:

  1. my_daemon_exec 的 vendor file_contexts;
  2. my_daemon、exec type 和 init_daemon_domain;
  3. 配置文件专用 type、目录/文件最小权限和 DAC owner;
  4. property_contexts 与 set_prop(my_daemon, ...);
  5. service_contexts、service type、add_service;
  6. system_server find 和双方 binder_call 数据流;
  7. vendor policy 只引用 platform public API;
  8. CIL/versioned vendor merge、neverallow、enforcing 回归。

任何一步都可以用本篇路由跳转到具体文章。只写 allow my_daemon ... 无法完成 domain transition、名字 context 或对象标签。

7.2 新APEX daemon ​

需求:可更新 APEX 新增 /bin/foo 并运行独立 daemon。

需要同时修改 APEX file_contexts 与 system sepolicy:payload 给 /bin/foo 一个已声明 exec type;platform policy 定义 domain、transition 和权限;APEX test 验证 type/attribute/消费者;system image 更新 policy,APEX 包本身不能动态注入 TE。若只更新 APEX,unknown exec type 或 transition 缺失会阻止正确运行。

7.3 新sysfs节点 ​

需求:vendor kernel module 新增 /sys/class/foo/state,vendor HAL 读取。

先决定标签兼容版本,再在 genfs 体系添加专用 type/path;vendor HAL rule 引用允许的 public/vendor type;更新 BOARD_GENFS_LABELS_VERSION 和对应 compatibility CIL;用旧 vendor/new system 组合验证。直接允许 HAL 读取 generic sysfs:file 会扩大到大量内核对象。

8. 验证矩阵 ​

修改类型源码检查构建检查运行检查反向测试
新 domainrc/exec type/transitioncheckpolicy + secilcps -Z、启动失败negative transition/neverallow
新文件 labelfile_contexts + ownercheckfc + contexts buildls -Z、restorecongeneric label must fail
property/servicecontext mapping + macrocontext tests + policyget/set/add/find未登记名字必须拒绝
vendor public APIpublic/private + mappingfreeze + Treble testsold vendor/new systemunmapped add/remove test
MLS/seapplevelFrom + constraintcheck_seapp + policyprocess/data category跨应用访问必须拒绝
APEX payloadAPEX file_contexts + policy typeapex_sepolicy_testsmounted labels/domaingeneric/unknown type error
genfs/kernel objectgenfs type/path/versionpolicy/genfs CILproc/sysfs labelold-version selection

验证必须同时有正向与反向:目标访问成功不能证明隔离仍在;应额外验证非 owner domain、旧 vendor、不匹配 category 或 generic label 仍被拒绝。

9. 最终读码任务 ​

完成下面四个任务,才能说明你已经掌握“沿源码处理 SELinux 问题”,而不仅是记住命令:

  1. 从一条真实 AVC 找到 LSM hook、source/target context 生产者、policydb allow/attribute 和最终修复层;
  2. 从一个新增 daemon 需求写出源码文件清单、构建产物链、运行消费者和失败清理;
  3. 从一个 public type 变更写出 board API、mapping/ignore/compat、freeze 和旧 vendor 验证;
  4. 从一个 APEX 或 GKI 更新需求明确哪些内容可以模块独立更新,哪些必须随 system sepolicy 更新。

如果遇到“规则明明存在但仍拒绝”,不要继续堆 allow。重新检查 query policy 来源、attribute/condition、MLS constraint、DAC、对象 label 和运行时 loaded policy,这通常才是缺失的那一层。