16. 权限与SELinux协同:权限检查流程、SELinux与权限的叠加控制

好,我们进入第十六章。前面我们分别讲了Android的权限模型和SELinux,现在要把它们串起来了。

很多开发者会问:「权限和SELinux到底什么关系?是不是重复了?」

其实不是。它们俩是不同层面的安全机制,一个管「能不能用这个API」,一个管「能不能碰这个文件」。我打个比方:权限就像公司门禁卡,决定你能不能进这栋楼;SELinux就像楼里的每个房间门锁,决定你能进哪个房间。

今天我们就来聊聊,这两套机制是怎么协同工作的。

16.1 权限检查流程:从API到底层

我们先看一个典型场景:一个App调用Camera.open()。系统会做什么?

流程大致是这样的:

  1. 应用层检查:App有没有声明CAMERA权限?
  2. 框架层检查:调用checkCallingPermission(),确认调用方UID是否在允许列表里。
  3. Native层检查:Camera Service会检查调用进程的SELinux上下文。
  4. 内核层检查:最终打开/dev/video0设备节点时,SELinux会做最后一次裁决。

你看,权限检查是自上而下的。每一层都在说「不」,只有全部通过,操作才能成功。

核心要点:权限控制的是「谁可以调用这个服务」,SELinux控制的是「这个服务能不能访问那个资源」。两者是串联关系,不是并联。

16.2 权限与SELinux的叠加控制模型

我画了一张图,帮你理解这个叠加关系:

权限与SELinux叠加控制模型 应用层:权限声明(AndroidManifest.xml) 检查:是否声明了 CAMERA 权限? 框架层:权限检查(PackageManagerService) 检查:调用方UID是否有权限? Native层:SELinux上下文检查(SecurityServer) 检查:进程的SELinux上下文是否允许访问Camera Service? 内核层:SELinux策略检查(AVC) 检查:是否允许访问 /dev/video0 设备节点? 权限层 SELinux层 SELinux层

这张图你看懂了吗?从上到下,每一层都在做自己的检查。权限层管的是「能不能调用」,SELinux层管的是「能不能访问」。任何一个环节拒绝,操作就失败。

16.3 实际案例:为什么权限通过了,操作还是失败?

我在项目中遇到过这样一个问题:一个系统App明明声明了WRITE_EXTERNAL_STORAGE权限,但写文件时却报Permission denied

排查过程很有意思:

  • 先看权限:dumpsys package显示权限已授予。没问题。
  • 再看SELinux:logcat | grep avc,果然有一条AVC denial。

原来是这个App的SELinux上下文是untrusted_app,而目标文件所在的目录被标记为system_data_file。策略里不允许untrusted_appsystem_data_file

排查技巧:遇到权限问题,先看logcat里有没有AVC denial。如果有,那就是SELinux的问题,不是权限的问题。我习惯用logcat -b events | grep am_grant快速定位权限授予情况。

16.4 权限与SELinux的典型交互场景

我整理了几个常见场景,帮你理解它们是怎么配合的:

场景 权限检查 SELinux检查 结果
普通App打开相机 检查CAMERA权限 检查能否访问camera_device 两者都通过才成功
系统App写/data分区 检查WRITE_EXTERNAL_STORAGE 检查能否写system_data_file 权限通过但SELinux可能拒绝
ADB shell执行命令 无权限检查(shell用户) 检查shell域能否执行 完全由SELinux控制
ContentProvider跨进程访问 检查调用方权限 检查进程间通信是否允许 双重保护

你看,有些场景权限检查是缺失的(比如ADB shell),这时候SELinux就是最后一道防线。这也是为什么Android从5.0开始强制启用SELinux enforcing模式。

16.5 叠加控制带来的问题

两套机制叠加,好处是安全,坏处是——排查问题变复杂了

我曾经帮一个团队排查问题:他们的App在Android 9上运行正常,升级到Android 11后,某个功能就崩溃了。代码没变,权限声明也没变。

查了半天,发现是Android 11收紧了对/proc文件系统的SELinux策略。App之前能读/proc/self/status,现在被deny了。

避坑指南:Android大版本升级时,一定要关注SELinux策略的变化。我建议你在升级前做一次全面的AVC denial扫描。命令很简单:adb logcat -b events | grep avc,跑一遍所有功能,看看有没有新的denial出现。

16.6 如何正确配置权限与SELinux

如果你在开发系统App或定制ROM,需要同时配置权限和SELinux策略。我的建议是:

  1. 先配权限:在AndroidManifest.xml里声明需要的权限。
  2. 再配SELinux:在.te文件里添加对应的allow规则。
  3. 验证:用dumpsys package permissions确认权限,用sesearch确认SELinux规则。

举个例子,假设你的App需要访问/dev/sensors

// AndroidManifest.xml
<uses-permission android:name="android.permission.BODY_SENSORS" />

// sensors_app.te
allow sensors_app sensors_device:chr_file { open read write };

嗯,这里要注意:权限和SELinux的命名不一定对应。权限叫BODY_SENSORS,但SELinux里的设备类型可能是sensors_device。你不能想当然地认为名字一样就能通。

16.7 调试技巧:快速定位是权限还是SELinux

遇到拒绝时,怎么快速判断是哪一层的问题?我分享一个自己的调试流程:

  • 第一步:看错误日志。如果是SecurityException,通常是权限问题。如果是Permission denied(没有堆栈),通常是SELinux。
  • 第二步:查logcat -b events | grep am_grant,看权限是否被授予。
  • 第三步:查logcat | grep avc,看有没有SELinux denial。
  • 第四步:如果还不行,临时把SELinux设为permissive模式:setenforce 0。如果问题消失,那就是SELinux的问题。

小技巧:我习惯在开发板上跑audit2allow工具。把AVC denial日志导出来,用audit2allow -i avc.log直接生成需要的SELinux规则。省时省力。

16.8 总结

权限和SELinux,一个管「能不能用」,一个管「能不能碰」。两者叠加,构成了Android纵深防御体系的核心。

你想想看,如果没有SELinux,一个App只要拿到权限就能为所欲为。有了SELinux,就算权限被滥用,它也只能在自己的沙箱里折腾,动不了系统核心资源。

这就是为什么我说:权限是门禁卡,SELinux是房间锁。门禁卡丢了,房间锁还能挡住人。

好了,这一章就到这里。下一章我们聊聊更深入的东西——如何编写自定义SELinux策略。


公众号:蓝海资料掘金营,微信deep3321