每个 Android 应用都有独立的 UID,这带来了哪些安全优势?

解读

面试官想确认两点:

  1. 你是否真正理解“UID=沙箱边界”这一 Android 安全模型的基石;
  2. 你能否把 Linux UID/GID、文件系统、内核层与上层权限声明、组件隔离串成一条线,体现“纵深防御”思维。
    国内面试场景里,这道题常作为“安全类”题目的起手式,答得浅会被追问“还能再细化吗?”,答得偏又容易绕到 SELinux 或 TEE 跑题。因此,回答要“先内核、后框架、再攻击面”,层层递进,用中文关键词把“沙箱”“隔离”“攻击面”“可控”说透,既展示底层功底,也体现对国内定制 ROM、多开、插件化等灰色场景的对抗意识。

知识点

  1. Linux UID/GID 与 Kernel 层 DAC(Discretionary Access Control)
  2. /data/data/包名、/data/app/包名、/data/user_de/0/包名 三大私有目录的 inode 权限 700
  3. procfs、sysfs、cgroups 对 UID 的可见性隔离(防止读 /proc/pid/status、/proc/net/tcp 等)
  4. Binder IPC 里 getCallingUid() 的“身份令牌”作用,决定 AIDL 接口是否返回敏感数据
  5. 共享 UID、多进程、插件化框架对 UID 模型的破坏与厂商加固方案(如华为沙箱 fork 检测、小米多开隔离)
  6. 国内生态下的典型攻击面:
    • 序列化 Parcel 错误匹配导致 UID 伪造
    • 调试器 ptrace 同一 UID 进程
    • 恶意 app 申请 sharedUserId="android.uid.system" 签名校验绕过
  7. 纵深防御:UID 隔离 + SELinux MAC + 签名权限 + 应用沙箱 + 用户组 GID 补充(如 sdcard_rw、net_raw)

答案

“独立 UID”是 Android 把 Linux 多用户机制嫁接到单用户手机上的神来之笔,核心优势可归纳为四点:

  1. 文件系统级强制隔离
    应用在安装时 PackageManagerService 为其分配唯一 UID,内核层通过 DAC 把 /data/data/包名 目录置为 700,任何其他 UID(包括 shell、system_server)若无 CAP_DAC_OVERRIDE 都无法直接读写,天然阻断数据窃取与配置文件篡改。
  2. 进程空间与内核信息隔离
    同一 UID 才能 ptrace 调试;/proc/pid/ 目录权限 700,恶意 app 无法枚举其他进程加载的 so、fd、内存映射,显著降低注入、ROP/RET 攻击面。
  3. IPC 身份令牌不可伪造
    Binder 驱动在每次跨进程调用时把发送方 UID/PID 写死在内核层,接收方通过 checkCallingPermission() 即可按 UID 映射签名权限,系统服务无需信任上层传入的“包名”字符串,彻底杜绝了中间人假冒。
  4. 权限与攻击面最小化
    独立 UID 使得“能力”粒度细化到应用级:
    • 不需要的 GID(如 inet、camera、media)默认不加入;
    • 即使利用本地提权漏洞拿到 root,仍需绕过 SELinux 域转换才能访问其他 UID 数据;
    • 国内定制 ROM 的“多开/分身”功能通过 UserHandle 隔离 + 动态挂载 700 目录,仍复用同一套 UID 模型,保证分身数据互不穿透。

综上,独立 UID 把“沙箱”从 Java 层口号变成内核强制执行的事实,既简化了应用开发者的安全负担,也为系统提供可控、可审计、可回滚的最小权限边界。

拓展思考

  1. 如果厂商把多个系统预装 App 设为 sharedUserId="android.uid.phone",攻击面会如何扩大?你会怎样在 Code Review 里发现风险?
  2. 在 TargetSdk 30 及以上,分区存储(Scoped Storage)把 /sdcard 访问权从 GID 转向 FUSE+UID 映射,如何与 MediaProvider 结合防止“相册泄露”?
  3. 插件化框架通过动态加载 dex 运行在宿主进程,天然共享 UID,怎样利用 JNI 层“匿名共享内存”+“fd 重定向”做二次隔离,避免恶意插件读取宿主私有数据库?
  4. 国内某些 ROM 允许“双开”微信,实质是创建二级用户(UserHandle),微信仍保持原 UID,只是数据目录被 bind mount 到 /data/user/10/,如何验证该方案在 adb shell 下无法通过 /proc/self/mountinfo 逃逸?
  5. 当应用使用 android:sharedUserMaxSdkVersion="29" 逐步下线共享 UID 时,怎样通过 Gradle + AGP 7.x 在 CI 阶段强制校验签名证书,防止旧模块被误合并?