3. Android安全模型
Android 的安全模型并不是单一机制,而是由内核层、系统服务层和应用框架层共同叠加形成的一套纵深防御体系。理解这套体系,是后续做安全审计与故障定位的基础:当应用出现“无法访问文件”“权限被拒绝”“安装失败”“跨进程调用异常”等问题时,根因往往就落在沙箱、UID/GID、SELinux 或签名校验中的某一环。
一、核心原理:从 UID/GID 到沙箱
Android 基于 Linux 内核,但并没有让每个应用直接共享一个用户身份。相反,系统在安装应用时为每个应用分配一个独立的 Linux UID(通常从 10000 开始),并配套分配 GID。这个 UID 就是应用在系统层面的“身份证”。应用进程启动后,以该 UID 运行,其私有数据目录 /data/data/<package> 的属主也是该 UID,权限通常为 700。于是,一个应用天然无法读写另一个应用的私有目录,这就是最基础的沙箱机制。
沙箱并不等于“完全隔离”。Android 还通过共享 UID、共享 GID、权限组等方式,让应用在受控条件下访问系统资源。例如,应用申请 INTERNET 权限后,会被加入 inet 组;申请存储权限后,会获得对应 GID 的访问能力。因此,审计时要同时看 UID、GID 和权限声明,而不是只看其中一项。
二、权限模型:从安装时授权到运行时授权
Android 权限模型经历了明显演进。早期 Android 6.0 之前,权限在安装时一次性授予;Android 6.0 引入运行时权限后,危险权限必须在运行时动态申请。权限按保护级别可分为 normal、dangerous、signature、signatureOrSystem 等。normal 权限通常自动授予,dangerous 权限需要用户确认,signature 权限只有与系统或声明方签名一致的应用才能获得。
权限检查发生在多个位置:应用框架层的 checkPermission、系统服务中的 enforceCallingPermission、Binder 调用时的 callingUid 校验,以及底层文件访问时的 Linux DAC 检查。审计时如果只看到 Manifest 中声明了权限,并不代表运行时一定拥有;还要确认权限是否被授予、调用方 UID 是否匹配、系统服务是否做了额外校验。
adb shell dumpsys package com.example.app | grep -A 20 "requested permissions"
adb shell pm list permissions -g -d
adb shell cmd appops get com.example.app
三、SELinux:强制访问控制的关键一层
即使 UID/GID 和权限都正确,SELinux 仍可能拒绝访问。Android 从 4.3 开始引入 SELinux,从 5.0 起进入 enforcing 模式。SELinux 使用类型强制(TE)规则,为进程分配域(domain),为文件、套接字、Binder 等对象分配类型(type)。只有策略中明确允许的“域 → 类型”访问才会被放行。
这意味着,一个应用即使拥有某个文件的 Linux 读写权限,如果 SELinux 策略不允许其域访问该类型,操作仍会失败。典型日志是 avc: denied。故障定位时,应优先查看 dmesg 或 logcat 中的 avc 拒绝记录,再结合 ls -Z 查看对象的安全上下文。
adb shell dmesg | grep avc
adb shell ls -Z /data/data/com.example.app
adb shell ps -Z | grep com.example.app
四、应用签名、UID/GID 与进程隔离的协同关系
应用签名决定应用的身份连续性和升级资格。Android 要求同一包名的应用升级时签名必须一致,否则安装失败。签名还影响 signature 权限授予、共享 UID 配置以及部分系统服务的调用资格。共享 UID 通过 android:sharedUserId 实现,但该属性已在新版本中逐步废弃,因为它会削弱沙箱边界。
进程隔离则体现在:每个应用默认运行在独立进程中,拥有独立 UID、独立 Dalvik/ART 虚拟机实例和独立文件目录。Binder 是跨进程通信的主通道,但 Binder 调用会携带调用方 UID/PID,系统服务据此做权限判断。若调用方伪造身份或绕过 Binder 直接访问底层接口,就会触发安全审计关注。
五、案例与排障:一次“文件写入失败”的定位过程
假设某应用在 /data/data/com.example.app/files 下写文件失败。排查顺序建议如下:
- 确认进程 UID:
adb shell ps -A | grep com.example.app,查看是否与目录属主一致。 - 确认目录权限:
adb shell ls -ld /data/data/com.example.app/files,检查是否被错误改为755或属主异常。 - 确认 SELinux 上下文:
adb shell ls -Z,检查文件类型是否为app_data_file。 - 查看 avc 拒绝:
adb shell dmesg | grep avc,确认是否存在域与类型不匹配。 - 检查是否跨用户或跨应用访问:多用户场景下 UID 会带用户前缀,如
u10_a123。
六、审计要点清单
| 审计对象 | 关键检查点 | 常用命令/日志 |
|---|---|---|
| UID/GID | 应用 UID 是否独立、目录属主是否匹配、是否共享 UID | ps -A、ls -l、dumpsys package |
| 权限模型 | 危险权限是否运行时授予、AppOps 是否限制、signature 权限是否匹配 | pm list permissions、cmd appops get |
| SELinux | 进程域与对象类型是否匹配、是否处于 enforcing、avc 拒绝记录 | ls -Z、ps -Z、dmesg | grep avc |
| 应用签名 | 升级签名一致性、签名权限授予、是否被重打包 | apksigner verify、dumpsys package |
| 进程隔离 | 是否独立进程、Binder 调用方 UID 是否可信、是否暴露组件 | dumpsys activity processes、Binder 日志 |
总体来看,Android 安全模型的核心思想是“默认隔离、按需授权、多层校验”。沙箱提供基础边界,权限模型控制能力授予,SELinux 提供强制约束,签名保证身份可信,UID/GID 与进程隔离则把这些机制落到具体进程和文件对象上。做安全审计时,沿着“应用 → 框架 → 内核 → 数据对象”这条链路逐层验证,才能快速定位真正的拒绝点,而不是停留在表面报错信息上。