AI 技术博客
Android全栈9 分钟阅读5350

【全栈第6课】SELinux 基础 — 让你的系统服务能起来

Android 全栈工程师进阶教程 第06课。SELinux 基础 — 让你的系统服务能起来。基于公开 AOSP 知识整理,含原理、可编译示例、常见问题定位。

> 本文是《Android 全栈工程师进阶教程》系列第 06 课。完整 26 课见 GitHub 仓库

【全栈第6课】SELinux 基础 — 让你的系统服务能起来


0. 这节课你将做出什么

给 L2 的 hello 服务补 SELinux 策略,让它能被 system_server 合法注册、被 App 合法获取——消除 avc: denied。 学完你能回答:为什么我服务编进去了、SystemServer 也启动了,logcat 却一片 avc: denied、服务起不来?SELinux 的 allow 规则到底怎么写?

1. 背景:光有代码权限不够,还得过 SELinux 这一关

你可能遇到过:服务代码没问题、编进系统了、SystemServer 也调了 addService,但 logcat 全是 avc: denied,服务死活起不来。原因是 SELinux

普通 Linux 权限是 DAC(自主访问控制):基于 uid/gid,root 能干一切。 SELinux 是 MAC(强制访问控制):即使你是 root/system,没有策略明确允许,就是不行。Android 全程 enforcing(强制模式)。

这是双保险:就算某进程被攻破拿到了 root,SELinux 还能挡住它干不该干的事。代价是:你加的每个新服务/新设备节点,都得显式写策略放行。

2. 全局图:SELinux 的核心模型

主体(scontext) ── 想对 ──▶ 客体(tcontext) ── 做 ──▶ 动作(class)
u:r:system_server:s0        u:object_r:hello_service:s0    add (service_manager)

必须有一条 allow 规则覆盖这个 (主体, 客体, 动作),否则 → 拒绝 + 打 avc:denied

一切皆有"类型(type)":进程有进程的类型(domain),文件/服务有它们的类型。规则就是"允许哪个 domain 对哪个 type 做哪些操作"。

3. 某真实机型 真实源码印证:给服务放行的三件事

以 input 服务为例:

① 定义 service 类型 system/sepolicy/public/service.te:164:

type input_service, app_api_service, system_server_service, service_manager_type;

给 input 服务定义了 input_service 类型,并打了几个标签属性:system_server_service(可被 system_server 注册)、app_api_service(App 可访问)。

② service_contexts 打标签 system/sepolicy/private/service_contexts:334:

input    u:object_r:input_service:s0

把服务名 input 绑到 input_service 这个类型。

③ allow 规则 system/sepolicy/private/system_server.te:1060:

add_service(system_server, system_server_service)

允许 system_server 注册所有 system_server_service 类型的服务(这是个宏,展开成 allow)。

> 巧妙之处:因为 input_service 在第①步被打了 system_server_service 标签,第③步这条通配规则就自动覆盖了它。所以给 hello 服务也打 system_server_service 标签,就能复用这条规则,不用单独写 allow

4. 前置准备

  • 依赖 L2:hello 服务已能注册(代码层面)。
  • system/sepolicy 仓两个文件:public/service.teprivate/service_contexts
  • 知道怎么读 avc:denied(下面教)。

5. 动手:给 hello 服务补策略

步骤 1:public/service.te 加 hello_service 类型

# 对照 input_service 的写法
type hello_service, app_api_service, system_server_service, service_manager_type;

逐个标签什么意思:

  • service_manager_type — 这是个能注册到 servicemanager 的服务类型(必须)
  • system_server_service — 允许 system_server 注册它(复用 add_service 通配规则)
  • app_api_service — 允许 App 通过 getSystemService 获取它

步骤 2:private/service_contexts 绑定服务名

hello    u:object_r:hello_service:s0

把服务名 hello(就是 L2 里 Context.HELLO_SERVICE)绑到 hello_service 类型。

做完这两步:system_server 注册 hello 走 add_service(system_server, system_server_service) 通配放行;App 访问走 app_api_service 放行。无需额外 allow。

6. 看底层:avc:denied 怎么读(最重要的实战技能)

avc: denied { add } for service=hello
     scontext=u:r:system_server:s0                  ← 谁(主体domain):system_server
     tcontext=u:object_r:default_android_service:s0 ← 对谁(客体type):hello 的标签
     tclass=service_manager                          ← 什么类:服务管理

逐字段翻译:

  • denied { add } — 被拒的动作是 add(注册服务)
  • scontext — 发起者是 system_server 进程(它的 domain)
  • tcontext — 目标是 default_android_service(⚠️ 注意!不是 hello_service)
  • tclass — 操作类别是 service_manager

这条 denied 告诉你根因:hello 没在 service_contexts 里打标签 → 落到了默认类型 default_android_service → 而 system_server 没权限往这个默认类型 add → 拒绝。这就是第 2 课服务刷机起不来的典型原因,补上步骤 1+2 就解决。

7. 编译 & 运行(验证状态如实)

make selinux_policy    # 或整编;sepolicy 改动会进 *_sepolicy.cil
# 刷机后:
adb logcat | grep -iE "avc.*hello|hello_service"   # 应该没有 denied 了
adb shell service list | grep hello                 # 服务成功上架

> 验证状态(诚实):本课作为学习样板,sepolicy 改动放在 zbl-lesson 目录;真实改动需进 system/sepolicy 真实策略文件并整编 sepolicy。语法对照 某真实机型 真实 hal_vibrator/input 策略写的。

8. 踩坑提醒

  1. 改了 service_contexts 不整编 sepolicy:策略不生效,还是 denied。sepolicy 改动要重新编译策略。
  2. 少打标签:只写 service_manager_type 不写 system_server_service → system_server 还是不能 add。
  3. enforcing vs permissive:adb shell getenforce 看模式。permissive 下 denied 只打日志不拦截(容易误以为没问题),量产是 enforcing。
  4. 审计日志被 ratelimited:denied 太多会被内核限流,dmesg 里看可能不全;用 dmesg | grep avc + 触发场景重现。
  5. neverallow 冲突:有些 allow 你写了也编不过——撞了 neverallow 规则(安全底线),说明那个权限本就不该给,要换设计。

9. 常见问题分析与定位(SELinux 实战)

9.1 常见问题清单

  1. 服务 addService 时 avc: denied { add }
  2. 进程访问 /dev 节点 avc: denied { read/write }
  3. App 访问服务 avc: denied { find }
  4. sepolicy 编译报 neverallow 违例
  5. 改了策略不生效(还是 denied)

9.2 分析与定位

通用第一步:抓 avc denied

adb shell getenforce                    # 确认 enforcing
adb logcat | grep -i "avc: denied"       # 用户态审计
adb shell dmesg | grep "avc:"            # 内核态审计(更全)

① add denied(服务注册被拒) — 见第 6 节。tcontext 是 default_android_service → 没打标签 → 补 service.te type + service_contexts。

② read/write denied(访问设备节点)

  • 现象:HAL/驱动进程读 /dev/xxx 被拒。
  • 定位:看 denied 的 scontext(哪个 domain)、tcontext(节点类型)、动作。
  • 修复:allow <domain> <节点type>:chr_file rw_file_perms;(字符设备)。这是 L11 HAL 访问驱动节点的常见配置。

③ find denied(App 拿服务被拒)

  • 原因:服务类型没打 app_api_service,App 无权 find。
  • 修复:service.te 的 type 加 app_api_service

④ neverallow 违例

  • 现象:make sepolicy 报 neverallow failure。
  • 原因:你写的 allow 撞了安全红线(比如让普通 app 访问敏感节点)。
  • 修复:不能简单加 allow 绕过——neverallow 是安全底线,说明设计有问题,换方案(走更受限的接口)。

⑤ 改了不生效

  • 原因:没整编 sepolicy / 没刷对分区(sepolicy 在 vendor 和 system 都有片段)/ 用了 prebuilt 策略没更新。
  • 定位:adb shell ls -Z /dev/xxxadb shell ps -Z | grep xxx 看实际生效的 context 是不是你设的。

9.3 定位决策树

SELinux denied
├─ 先 getenforce 确认 enforcing + logcat/dmesg 抓 "avc: denied"
├─ { add } → tcontext=default_android_service → 没打标签 → service.te type + service_contexts
├─ { read/write } 节点 → allow <domain> <type>:chr_file rw_file_perms (L11)
├─ { find } → 服务 type 加 app_api_service
├─ make 报 neverallow → 撞安全红线 → 换设计,别硬加 allow
└─ 改了不生效 → 整编 sepolicy + ls -Z/ps -Z 看实际 context

10. 小结

  1. SELinux = MAC:没策略明确允许就拒,root/system 也不行;Android 全程 enforcing。
  2. avc:denied 三要素:scontext(谁)、tcontext(对谁)、tclass(什么类),读懂它=定位一半。
  3. 给服务放行三件事:service.te 定义 type + service_contexts 打标签 +(复用通配)allow。
  4. 打 system_server_service / app_api_service 标签就能复用现成通配规则,不用单独写 allow。
  5. 排查口诀:先 getenforce+抓 denied;add 没标签;read/write 加节点 allow;neverallow 别硬绕。

下节预告

L7:Treble 架构 + VINTF —— 进入 HAL 阶段(框架→BSP 分水岭)。为什么 Android 把系统拆成 system/vendor 两半?它们靠什么约定接口?

评论