1x00 - UID/GID 隔离深度解析
Android 安全模型的核心建立在 Linux 的 DAC (Discretionary Access Control, 自主访问控制) 机制之上。在传统的 Linux 系统中,UID (User ID) 用于区分不同的用户;而在 Android 中,UID 被创造性地用于区分不同的应用程序。
这就是所谓的“应用沙箱”的第一道防线。
1. Android ID (AID) 机制
在 Android 中,每一个安装的应用都会被分配一个唯一的 UID,称为 AID (Android ID)。
1.1 AID 的分配范围
Android 在 system/core/include/private/android_filesystem_config.h 中定义了 AID 的分配规则:
- Root 与保留 UID (0 - 999): 包括
root(0) 及部分保留用途。 - 核心系统服务 UID (1000 - 9999): 包括
system(1000)、radio(1001)、bluetooth(1002)、shell(2000) 等 AID 定义。 - App UID (10000 - 19999): 分配给普通安装的应用。
- Isolated UID (99000 - 99999): 用于隔离进程(如 Chrome 的渲染进程),这些进程权限极低。
1.2 映射关系
当应用安装时,PackageManagerService (PMS) 会为其分配一个未使用的 AID。这个映射关系持久化在 /data/system/packages.xml 中:
<package name="com.example.app" codePath="/data/app/..." ... userId="10123">
<sigs count="1">
<cert index="0" key="..." />
</sigs>
</package>在 Linux 内核看来,com.example.app 就是一个名为 u0_a123 的用户(假设是 User 0)。
2. installd 与目录权限设置
应用安装后,系统需要为其创建私有数据目录(/data/data/<pkg_name>)。这个过程是由高权限守护进程 installd 完成的。
2.1 源码简析
在 frameworks/native/cmds/installd/InstalldNativeService.cpp 中,createAppData 函数负责设置目录权限:
// 简化代码片段
auto path = create_data_user_ce_package_path(uuid, userId, pkgname);
if (fs_prepare_dir_strict(path, 0751, uid, gid) != 0) {
return error("Failed to prepare " + path);
}- 权限 0751: 只有所有者(该应用)拥有读写执行权限,其他用户(其他应用)只有执行权限(用于进入目录,但不能列出文件)。
- 所有者 (uid/gid): 设置为该应用被分配的 AID。
这种机制确保了即使应用 A 知道应用 B 的路径,也无法读取其内容,因为 Linux 内核会在文件系统层级拦截越权访问。
3. SharedUserId 的“原罪”
在 Android 早期,为了方便同一开发者的多个应用共享数据,引入了 android:sharedUserId 属性。
3.1 机制
如果两个应用声明了相同的 sharedUserId 并且使用相同的证书签名,它们将共享同一个 UID。这意味着:
- 它们可以互相访问对方的私有数据目录。
- 它们运行在同一个进程中(如果指定了相同的
android:process)。
3.2 安全风险
sharedUserId 极大地破坏了沙箱的隔离性。如果其中一个应用存在漏洞(如文件遍历),攻击者可以轻易获取另一个应用的所有敏感数据。
注意:AOSP 官方口径是从 Android 13 起废弃“共用 UID”(Shared UID)。实际设备/应用是否还能使用,取决于目标版本与兼容性策略;跨应用共享能力更推荐使用 ContentProvider/Service 等显式 IPC 机制。
5. 边界与局限性:为什么仅有 UID 隔离是不够的?
虽然 UID/GID 提供了基础的隔离,但在复杂的 Android 环境中,它并非万能。
5.1 Root 用户的特殊性
在 Linux 传统权限模型中,UID 0 (root) 拥有最高权限,可以绕过所有的 DAC 检查。
- 背景: 许多系统守护进程(如
vold,netd)需要以 root 身份运行以执行底层操作。 - 风险: 一旦 root 进程被攻破,整个 DAC 隔离将形同虚设。
- 结果: Android 引入了 SELinux (MAC) 来限制 root 进程。即使是 root,也只能访问其安全策略(Policy)允许的资源。此外,通过 Capabilities 机制,系统可以只赋予进程必要的特权,而非完整的 root 权限。
5.2 权限放宽的风险
如果应用开发者错误地使用了 chmod 777 或 Context.MODE_WORLD_READABLE(已废弃),会发生什么?
- 分析: 虽然应用可以放宽其私有目录的 DAC 权限,但现代 Android 还有其他保护层。
- 结果:
- 父目录限制:
/data/data目录通常对普通应用不可列出。 - SELinux 拦截: 即使文件权限允许,SELinux 策略通常也会禁止一个应用进程访问另一个应用标签(Label)下的文件。
- API 限制: 从 Android 7.0 开始,系统严禁通过
FileUriExposedException传递私有文件,强制开发者使用FileProvider。
- 父目录限制:
6. 总结
UID/GID 隔离是 Android 沙箱的底层基石,利用 Linux 成熟的 DAC 机制以极低性能开销实现应用间数据隔离。
然而,随着系统复杂度的增加,单纯依靠 UID 已经不足以应对现代安全挑战——这也是为什么 Android 后来引入了 SELinux (MAC)、Capabilities 和 Scoped Storage 的原因。对安全研究员而言,理解 UID 映射与 installd 的权限设置逻辑,是分析应用数据泄露和提权漏洞的起点。