Allow bindServiceAsUser without _FULL if same app

Previously we allowed bindServiceAsUser in the following situations:
* caller has INTERACT_ACROSS_USERS_FULL
* caller has INTERACT_ACROSS_USERS and is in same profile group
* caller has INTERACT_ACROSS_PROFILES and is in same profile group and is the same app as the service

We now additionally allow:
* caller has INTERACT_ACROSS_USERS and is the same app as the service
(regardless of whether it is in the same profile group)

Bug: 209989958
Test: tested locally that granting the permission allows access to bindServiceAsUser
Test: atest UserControllerTest
Change-Id: I47d6f86fd159aa993971366ee9d7d334644d391e
This commit is contained in:
Adam Bookatz
2022-01-05 12:59:39 -08:00
parent 040d933827
commit a482921588
6 changed files with 170 additions and 31 deletions

View File

@@ -10662,7 +10662,7 @@ package android.content {
method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL", android.Manifest.permission.INTERACT_ACROSS_PROFILES}, conditional=true) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
method @NonNull public int[] checkCallingOrSelfUriPermissions(@NonNull java.util.List<android.net.Uri>, int);

View File

@@ -71,6 +71,9 @@ public abstract class ActivityManagerInternal {
}
// Access modes for handleIncomingUser.
/**
* Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}.
*/
public static final int ALLOW_NON_FULL = 0;
/**
* Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
@@ -78,13 +81,18 @@ public abstract class ActivityManagerInternal {
* Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
*/
public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
/**
* Allows access to a caller only if it has the full
* {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
*/
public static final int ALLOW_FULL_ONLY = 2;
/**
* Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
* or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group.
* Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
* if in the same profile group.
* Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS} is required and suffices
* as in {@link #ALLOW_NON_FULL}.
*/
public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3;
public static final int ALLOW_PROFILES_OR_NON_FULL = 3;
/**
* Returns profile information in free form string in two separate strings.

View File

@@ -3589,10 +3589,18 @@ public abstract class Context {
* Binds to a service in the given {@code user} in the same manner as
* {@link #bindService(Intent, ServiceConnection, int)}.
*
* <p>If the given {@code user} is in the same profile group and the target package is the
* same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is
* sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS}
* for interacting with other users.
* <p>Requires that one of the following conditions are met:
* <ul>
* <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}</li>
* <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_USERS} and is the same
* package as the {@code service} (determined by its component's package) and the Android
* version is at least {@link android.os.Build.VERSION_CODES#S}</li>
* <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_USERS} and is in same
* profile group as the given {@code user}</li>
* <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} and is in same
* profile group as the given {@code user} and is the same package as the {@code service}
* </li>
* </ul>
*
* @param service Identifies the service to connect to. The Intent must
* specify an explicit component name.
@@ -3615,8 +3623,9 @@ public abstract class Context {
@SuppressWarnings("unused")
@RequiresPermission(anyOf = {
android.Manifest.permission.INTERACT_ACROSS_USERS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
android.Manifest.permission.INTERACT_ACROSS_PROFILES
})
}, conditional = true)
public boolean bindServiceAsUser(
@NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags,
@NonNull UserHandle user) {
@@ -3629,7 +3638,11 @@ public abstract class Context {
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
@RequiresPermission(anyOf = {
android.Manifest.permission.INTERACT_ACROSS_USERS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
android.Manifest.permission.INTERACT_ACROSS_PROFILES
}, conditional = true)
@UnsupportedAppUsage(trackingBug = 136728678)
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
Handler handler, UserHandle user) {

View File

@@ -3472,11 +3472,9 @@ public final class ActiveServices {
}
private int getAllowMode(Intent service, @Nullable String callingPackage) {
if (callingPackage == null || service.getComponent() == null) {
return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
}
if (callingPackage.equals(service.getComponent().getPackageName())) {
return ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
if (callingPackage != null && service.getComponent() != null
&& callingPackage.equals(service.getComponent().getPackageName())) {
return ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
} else {
return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
}

View File

@@ -26,10 +26,10 @@ import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
import static android.app.ActivityManager.USER_OP_SUCCESS;
import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
import static android.os.PowerWhitelistManager.REASON_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -2143,11 +2143,10 @@ class UserController implements Handler.Callback {
callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
// If the caller does not have either permission, they are always doomed.
allow = false;
} else if (allowMode == ALLOW_NON_FULL) {
} else if (allowMode == ALLOW_NON_FULL || allowMode == ALLOW_PROFILES_OR_NON_FULL) {
// We are blanket allowing non-full access, you lucky caller!
allow = true;
} else if (allowMode == ALLOW_NON_FULL_IN_PROFILE
|| allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
} else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
// We may or may not allow this depending on whether the two users are
// in the same profile.
allow = isSameProfileGroup;
@@ -2174,12 +2173,13 @@ class UserController implements Handler.Callback {
builder.append("; this requires ");
builder.append(INTERACT_ACROSS_USERS_FULL);
if (allowMode != ALLOW_FULL_ONLY) {
if (allowMode == ALLOW_NON_FULL || isSameProfileGroup) {
if (allowMode == ALLOW_NON_FULL
|| allowMode == ALLOW_PROFILES_OR_NON_FULL
|| (allowMode == ALLOW_NON_FULL_IN_PROFILE && isSameProfileGroup)) {
builder.append(" or ");
builder.append(INTERACT_ACROSS_USERS);
}
if (isSameProfileGroup
&& allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
if (isSameProfileGroup && allowMode == ALLOW_PROFILES_OR_NON_FULL) {
builder.append(" or ");
builder.append(INTERACT_ACROSS_PROFILES);
}
@@ -2206,19 +2206,14 @@ class UserController implements Handler.Callback {
private boolean canInteractWithAcrossProfilesPermission(
int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid,
String callingPackage) {
if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
if (allowMode != ALLOW_PROFILES_OR_NON_FULL) {
return false;
}
if (!isSameProfileGroup) {
return false;
}
return PermissionChecker.PERMISSION_GRANTED
== PermissionChecker.checkPermissionForPreflight(
mInjector.getContext(),
INTERACT_ACROSS_PROFILES,
callingPid,
callingUid,
callingPackage);
return mInjector.checkPermissionForPreflight(INTERACT_ACROSS_PROFILES, callingPid,
callingUid, callingPackage);
}
int unsafeConvertIncomingUser(@UserIdInt int userId) {
@@ -3147,6 +3142,12 @@ class UserController implements Handler.Callback {
return mService.checkComponentPermission(permission, pid, uid, owningUid, exported);
}
boolean checkPermissionForPreflight(String permission, int pid, int uid, String pkg) {
return PermissionChecker.PERMISSION_GRANTED
== PermissionChecker.checkPermissionForPreflight(
getContext(), permission, pid, uid, pkg);
}
protected void startHomeActivity(@UserIdInt int userId, String reason) {
mService.mAtmInternal.startHomeActivity(userId, reason);
}
@@ -3224,7 +3225,7 @@ class UserController implements Handler.Callback {
mService.mAtmInternal.clearLockedTasks(reason);
}
protected boolean isCallerRecents(int callingUid) {
boolean isCallerRecents(int callingUid) {
return mService.mAtmInternal.isCallerRecents(callingUid);
}

View File

@@ -16,6 +16,13 @@
package com.android.server.am;
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
@@ -64,6 +71,7 @@ import android.app.KeyguardManager;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
import android.os.Binder;
@@ -617,6 +625,100 @@ public class UserControllerTest {
assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
}
/** Tests handleIncomingUser() for a variety of permissions and situations. */
@Test
public void testHandleIncomingUser() throws Exception {
final UserInfo user1a = new UserInfo(111, "user1a", 0);
final UserInfo user1b = new UserInfo(112, "user1b", 0);
final UserInfo user2 = new UserInfo(113, "user2", 0);
// user1a and user2b are in the same profile group; user2 is in a different one.
user1a.profileGroupId = 5;
user1b.profileGroupId = 5;
user2.profileGroupId = 6;
final List<UserInfo> users = Arrays.asList(user1a, user1b, user2);
when(mInjector.mUserManagerMock.getUsers(false)).thenReturn(users);
mUserController.onSystemReady(); // To set the profileGroupIds in UserController.
// Has INTERACT_ACROSS_USERS_FULL.
when(mInjector.checkComponentPermission(
eq(INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt(), anyInt(), anyBoolean()))
.thenReturn(PackageManager.PERMISSION_GRANTED);
when(mInjector.checkComponentPermission(
eq(INTERACT_ACROSS_USERS), anyInt(), anyInt(), anyInt(), anyBoolean()))
.thenReturn(PackageManager.PERMISSION_DENIED);
when(mInjector.checkPermissionForPreflight(
eq(INTERACT_ACROSS_PROFILES), anyInt(), anyInt(), any())).thenReturn(false);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL, true);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL_IN_PROFILE, true);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_FULL_ONLY, true);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_PROFILES_OR_NON_FULL, true);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL, true);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL_IN_PROFILE, true);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_FULL_ONLY, true);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_PROFILES_OR_NON_FULL, true);
// Has INTERACT_ACROSS_USERS.
when(mInjector.checkComponentPermission(
eq(INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt(), anyInt(), anyBoolean()))
.thenReturn(PackageManager.PERMISSION_DENIED);
when(mInjector.checkComponentPermission(
eq(INTERACT_ACROSS_USERS), anyInt(), anyInt(), anyInt(), anyBoolean()))
.thenReturn(PackageManager.PERMISSION_GRANTED);
when(mInjector.checkPermissionForPreflight(
eq(INTERACT_ACROSS_PROFILES), anyInt(), anyInt(), any())).thenReturn(false);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL, true);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL_IN_PROFILE, false);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_FULL_ONLY, false);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_PROFILES_OR_NON_FULL, true);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL, true);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL_IN_PROFILE, true);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_FULL_ONLY, false);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_PROFILES_OR_NON_FULL, true);
// Has INTERACT_ACROSS_PROFILES.
when(mInjector.checkComponentPermission(
eq(INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt(), anyInt(), anyBoolean()))
.thenReturn(PackageManager.PERMISSION_DENIED);
when(mInjector.checkComponentPermission(
eq(INTERACT_ACROSS_USERS), anyInt(), anyInt(), anyInt(), anyBoolean()))
.thenReturn(PackageManager.PERMISSION_DENIED);
when(mInjector.checkPermissionForPreflight(
eq(INTERACT_ACROSS_PROFILES), anyInt(), anyInt(), any())).thenReturn(true);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL, false);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL_IN_PROFILE, false);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_FULL_ONLY, false);
checkHandleIncomingUser(user1a.id, user2.id, ALLOW_PROFILES_OR_NON_FULL, false);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL, false);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL_IN_PROFILE, false);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_FULL_ONLY, false);
checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_PROFILES_OR_NON_FULL, true);
}
private void checkHandleIncomingUser(int fromUser, int toUser, int allowMode, boolean pass) {
final int pid = 100;
final int uid = fromUser * UserHandle.PER_USER_RANGE + 34567 + fromUser;
final String name = "whatever";
final String pkg = "some.package";
final boolean allowAll = false;
if (pass) {
mUserController.handleIncomingUser(pid, uid, toUser, allowAll, allowMode, name, pkg);
} else {
assertThrows(SecurityException.class, () -> mUserController.handleIncomingUser(
pid, uid, toUser, allowAll, allowMode, name, pkg));
}
}
private void setUpAndStartUserInBackground(int userId) throws Exception {
setUpUser(userId, 0);
mUserController.startUser(userId, /* foreground= */ false);
@@ -783,6 +885,23 @@ public class UserControllerTest {
return PERMISSION_GRANTED;
}
@Override
int checkComponentPermission(String permission, int pid, int uid, int owner, boolean exp) {
Log.i(TAG, "checkComponentPermission " + permission);
return PERMISSION_GRANTED;
}
@Override
boolean checkPermissionForPreflight(String permission, int pid, int uid, String pkg) {
Log.i(TAG, "checkPermissionForPreflight " + permission);
return true;
}
@Override
boolean isCallerRecents(int uid) {
return false;
}
@Override
WindowManagerService getWindowManager() {
return mWindowManagerMock;