16. 权限与SELinux协同:权限检查流程、SELinux与权限的叠加控制
好,我们进入第十六章。前面我们分别讲了Android的权限模型和SELinux,现在要把它们串起来了。
很多开发者会问:「权限和SELinux到底什么关系?是不是重复了?」
其实不是。它们俩是不同层面的安全机制,一个管「能不能用这个API」,一个管「能不能碰这个文件」。我打个比方:权限就像公司门禁卡,决定你能不能进这栋楼;SELinux就像楼里的每个房间门锁,决定你能进哪个房间。
今天我们就来聊聊,这两套机制是怎么协同工作的。
16.1 权限检查流程:从API到底层
我们先看一个典型场景:一个App调用Camera.open()。系统会做什么?
流程大致是这样的:
- 应用层检查:App有没有声明
CAMERA权限? - 框架层检查:调用
checkCallingPermission(),确认调用方UID是否在允许列表里。 - Native层检查:Camera Service会检查调用进程的SELinux上下文。
- 内核层检查:最终打开
/dev/video0设备节点时,SELinux会做最后一次裁决。
你看,权限检查是自上而下的。每一层都在说「不」,只有全部通过,操作才能成功。
核心要点:权限控制的是「谁可以调用这个服务」,SELinux控制的是「这个服务能不能访问那个资源」。两者是串联关系,不是并联。
16.2 权限与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_app写system_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策略。我的建议是:
- 先配权限:在
AndroidManifest.xml里声明需要的权限。 - 再配SELinux:在
.te文件里添加对应的allow规则。 - 验证:用
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