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/seapp | domain、MLS category | 内核 task SID |
| 对象标签 | contexts/restorecon/genfs | target type/context | inode、service、property 等对象 SID |
| 策略构建 | Soong/checkpolicy/secilc | CIL、mapping、binary | init/kernel |
| 策略安装 | init + selinuxfs | 当前 policydb、enforcing | LSM hooks/AVC |
| 访问裁决 | LSM hook + security server | ssid、tsid、class、permission | syscall/Binder/property/service |
| 反向证据 | audit/tests/query tools | denial、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
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
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
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
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
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
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 ruleAOSP 查询器消费 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 service | Type Transition、Seapp Contexts、Init Domain |
tcontext=device/tmpfs/system_data_file | 标签是否过于 generic | File Contexts、Denial 排查手册 |
tclass=property_service | property name→context→set | Property Contexts |
tclass=service_manager | service_contexts lookup 与 add/find | Service Contexts |
tclass=binder | call/transfer/impersonate 双方 domain | TE宏库、Denial 排查手册 |
tclass=file 且 TE allow 已存在 | MLS category、DAC、查询 policy | MLS 多级安全、策略查询工具 |
permissive=1 | 全局模式和 binary permissive_map | Permissive 域调试 |
| 没有日志但访问失败 | dontaudit、DAC、调用路径 | AVC Denial 日志 |
4.2 按构建错误
| 错误 | 第一责任层 | 入口 |
|---|---|---|
| M4 warning/undefined macro | policy.conf 文本生成 | M4预处理、policy.conf 生成 |
| checkpolicy undefined type/class | scope、声明、排序 | Policy Compilation |
| secilc unknown type/duplicate | CIL 集合、mapping、分区边界 | CIL 中间语言、版本化策略 |
| neverallow violation | 架构边界或过宽规则 | Neverallow、sepolicy-analyze 工具 |
| user build permissive domain | binary permissive_map | Permissive 域调试 |
| public API freeze/mapping 失败 | Treble API 维护 | Treble 与 sepolicy |
| APEX unknown/generic label | payload file_contexts 与预定义 type | APEX 与 SELinux |
4.3 按启动错误
| 日志阶段 | owner | 入口 |
|---|---|---|
| first-stage mount 失败 | fs_mgr/vendor ramdisk | 内核加载流程 |
Compiling SELinux policy 后 secilc 失败 | split CIL/mapping/compat | selinux.cpp 详解 |
Could not load policy | kernel policydb/parser | GKI 与 SELinux |
| restorecon init 失败 | init exec label/transition | Init Domain |
| 仅旧 vendor 组合出现 AVC | Treble compat/genfs version | Treble 与 sepolicy、GKI 与 SELinux |
5. 系列索引
5.1 基础模型
从零建立术语和内核位置时,按以下顺序:
- SELinux架构:进程、用户空间工具、内核 security server 的边界。
- DAC与MAC:为什么 Unix mode 和 SELinux 要分层排查。
- LSM框架:hook 如何进入 SELinux。
- 安全上下文:user:role:type:level。
- 类型强制:source/target/class/permission。
- RBAC角色:role 与 Android 固定模型。
- 客体类别与权限:class 决定 permission bit 语义。
- SELinux运行模式:enforcing/permissive 的全局结果。
5.2 策略语言
准备阅读或修改 .te 时:
前五篇解决声明、规则、宏和 transition;后五篇解决构建条件、全局约束和从需求选择规则。不要用速查篇替代具体机制篇。
5.3 Android上下文
根据 target 对象类型选择:
- File Contexts:磁盘/ramdisk 文件路径、restorecon 和 xattr。
- Property Contexts:属性名字空间与 set/read。
- Seapp Contexts:应用 domain/data type/MLS level。
- Service Contexts:Binder ServiceManager 名字空间。
- Hwservice Contexts:HIDL hwservice 名字空间。
- Vndservice Contexts:vndbinder 名字空间。
- Genfs Contexts:proc/sysfs 等无 xattr 文件系统。
- Keystore2 Key Contexts:密钥命名空间。
- MAC Permissions:签名/seinfo 输入。
- Public Private Policy:platform/vendor API 边界。
5.4 进程domain
按进程 owner 进入:
- Init Domain
- System Server Domain
- Zygote Domain
- App Domains
- Shell Domain
- HAL Domain Template
- Vendor Domain
- Kernel Domain
- Recovery Fastbootd
- Custom Daemon Domain
这些文章的共同问题是“进程怎样进入该 domain、拥有什么资源、和谁通信”;不同点是启动 owner、分区和失败清理,不能互换模板。
5.5 编译与加载
按产物阶段阅读:
- Policy Compilation:所有阶段、owner 和消费者地图。
- policy.conf 生成:scope、排序、M4 和 build variant。
- CIL 中间语言:checkpolicy 输出、filter、secilc 输入。
- 版本化策略:mapping、target attributize、compat。
- 内核加载流程:first stage、snapuserd、load/enforce/exec。
- selinux.cpp 详解:日志、hash、overlay、分区补挂和辅助函数。
定位构建错误时从 1 进入,再按失败产物下钻;定位启动错误时从 5 进入,只有需要函数级细节时再读 6。
5.6 调试与排查
按证据逐层收敛:
- AVC Denial 日志:内核字段生成与 permissive 结果。
- audit2allow 工具:外部候选生成器的边界。
- 策略查询工具:binary policy 查询和 attribute/genfs 展开。
- Permissive 域调试:全局/per-domain 状态和 user build 约束。
- sepolicy-analyze 工具:permissive、attribute、neverallow、dups 等 policydb 分析。
- Denial 排查手册:按 target producer 选择修复层。
5.7 高级边界
- Treble 与 sepolicy:新 system/旧 vendor、public API、mapping/compat/freeze。
- MLS 多级安全:同 domain 应用的 category 隔离和 constraint evaluator。
- APEX 与 SELinux:payload 可更新,但 policydb 不从 APEX 动态扩展。
- GKI 与 SELinux:内核引擎、policy format、vendor_boot 和 genfs version。
高级篇不是入门必读;当问题涉及独立镜像更新、同 type 跨应用隔离、模块化 payload 或内核对象版本时才进入。
6. 源码目录地图
6.1 system/sepolicy
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
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.cppinit 负责启动与属性,Zygote 负责 app process context,servicemanager 负责服务名 context。三者都调用 libselinux,但输入对象和 class 完全不同。
6.3 内核裁决层
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 调用。
必须形成的链路:
my_daemon_exec的 vendor file_contexts;my_daemon、exec type 和init_daemon_domain;- 配置文件专用 type、目录/文件最小权限和 DAC owner;
- property_contexts 与
set_prop(my_daemon, ...); - service_contexts、service type、
add_service; - system_server
find和双方binder_call数据流; - vendor policy 只引用 platform public API;
- 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. 验证矩阵
| 修改类型 | 源码检查 | 构建检查 | 运行检查 | 反向测试 |
|---|---|---|---|---|
| 新 domain | rc/exec type/transition | checkpolicy + secilc | ps -Z、启动失败 | negative transition/neverallow |
| 新文件 label | file_contexts + owner | checkfc + contexts build | ls -Z、restorecon | generic label must fail |
| property/service | context mapping + macro | context tests + policy | get/set/add/find | 未登记名字必须拒绝 |
| vendor public API | public/private + mapping | freeze + Treble tests | old vendor/new system | unmapped add/remove test |
| MLS/seapp | levelFrom + constraint | check_seapp + policy | process/data category | 跨应用访问必须拒绝 |
| APEX payload | APEX file_contexts + policy type | apex_sepolicy_tests | mounted labels/domain | generic/unknown type error |
| genfs/kernel object | genfs type/path/version | policy/genfs CIL | proc/sysfs label | old-version selection |
验证必须同时有正向与反向:目标访问成功不能证明隔离仍在;应额外验证非 owner domain、旧 vendor、不匹配 category 或 generic label 仍被拒绝。
9. 最终读码任务
完成下面四个任务,才能说明你已经掌握“沿源码处理 SELinux 问题”,而不仅是记住命令:
- 从一条真实 AVC 找到 LSM hook、source/target context 生产者、policydb allow/attribute 和最终修复层;
- 从一个新增 daemon 需求写出源码文件清单、构建产物链、运行消费者和失败清理;
- 从一个 public type 变更写出 board API、mapping/ignore/compat、freeze 和旧 vendor 验证;
- 从一个 APEX 或 GKI 更新需求明确哪些内容可以模块独立更新,哪些必须随 system sepolicy 更新。
如果遇到“规则明明存在但仍拒绝”,不要继续堆 allow。重新检查 query policy 来源、attribute/condition、MLS constraint、DAC、对象 label 和运行时 loaded policy,这通常才是缺失的那一层。
