Android 原生服务访问外部存储 SELinux 权限问题排查与解决
Android 原生服务访问外部存储 SELinux 权限问题排查与解决
适用场景:在 Android 系统(特别是 Android 15 及以上)中,自定义 HAL 或原生守护进程需要将文件写入 U 盘或 SD 卡,但在 SELinux 处于 Enforcing 模式时操作失败,而执行 setenforce 0 后成功。
核心思路:定位进程域 → 强制暴露隐藏的 AVC 拒绝 → 根据日志补全权限 → 处理 DAC 限制与 neverallow 冲突 → 更新冻结基线(如需要) → 清理验证。
一、准备工作:确定进程名和 SELinux 域
-
根据可执行文件名推测关键字
例如服务文件名为vendor.huanglong.hardware.hwtvmw@1.0-service,可取hwtvmw作为关键字。 -
查找进程及其安全上下文
adb shell ps -AZ | grep hwtvmw输出示例:
u:r:hal_hwtvmwservice:s0 root 2260 1 ... vendor.huanglong.hardware.hwtvmw@1.0-service其中
u:r:hal_hwtvmwservice:s0中的hal_hwtvmwservice就是该进程的 SELinux 域(scontext),后续所有规则都围绕此域添加。 -
获取目标路径的 SELinux 标签(tcontext)
adb shell ls -Z /mnt/media_rw/sda1 adb shell ls -Z /storage/sda1例如
/mnt/media_rw/sda1的标签为u:object_r:vfat:s0,则目标类型为vfat。
二、强制暴露被隐藏的 AVC 拒绝
SELinux 的 dontaudit 规则和内核审计速率限制会隐藏许多拒绝日志,必须临时关闭。
-
修改 SELinux 策略,将目标域设为 permissive 并开启审计
在设备 sepolicy 目录下对应的.te文件(如hal_hwtvmwservice.te)中添加:permissive hal_hwtvmwservice; auditallow hal_hwtvmwservice vfat:dir *; auditallow hal_hwtvmwservice vfat:file *;如果还涉及其他目标类型(如
fuse、mnt_media_rw_file),也一并添加auditallow。 -
编译并刷入临时策略
make bootimage # 或 make vendorbootimage adb reboot bootloader fastboot flash boot boot.img fastboot reboot -
关闭内核审计速率限制
adb root adb shell echo 0 > /proc/sys/kernel/printk_ratelimit adb shell echo 0 > /proc/sys/kernel/printk_ratelimit_burst adb shell echo 0 > /sys/kernel/security/audit/rate_limit 2>/dev/null # 如存在 -
清空旧日志并启动实时监控
adb shell dmesg -c > /dev/null adb shell cat /proc/kmsg | grep "avc:" & -
触发一次写入操作(例如调用 HAL 的文件导出接口)
等待几秒后按下 Ctrl+C 停止监控,收集所有avc: denied日志。
三、分析 AVC 日志并补齐权限
日志分析模板
一条典型的 AVC 拒绝日志:
avc: denied { search } for comm="HwBinder:2807_2" name="/" dev="sda1" ...
scontext=u:r:hal_hwtvmwservice:s0 tcontext=u:object_r:vfat:s0 tclass=dir permissive=1
| 字段 | 含义 | 如何指导添加规则 |
|---|---|---|
scontext |
源域(谁在执行操作) | 确定修改哪个 .te 文件(这里是 hal_hwtvmwservice) |
tcontext |
目标对象的安全上下文 | 确定规则中的目标类型(这里是 vfat) |
tclass |
对象类别(dir, file, capability 等) |
确定规则中 : 后面的类别(vfat:dir) |
denied { xxx } |
被拒绝的权限 | 需要添加的权限(这里是 search) |
示例:补全 vfat 目录权限
日志中看到 denied { search } 和 denied { write } 对 vfat:dir,需要至少添加:
allow hal_hwtvmwservice vfat:dir { search write };
但实际写入文件还需要 add_name(添加目录条目)和 open(打开目录),建议一次性补全:
allow hal_hwtvmwservice vfat:dir { open read write add_name search getattr };
allow hal_hwtvmwservice vfat:file { open create read write getattr setattr };
示例:补全路径遍历权限
访问 /storage/sda1 时,日志出现:
avc: denied { search } ... tcontext=u:object_r:mnt_user_file:s0 tclass=dir
需要添加:
allow hal_hwtvmwservice mnt_user_file:dir search;
通用原则:对文件路径中的每一级目录,都要确保有 search 权限;对目标文件本身,确保有读/写/创建等权限。如果不知道需要哪些权限,可以参考系统中类似功能的其他 HAL 策略(如 hal_hwdtvservice.te)来补全。
四、处理 DAC 权限限制(目录 traversing 失败)
当 AVC 日志中无拒绝,但 errno=13 (Permission denied) 时,可能是 Linux DAC 权限不足。
-
检查目录权限和进程属组
adb shell ls -ld /storage adb shell cat /proc/$(pidof <进程名>)/status | grep Groups例如
/storage权限为drwx--x--- shell everybody,而 HAL 进程不在everybody组中,则无法遍历。 -
解决方案(按优先级选择)
- 方案 A(推荐):改用真实挂载点
/mnt/media_rw/sda1,并在init.rc中将进程加入media_rw组(gid=1023):service myservice /vendor/bin/myhal class hal user root group root system media_rw - 方案 B:赋予进程
dac_read_search能力绕过 DAC,但该能力通常被 neverallow 禁止,需进行下一步操作。
- 方案 A(推荐):改用真实挂载点
五、突破 neverallow 限制
当添加某个权限时编译报错 “violated by neverallow”,例如 dac_read_search,且必须使用该能力,可执行以下步骤(仅限非认证设备)。
-
在 platform 公共目录定义新属性
创建system/sepolicy/public/vendor_dac_read_search.te(文件名可自定义):attribute vendor_dac_read_search_domain; -
在
domain.te的 neverallow 豁免列表中加入该属性
编辑system/sepolicy/private/domain.te,找到dac_read_search的 neverallow 行:neverallow ~{ dac_override_allowed traced_perf traced_probes heapprofd vendor_dac_read_search_domain # 新增这一行 } self:global_capability_class_set dac_read_search; -
在 vendor HAL 策略中关联属性并授权
编辑hal_hwtvmwservice.te:typeattribute hal_hwtvmwservice vendor_dac_read_search_domain; allow hal_hwtvmwservice self:capability dac_read_search; -
更新 sepolicy 冻结基线
因为新增了 public 属性,需要同步更新预置基线文件。
在system/sepolicy/prebuilts/api/202404/(或其他对应 API 级别)下,找到202404_plat_pub_policy.cil(以及其他类似的.cil文件),末尾添加:(typeattribute vendor_dac_read_search_domain)注意:必须更新所有包含该公共策略的基线文件,否则编译会报
se_freeze_test错误。
六、清理与最终验证
-
移除所有调试规则
删除或注释掉permissive、auditallow等临时规则,确保最终策略干净。 -
编译并刷入最终镜像
make bootimage adb reboot bootloader && fastboot flash boot boot.img && fastboot reboot -
确认 SELinux 状态为 Enforcing
adb shell getenforce输出必须为
Enforcing。 -
功能测试
- 触发文件写入操作。
- 检查目标文件是否生成且内容正确。
- 确认无相关 AVC 拒绝:
adb logcat -b all -d | grep "avc.*denied.*hal_hwtvmwservice"
-
如仍需支持 user 版本,确保基线已更新且没有使用
userdebug_or_eng宏包裹属性定义,重复步骤五-4。
七、常见问题速查
| 现象 | 可能原因 | 解决措施 |
|---|---|---|
| 没有 AVC 拒绝但操作失败 | 审计被 dontaudit 隐藏 |
使用 permissive + 关闭 rate limit 强制暴露 |
编译报 unknown type |
引用未定义的类型 | 检查类型是否在正确的 public/private 目录定义,注意 treble 隔离 |
se_freeze_test 错误 |
新增了 public 类型/属性 | 更新 prebuilts/api/ 下对应基线文件 |
| 语法错误(反引号、换行符) | 文件格式不对(CRLF 等) | 使用 dos2unix 转换,确保 Unix 换行符,typeattribute 连写 |
storage/sda1 始终无法写入 |
缺少 DAC traversing 或 dac_read_search |
优先使用真实挂载点 /mnt/media_rw/...,避免依赖高特权能力 |
八、总结
核心步骤依次为:定位域 → 抓日志 → 补权限 → 解 DAC → 破 neverallow → 固基线 → 清验证。仅作个人总结

浙公网安备 33010602011771号