多用户与SELinux:用户隔离与数据保护
多用户场景,说白了就是一台设备给多个人用。Android从4.2开始支持多用户,但真正让多用户变得安全可靠的,是SELinux这套强制访问控制机制。我最早接触这个是在做企业定制ROM的时候,客户要求一台平板能分给不同员工使用,数据必须完全隔离。嗯,当时踩了不少坑,今天把这些经验分享给你。
多用户场景下的安全挑战
先想想看,多用户最怕什么?
- 用户A能看到用户B的照片——隐私泄露
- 用户A能卸载用户B的应用——数据破坏
- 用户A能读取用户B的数据库——信息窃取
传统的Linux多用户靠UID隔离,但Android的权限模型更复杂。每个应用有自己的UID,每个用户又有自己的用户ID。这两层叠加起来,光靠DAC(自主访问控制)根本不够。我在项目中遇到过,有个第三方应用通过ptrace跨用户调试,差点把用户B的支付信息读走了。这就是为什么需要SELinux。
SELinux用户隔离的核心机制
SELinux做隔离,靠的是三个东西:安全上下文、类型强制、多类别安全(MCS)。我一个个说。
1. 安全上下文:给每个进程和文件贴标签
在Android多用户环境下,每个用户的应用进程和文件都有独立的安全上下文。举个例子:
# 用户0的应用
u:r:untrusted_app:s0:c0,c1,c2
# 用户10的应用
u:r:untrusted_app:s0:c10,c11,c12
看到没?前面的u:r:untrusted_app是类型,后面的s0:c0,c1,c2是MCS类别。不同用户的MCS类别不同,这就是隔离的关键。
核心原理:SELinux策略规定,只有MCS类别匹配的进程才能访问对应类别的文件。用户0的进程只能访问带c0,c1,c2标签的文件,用户10的进程只能访问带c10,c11,c12标签的文件。交叉访问?直接拒绝。
2. 多类别安全(MCS):用户隔离的基石
MCS是SELinux的一个扩展机制。它允许你在同一个安全上下文中附加多个类别标签。Android用MCS来实现用户隔离,具体做法是:
- 每个用户分配一个唯一的MCS范围(比如用户0是c0-c2,用户10是c10-c12)
- 用户创建的文件自动继承该用户的MCS标签
- 策略中定义:进程只能读写自己MCS范围内的文件
我个人习惯把MCS理解成「颜色标签」。每个用户有自己的颜色,只能看到自己颜色的文件。其他颜色的文件?权限再高也看不到。
用户隔离策略的具体实现
来看一个实际的策略文件片段。这是Android 12中多用户隔离的核心策略:
# 定义MCS范围
mlsconstrain file { read write create unlink }
( u1 == u2
or
( t1 == untrusted_app
and t2 == app_data_file
and h1 <= h2
and l1 >= l2 ) );
# 用户数据目录隔离
type user_data_file, file_type, data_file_type;
allow untrusted_app user_data_file:dir { read search };
allow untrusted_app user_data_file:file { read write getattr };
# 注意:这里限制了只能访问自己用户的数据
这段策略的意思是:非同一用户的应用,不能读写其他用户的数据文件。即使两个应用都是untrusted_app类型,只要MCS类别不同,就无法互相访问。
避坑指南:我曾经在定制策略时,忘记给新创建的用户分配MCS范围。结果用户10的应用启动后,SELinux疯狂报avc denial,因为进程没有合法的MCS标签,无法访问任何文件。解决方案是在init.rc中为每个用户正确设置security context。
应用数据保护:从文件到数据库
多用户场景下,应用数据保护不仅仅是文件权限。还要考虑:
- SharedPreferences:每个用户的应用有独立的sp目录
- SQLite数据库:数据库文件受SELinux保护
- ContentProvider:跨用户访问需要特殊权限
- 外部存储:/sdcard下的用户隔离
我记得有个项目,客户反馈用户B能看到用户A的微信聊天记录。查了半天,发现是微信的数据库文件放在了/sdcard/Android/data/下,而外部存储的SELinux策略没有做MCS隔离。后来我们在external_storage.te中加了MCS约束,问题才解决。
外部存储的隔离策略
# 外部存储文件隔离
type sdcard_file, file_type, sdcard_type;
allow untrusted_app sdcard_file:file { read write };
# 但必须匹配MCS
mlsconstrain file { read write }
( t1 == untrusted_app
and t2 == sdcard_file
and h1 <= h2
and l1 >= l2 );
这段策略确保:即使应用有权限读写sdcard,也只能读写自己用户MCS范围内的文件。用户A的应用无法访问用户B在sdcard上的文件。
多用户SELinux策略的调试技巧
调试多用户SELinux问题,我常用的命令:
| 命令 | 用途 |
|---|---|
ps -Z |
查看进程的安全上下文,确认MCS标签 |
ls -Z /data/data/ |
查看应用数据目录的安全上下文 |
dmesg | grep avc |
查看SELinux拒绝日志,定位跨用户访问 |
audit2allow -i /proc/kmsg |
将avc denial转换为策略规则 |
注意:调试时一定要确认当前是哪个用户。我见过有人用adb shell查看进程,发现安全上下文不对,结果是因为adb默认运行在用户0下。切换到目标用户再查,才能看到真实情况。
多用户SELinux架构图
下面这张图展示了多用户场景下SELinux的隔离架构:
实际项目中的避坑经验
最后分享几个我踩过的坑:
- 系统服务也要隔离:不只是第三方应用,系统服务如installd、packagemanager也要考虑多用户。我遇到过installd在安装应用时,给所有用户的数据目录打了相同的MCS标签,导致用户隔离失效。
- 升级策略要谨慎:OTA升级时,如果SELinux策略变了,旧用户的数据文件标签可能不匹配。需要在升级脚本中重新relabel文件。
- adb shell的坑:adb shell默认运行在用户0,但shell进程的MCS范围是s0(无限制)。这意味着adb shell可以访问所有用户的数据。生产环境一定要限制adb权限。
- 多用户+多应用:同一个用户下多个应用之间,SELinux也做隔离。但那是基于应用UID的,和MCS不同。别搞混了。
总结一下:多用户SELinux隔离的核心就是MCS。每个用户分配独立的MCS类别,策略中强制要求MCS匹配才能访问。文件创建时自动继承用户的MCS标签,进程运行时携带用户的MCS标签。这样,用户A无论如何也访问不到用户B的数据。嗯,原理不复杂,但实现细节很多。建议你在自己的项目里,先写一个最小化的多用户策略,然后逐步加功能,边加边测试。
公众号:蓝海资料掘金营,微信deep3321