Android 原生服务访问外部存储 SELinux 权限问题排查与解决

Android 原生服务访问外部存储 SELinux 权限问题排查与解决

适用场景:在 Android 系统(特别是 Android 15 及以上)中,自定义 HAL 或原生守护进程需要将文件写入 U 盘或 SD 卡,但在 SELinux 处于 Enforcing 模式时操作失败,而执行 setenforce 0 后成功。

核心思路:定位进程域 → 强制暴露隐藏的 AVC 拒绝 → 根据日志补全权限 → 处理 DAC 限制与 neverallow 冲突 → 更新冻结基线(如需要) → 清理验证。


一、准备工作:确定进程名和 SELinux 域

  1. 根据可执行文件名推测关键字
    例如服务文件名为 vendor.huanglong.hardware.hwtvmw@1.0-service,可取 hwtvmw 作为关键字。

  2. 查找进程及其安全上下文

    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),后续所有规则都围绕此域添加。

  3. 获取目标路径的 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 规则和内核审计速率限制会隐藏许多拒绝日志,必须临时关闭。

  1. 修改 SELinux 策略,将目标域设为 permissive 并开启审计
    在设备 sepolicy 目录下对应的 .te 文件(如 hal_hwtvmwservice.te)中添加:

    permissive hal_hwtvmwservice;
    auditallow hal_hwtvmwservice vfat:dir *;
    auditallow hal_hwtvmwservice vfat:file *;
    

    如果还涉及其他目标类型(如 fusemnt_media_rw_file),也一并添加 auditallow

  2. 编译并刷入临时策略

    make bootimage   # 或 make vendorbootimage
    adb reboot bootloader
    fastboot flash boot boot.img
    fastboot reboot
    
  3. 关闭内核审计速率限制

    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   # 如存在
    
  4. 清空旧日志并启动实时监控

    adb shell dmesg -c > /dev/null
    adb shell cat /proc/kmsg | grep "avc:" &
    
  5. 触发一次写入操作(例如调用 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 权限不足。

  1. 检查目录权限和进程属组

    adb shell ls -ld /storage
    adb shell cat /proc/$(pidof <进程名>)/status | grep Groups
    

    例如 /storage 权限为 drwx--x--- shell everybody,而 HAL 进程不在 everybody 组中,则无法遍历。

  2. 解决方案(按优先级选择)

    • 方案 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 禁止,需进行下一步操作。

五、突破 neverallow 限制

当添加某个权限时编译报错 “violated by neverallow”,例如 dac_read_search,且必须使用该能力,可执行以下步骤(仅限非认证设备)。

  1. 在 platform 公共目录定义新属性
    创建 system/sepolicy/public/vendor_dac_read_search.te(文件名可自定义):

    attribute vendor_dac_read_search_domain;
    
  2. 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;
    
  3. 在 vendor HAL 策略中关联属性并授权
    编辑 hal_hwtvmwservice.te

    typeattribute hal_hwtvmwservice vendor_dac_read_search_domain;
    allow hal_hwtvmwservice self:capability dac_read_search;
    
  4. 更新 sepolicy 冻结基线
    因为新增了 public 属性,需要同步更新预置基线文件。
    system/sepolicy/prebuilts/api/202404/(或其他对应 API 级别)下,找到 202404_plat_pub_policy.cil(以及其他类似的 .cil 文件),末尾添加:

    (typeattribute vendor_dac_read_search_domain)
    

    注意:必须更新所有包含该公共策略的基线文件,否则编译会报 se_freeze_test 错误。


六、清理与最终验证

  1. 移除所有调试规则
    删除或注释掉 permissiveauditallow 等临时规则,确保最终策略干净。

  2. 编译并刷入最终镜像

    make bootimage
    adb reboot bootloader && fastboot flash boot boot.img && fastboot reboot
    
  3. 确认 SELinux 状态为 Enforcing

    adb shell getenforce
    

    输出必须为 Enforcing

  4. 功能测试

    • 触发文件写入操作。
    • 检查目标文件是否生成且内容正确。
    • 确认无相关 AVC 拒绝:
      adb logcat -b all -d | grep "avc.*denied.*hal_hwtvmwservice"
      
  5. 如仍需支持 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 → 固基线 → 清验证。仅作个人总结

posted @ 2026-06-21 08:32  我,闪刀高手  阅读(5)  评论(0)    收藏  举报