Add APIs for interacting across users.
- Expose the existing Context.sendBroadcast() as
Context.sendBroadcastAsUser().
- Add new android:singleUser attribute for services.
- Add new INTERACT_ACROSS_USERS_FULL permission for full
system-level access to cross-user interface (allows
sendBroadcastAsUser() to send to any receiver).
- Add new INTERACT_ACROSS_USERS_FULL permission for
more restricted cross-user interaction: this is required
for android:singleUser, and allows you to use
sendBroadcastAsUser() but only to send to your own
receivers.
Change-Id: I0de88f6718e9505f4de72e3f45d29c0f503b76e9
diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java
index 46b968a..48f967c 100644
--- a/services/java/com/android/server/AppWidgetServiceImpl.java
+++ b/services/java/com/android/server/AppWidgetServiceImpl.java
@@ -499,7 +499,7 @@
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
intent.setComponent(p.info.provider);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
- mContext.sendBroadcast(intent, mUserId);
+ mContext.sendBroadcastToUser(intent, mUserId);
if (p.instances.size() == 0) {
// cancel the future updates
cancelBroadcasts(p);
@@ -507,7 +507,7 @@
// send the broacast saying that the provider is not in use any more
intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
intent.setComponent(p.info.provider);
- mContext.sendBroadcast(intent, mUserId);
+ mContext.sendBroadcastToUser(intent, mUserId);
}
}
}
@@ -880,7 +880,7 @@
intent.setComponent(p.info.provider);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
- mContext.sendBroadcast(intent, mUserId);
+ mContext.sendBroadcastToUser(intent, mUserId);
}
}
@@ -1205,7 +1205,7 @@
void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.info.provider);
- mContext.sendBroadcast(intent, mUserId);
+ mContext.sendBroadcastToUser(intent, mUserId);
}
void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) {
@@ -1213,7 +1213,7 @@
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
intent.setComponent(p.info.provider);
- mContext.sendBroadcast(intent, mUserId);
+ mContext.sendBroadcastToUser(intent, mUserId);
}
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 26ebb98..3b4200a 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -11140,9 +11140,8 @@
private ServiceLookupResult retrieveServiceLocked(Intent service,
String resolvedType, int callingPid, int callingUid, int userId) {
ServiceRecord r = null;
- if (DEBUG_SERVICE)
- Slog.v(TAG, "retrieveServiceLocked: " + service + " type=" + resolvedType
- + " callingUid=" + callingUid);
+ if (DEBUG_SERVICE) Slog.v(TAG, "retrieveServiceLocked: " + service
+ + " type=" + resolvedType + " callingUid=" + callingUid);
if (service.getComponent() != null) {
r = mServiceMap.getServiceByName(service.getComponent(), userId);
@@ -11163,14 +11162,29 @@
": not found");
return null;
}
- if (userId > 0) {
- if (isSingleton(sInfo.processName, sInfo.applicationInfo)) {
- userId = 0;
- }
- sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, userId);
- }
ComponentName name = new ComponentName(
sInfo.applicationInfo.packageName, sInfo.name);
+ if (userId > 0) {
+ if (isSingleton(sInfo.processName, sInfo.applicationInfo)
+ || (sInfo.flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
+ userId = 0;
+ } else if ((sInfo.flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
+ if (checkComponentPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ callingPid, callingUid, -1, true)
+ == PackageManager.PERMISSION_GRANTED) {
+ userId = 0;
+ } else {
+ String msg = "Permission Denial: Service " + name
+ + " requests FLAG_SINGLE_USER, but app does not hold "
+ + android.Manifest.permission.INTERACT_ACROSS_USERS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+ sInfo = new ServiceInfo(sInfo);
+ sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, userId);
+ }
r = mServiceMap.getServiceByName(name, userId);
if (r == null) {
Intent.FilterComparison filter = new Intent.FilterComparison(
@@ -11531,11 +11545,11 @@
}
final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
- final String appName = r.processName;
+ final String procName = r.processName;
ProcessRecord app;
if (!isolated) {
- app = getProcessRecordLocked(appName, r.appInfo.uid);
+ app = getProcessRecordLocked(procName, r.appInfo.uid);
if (DEBUG_MU)
Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app);
if (app != null && app.thread != null) {
@@ -11563,7 +11577,7 @@
// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
if (app == null) {
- if ((app=startProcessLocked(appName, r.appInfo, true, intentFlags,
+ if ((app=startProcessLocked(procName, r.appInfo, true, intentFlags,
"service", r.name, false, isolated)) == null) {
Slog.w(TAG, "Unable to launch app "
+ r.appInfo.packageName + "/"
@@ -12668,6 +12682,7 @@
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter, String permission) {
enforceNotIsolatedCaller("registerReceiver");
+ int callingUid;
synchronized(this) {
ProcessRecord callerApp = null;
if (caller != null) {
@@ -12683,8 +12698,10 @@
throw new SecurityException("Given caller package " + callerPackage
+ " is not running in process " + callerApp);
}
+ callingUid = callerApp.info.uid;
} else {
callerPackage = null;
+ callingUid = Binder.getCallingUid();
}
List allSticky = null;
@@ -12729,7 +12746,8 @@
}
mRegisteredReceivers.put(receiver.asBinder(), rl);
}
- BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, permission);
+ BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
+ permission, callingUid);
rl.add(bf);
if (!bf.debugCheck()) {
Slog.w(TAG, "==> For Dynamic broadast");
@@ -12748,7 +12766,7 @@
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
null, -1, -1, null, receivers, null, 0, null, null,
- false, true, true);
+ false, true, true, false);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
@@ -12840,7 +12858,34 @@
if ((resultTo != null) && !ordered) {
Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
}
-
+
+ boolean onlySendToCaller = false;
+
+ // If the caller is trying to send this broadcast to a different
+ // user, verify that is allowed.
+ if (UserId.getUserId(callingUid) != userId) {
+ if (checkComponentPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ callingPid, callingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED) {
+ if (checkComponentPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ callingPid, callingUid, -1, true)
+ == PackageManager.PERMISSION_GRANTED) {
+ onlySendToCaller = true;
+ } else {
+ String msg = "Permission Denial: " + intent.getAction()
+ + " broadcast from " + callerPackage
+ + " asks to send as user " + userId
+ + " but is calling from user " + UserId.getUserId(callingUid)
+ + "; this requires "
+ + android.Manifest.permission.INTERACT_ACROSS_USERS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+ }
+
// Handle special intents: if this broadcast is from the package
// manager about a package being removed, we need to remove all of
// its activities from the history stack.
@@ -13042,7 +13087,7 @@
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
callerPackage, callingPid, callingUid, requiredPermission,
registeredReceivers, resultTo, resultCode, resultData, map,
- ordered, sticky, false);
+ ordered, sticky, false, onlySendToCaller);
if (DEBUG_BROADCAST) Slog.v(
TAG, "Enqueueing parallel broadcast " + r);
final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
@@ -13132,7 +13177,7 @@
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
callerPackage, callingPid, callingUid, requiredPermission,
receivers, resultTo, resultCode, resultData, map, ordered,
- sticky, false);
+ sticky, false, onlySendToCaller);
if (DEBUG_BROADCAST) Slog.v(
TAG, "Enqueueing ordered broadcast " + r
+ ": prev had " + queue.mOrderedBroadcasts.size());
diff --git a/services/java/com/android/server/am/BroadcastFilter.java b/services/java/com/android/server/am/BroadcastFilter.java
index b49bc22..4e6d0fa 100644
--- a/services/java/com/android/server/am/BroadcastFilter.java
+++ b/services/java/com/android/server/am/BroadcastFilter.java
@@ -27,13 +27,15 @@
final ReceiverList receiverList;
final String packageName;
final String requiredPermission;
+ final int owningUid;
BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
- String _packageName, String _requiredPermission) {
+ String _packageName, String _requiredPermission, int _owningUid) {
super(_filter);
receiverList = _receiverList;
packageName = _packageName;
requiredPermission = _requiredPermission;
+ owningUid = _owningUid;
}
public void dump(PrintWriter pw, String prefix) {
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index 47b8c0a..c6d46fc 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -369,7 +369,17 @@
private final void deliverToRegisteredReceiverLocked(BroadcastRecord r,
BroadcastFilter filter, boolean ordered) {
boolean skip = false;
- if (filter.requiredPermission != null) {
+ if (r.onlySendToCaller) {
+ if (!UserId.isSameApp(r.callingUid, filter.owningUid)) {
+ Slog.w(TAG, "Permission Denial: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid="
+ + r.callingPid + ", uid=" + r.callingUid + ")"
+ + " not allowed to go to different app " + filter.owningUid);
+ skip = true;
+ }
+ }
+ if (!skip && filter.requiredPermission != null) {
int perm = mService.checkComponentPermission(filter.requiredPermission,
r.callingPid, r.callingUid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
@@ -382,7 +392,7 @@
skip = true;
}
}
- if (r.requiredPermission != null) {
+ if (!skip && r.requiredPermission != null) {
int perm = mService.checkComponentPermission(r.requiredPermission,
filter.receiverList.pid, filter.receiverList.uid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
@@ -651,6 +661,17 @@
(ResolveInfo)nextReceiver;
boolean skip = false;
+ if (r.onlySendToCaller) {
+ if (!UserId.isSameApp(r.callingUid, info.activityInfo.applicationInfo.uid)) {
+ Slog.w(TAG, "Permission Denial: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid="
+ + r.callingPid + ", uid=" + r.callingUid + ")"
+ + " not allowed to go to different app "
+ + info.activityInfo.applicationInfo.uid);
+ skip = true;
+ }
+ }
int perm = mService.checkComponentPermission(info.activityInfo.permission,
r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
info.activityInfo.exported);
diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java
index dd560fc..799b609 100644
--- a/services/java/com/android/server/am/BroadcastRecord.java
+++ b/services/java/com/android/server/am/BroadcastRecord.java
@@ -44,6 +44,7 @@
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
final boolean initialSticky; // initial broadcast from register to sticky?
+ final boolean onlySendToCaller; // only allow receipt by sender's components?
final String requiredPermission; // a permission the caller has required
final List receivers; // contains BroadcastFilter and ResolveInfo
IIntentReceiver resultTo; // who receives final result if non-null
@@ -167,7 +168,7 @@
int _callingPid, int _callingUid, String _requiredPermission,
List _receivers, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized,
- boolean _sticky, boolean _initialSticky) {
+ boolean _sticky, boolean _initialSticky, boolean _onlySendToCaller) {
queue = _queue;
intent = _intent;
callerApp = _callerApp;
@@ -183,6 +184,7 @@
ordered = _serialized;
sticky = _sticky;
initialSticky = _initialSticky;
+ onlySendToCaller = _onlySendToCaller;
nextReceiver = 0;
state = IDLE;
}