系列目录 :第一篇:功能演进与系统架构总览 | 第二篇:核心数据结构深度剖析 | 第三篇:用户创建与删除流程全链路分析 | 第四篇:用户切换机制源码剖析 | 第五篇:应用安装与管理隔离机制 | 第六篇:数据隔离与存储机制详解 | 第七篇:系统服务的多用户感知处理 | [第八篇:广播分发与 Intent 路由的用户隔离](#第八篇:广播分发与 Intent 路由的用户隔离) | 第九篇:权限系统与多用户的交互机制 | [第十篇:Work Profile 企业模式与开发实战指南](#第十篇:Work Profile 企业模式与开发实战指南)
一、核心问题
- 一个 Binder API 的
userId参数由调用方传入------服务端怎么防止它"冒充"别的用户? - 通知、设置、AppOps 这类服务,状态到底是"按用户分桶"还是"全局一份"?
- 为什么 AOSP 7 没有统一的"多用户基类"?
这些问题的答案决定了系统服务是多用户"天然隔离"还是"漏洞百出"。本篇按"调用方识别 → 跨用户权限 → 典型服务实践 → 状态分桶"展开,修正原稿中整段虚构的代码(AMS 的 handleUserSwitch、NMS 的 mNotificationRecords HashMap、WallpaperManager 的 setWallpaper(name, which, outStream) 均不存在)。
二、识别调用方:UID → 用户
Binder 调用天然携带 pid/uid:
java
// Binder.java
public static int getCallingUid() {
return nativeGetCallingUid();
}
// UserHandle.java
public static @UserIdInt int getUserId(int uid) {
if (MU_ENABLED) {
return uid / PER_USER_RANGE;
}
return USER_SYSTEM;
}
关键设计 :
uid由内核在 Binder 事务时填充,调用方无法伪造 ------这是多用户识别的信任根。服务端的防御重点不是"识别",而是"限制":调用方声明的userId参数必须经过校验。
三、跨用户权限:两套校验路径
3.1 handleIncomingUser:用户归属校验
大多数服务用 ActivityManager.handleIncomingUser 校验"调用方是否有权代表这个 userId":
源码路径 :frameworks/base/core/java/android/app/ActivityManager.java
java
public class ActivityManager {
// ...
public static int handleIncomingUser(int callingPid, int callingUid, int userId,
boolean allowAll, boolean requireFull, String name, String callerPackage) {
if (UserHandle.getUserId(callingUid) == userId) {
return userId; // 自己用户 → 直接放行
}
try {
return ActivityManagerNative.getDefault().handleIncomingUser(callingPid,
callingUid, userId, allowAll, requireFull, name, callerPackage);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
AMS 端委托给 UserController:
java
// ActivityManagerService.java
public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
boolean requireFull, String name, String callerPackage) {
return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
}
UserController.handleIncomingUser 的核心规则:
userId == 调用方用户 → 放行
userId == USER_ALL / USER_CURRENT → 需 INTERACT_ACROSS_USERS(_FULL)
调用方是 system/shell → 放行(shell 受 DISALLOW_DEBUGGING_FEATURES 限制)
requireFull 且只有 INTERACT_ACROSS_USERS → 拒绝
否则 → 需 INTERACT_ACROSS_USERS(部分)或 _FULL(全部)
关键设计 :这是声明式 校验------API 带
userId参数时,服务端第一行就该是handleIncomingUser。它同时处理了"特殊值语义"(USER_ALL/USER_CURRENT)和权限分层,是 AOSP 7 最普遍的用户校验入口。
3.2 enforceCrossUserPermission:PMS 专用校验
源码路径 :frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
java
void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
boolean checkShell, String message) {
if (userId < 0) {
throw new IllegalArgumentException("Invalid userId " + userId);
}
if (checkShell) {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
}
if (userId == UserHandle.getUserId(callingUid)) return;
if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
if (requireFullPermission) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
} else {
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
} catch (SecurityException se) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS, message);
}
}
}
}
关键设计 :PMS 没有复用
handleIncomingUser,而是自己实现了一套"先 FULL 后降级"的校验(requireFullPermission=false时 FULL 不满足则尝试普通 INTERACT_ACROSS_USERS)。两套并存的代价是语义不完全一致------读 PMS 代码时别被"应该用 handleIncomingUser"的直觉带偏。
3.3 跨用户权限矩阵
| 权限 | 级别 | 能力 |
|---|---|---|
INTERACT_ACROSS_USERS |
signature|privileged | 读他用户状态、部分操作 |
INTERACT_ACROSS_USERS_FULL |
signature | 全量跨用户操作 |
MANAGE_USERS |
signature | 用户生命周期(建/删/广播接收) |
关键设计 :三方 App 拿不到任何跨用户权限------多用户隔离对三方是"硬边界",只有系统签名组件能越界。
四、典型服务的用户感知实践
4.1 NotificationManagerService:全局列表 + 按 userId 过滤
原稿描述 NMS 用 HashMap<Integer, ArrayList<NotificationRecord>> 按用户分桶------实际不是 。AOSP 7 的 NMS 是单列表:
源码路径 :frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
java
public class NotificationManagerService extends SystemService
implements IStatusBarService.Stub {
// ...
final Object mNotificationLock = new Object();
final ArrayList<NotificationRecord> mNotificationList =
new ArrayList<NotificationRecord>();
final ArrayMap<String, NotificationRecord> mNotificationsByKey =
new ArrayMap<String, NotificationRecord>();
final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
// NotificationManagerInternal 实现
public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int[] idReceived, int userId) {
enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
idReceived, userId);
}
}
入队时的用户校验(enqueueNotificationInternal):
java
// NotificationManagerService.java
private void enqueueNotificationInternal(final String pkg, final String opPkg,
final int callingPid, final int callingUid, final String tag, final int id,
final Notification notification, int[] idOut, int incomingUserId) {
checkCallerIsSystemOrSameApp(pkg);
// ...
final int userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
final UserHandle user = new UserHandle(userId);
// ...
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, callingUid, callingPid, 0, notification, user);
// ...
}
NotificationRecord 持有 StatusBarNotification(内含 UserHandle),过滤靠 mAutobundledSummaries 的 key(userId)和查询时的 userId 匹配:
java
final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
// ...
final ArrayMap<String, String> summaries = mAutobundledSummaries.get(r.sbn.getUserId());
关键设计 :通知是"全局存储 + 查询时过滤"------每个
NotificationRecord自带 userId,StatusBar 显示前按当前用户过滤。这比"按用户分桶"更简单(不用同步多桶),代价是查询要遍历。enqueueNotification走handleIncomingUser保证"只能给自己用户发"(除非 FULL 权限)。
4.2 SettingsProvider:per-user 数据库
SettingsProvider 的隔离是存储级 的:每个用户一个 SQLite 文件 /data/system/users/<id>/settings_user.xml(或独立 db),API 带 userId 参数时按用户路由到对应存储。这是"按用户分桶"在持久化层的落地------与 NMS 的"全局列表"形成两种模式对照。
4.3 AppOpsService:per-user 操作状态
AppOpsService 的操作(如"后台定位")状态按 uid(含用户维度)存储,用户删除时 mAppOpsService.removeUser(userId) 清理(第三篇删除链路已见)。它的特殊值处理:USER_ALL 聚合所有用户状态。
关键设计:三种服务、三种隔离模式------
- 全局列表 + userId 字段(NMS):写多读少、查询过滤;
- per-user 存储(SettingsProvider):读写都按用户路由;
- per-uid 状态 + 删除清理 (AppOps):UID 天然含用户维度,删除时批量清。
选型取决于"按用户查询频率"和"删除/重置语义"。
4.4 ContentProvider:进程即用户
ContentProvider 的隔离靠进程模型 :provider 进程以自己的 UID(含用户)运行,ActivityManagerService.getContentProvider 查找时按调用方 userId 解析(resolveContentProvider(authority, ..., userId))。不同用户的同一 provider 是不同进程实例 (除非 FLAG_SINGLE_USER,后者需 INTERACT_ACROSS_USERS 权限):
java
// ActivityManagerService.java
boolean isSingleton(String componentProcessName, ApplicationInfo aInfo,
String className, int flags) {
if (UserHandle.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) {
if ((flags & ServiceInfo.FLAG_SINGLE_USER) != 0) {
if (ActivityManager.checkUidPermission(
INTERACT_ACROSS_USERS, aInfo.uid) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(msg); // 三方不能声明 SINGLE_USER
}
return true;
}
}
// ...
}
关键设计 :
FLAG_SINGLE_USER(所有用户共享一个 provider 进程)是敏感声明------三方应用禁止,只有持有INTERACT_ACROSS_USERS的系统组件能声明。默认行为是"每用户一个进程",隔离最强。
五、没有统一基类:为什么
AOSP 7 没有 MultiUserService 之类的抽象基类,每个服务自己处理 userId 参数。原因是:
- API 签名各异 :有的按
userId分桶(SettingsProvider),有的全局过滤(NMS),有的按 uid(AppOps)------统一抽象反而僵化; - 校验入口统一 :虽然处理模式不同,但校验入口收敛在
handleIncomingUser/enforceCrossUserPermission两处,保证了权限语义一致; - 服务发现靠 LocalServices :服务间调用通过
LocalServices.getService(XxxInternal.class),接口方法自带 userId 参数,绕过 Binder 但同样做校验。
关键设计 :AOSP 7 的多用户是"校验集中、处理分散"架构------权限语义靠两个公共方法统一,状态管理交给各服务按场景选择。这比"强制基类"更灵活,但要求每个新服务自觉调用校验入口。
六、调试
bash
# 查看某服务的用户维度状态
adb shell dumpsys notification | grep -i user
adb shell dumpsys settings | grep -i user
# 查看 ContentProvider 进程的用户归属
adb shell ps -A | grep <package>
七、本篇总结
- 识别 :Binder uid 内核填充不可伪造,
UserHandle.getUserId纯数学换算; - 校验 :
handleIncomingUser(普遍)+enforceCrossUserPermission(PMS 专用)双入口,权限分INTERACT_ACROSS_USERS/_FULL两档; - 隔离模式:全局列表+过滤(NMS)、per-user 存储(Settings)、per-uid 状态(AppOps)三种并存;
- Provider :进程即隔离单元,
FLAG_SINGLE_USER需 FULL 权限; - 架构:无统一基类,"校验集中、处理分散"。
下一篇进入广播:sendBroadcastAsUser 的 userId 参数如何决定"谁收得到",有序广播与注册用户广播在多用户下的路由差异。