应用沙箱与 UID:进程隔离的基础
2026/8/9大约 3 分钟权限与安全Android 14沙箱UIDSELinux
应用沙箱与 UID:进程隔离的基础
上一章建立了四层模型。这一章进入第一层,回答:应用为什么不能互相读数据,UID、 独立进程与 SELinux 标签如何共同构成沙箱?
UID 是进程隔离的第一道边界
| 常见误解 | 正确理解 |
|---|---|
| 沙箱就是“每个应用一个进程” | 进程隔离只是表象,内核按 UID 限制资源与文件访问才是本质 |
| 应用可以随便读别的应用文件 | 文件按 UID/权限隔离,SELinux 再叠加一层 |
| 沙箱一成不变 | 共享 UID、系统应用、adb 等都有不同的边界 |
核心结论
- 每个应用默认获得独立 UID,内核据此隔离进程与文件。
- 应用数据目录
/data/user/<userId>/<pkg>/的访问按 UID 授权。 - SELinux 标签(domain)在 UID 之上追加强制约束。
- 共享 UID(shareUID)与系统应用会扩大边界,需谨慎。
- 多用户下每个用户一份数据,UID 与用户维度叠加。
沙箱主链
逐步解释:
- 安装分配 UID:包安装时获得唯一 UID。
- 准备数据:创建应用数据目录并打上 SELinux 标签。
- 运行隔离:独立进程 + UID 文件授权 + domain 约束。
- 边界扩展:共享 UID、系统特权、多用户各自调整边界。
共享 UID 为什么危险
sharedUserId 让多个包共用同一 UID,意味着它们可以互相访问数据、拥有相同权限。 历史上它是合法的多包协作方案,但也显著扩大了攻击面;新项目优先用签名级权限或 Provider 授权替代。
源码证据
阅读目标:确认 UID 分配与目录隔离。
正文指针:PackageManagerService.java 中 UID 分配;Environment.java 的数据目录 路径;packages.list 记录包与 UID 对应。
// PackageManagerService.java(伪代码,行号以 r75 为准)
int assignUid(String packageName) {
// 分配独立 UID(或复用 sharedUserId)
}这段代码证明: UID 由包管理在安装时分配;之后进程、文件与 SELinux 都围绕这个 身份工作。/data/user/<userId>/<pkg>/ 的权限也基于 UID。
验证与排障
环境:开发设备或模拟器;查看 UID 不需要 root。
adb shell dumpsys package <包名> | grep -E "userId|sharedUser"
adb shell ls -ln /data/user/0/<包名>/ 2>/dev/null || adb shell su 0 ls -ln /data/user/0/<包名>/
adb shell cat /data/system/packages.list | grep <包名>先确认包的 UID 与是否共享 UID;再看数据目录属主;最后用 SELinux 标签判断有没有被 策略限制。“别的应用能读我数据”这类问题,先查 UID 隔离是否被破坏。
常见误区
- “进程数 = 沙箱数”:共享 UID 下多进程同一沙箱。
- “沙箱只靠 UID”:SELinux 标签是第二道墙。
- “系统应用不受沙箱约束”:系统应用特权更大,但仍在 SELinux 策略内。
- “多用户共用一份数据”:每个用户一份
/data/user/<userId>/。
延伸问题
- shareUID 与签名级权限的关系是什么?
- SELinux domain 与 UID 在隔离中各承担什么?
- adb/shell 的沙箱边界是什么?
- 多用户切换时 UID 如何保持不变、数据如何隔离?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
PackageManagerService.java | UID 分配 | 沙箱身份 |
Environment.java | 数据目录 | 用户隔离 |
packages.list | 包-UID 映射 | 排障入口 |
| SELinux 标签 | 应用 domain | 强制约束 |
公共路径:frameworks/base/services/core/java/com/android/server/pm/ 与 frameworks/base/core/java/android/os/。行号以 r75 检索为准。
跨出 UID 边界后的能力授权,见 权限安全模型。