diff options
6 files changed, 170 insertions, 31 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index c46449c8bf3b..ea6ab0ce14a1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -10823,7 +10823,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); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 324e1aea81e7..96487de5e119 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -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. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2df6f9ae2bd6..0f24de6c54cb 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3601,10 +3601,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. @@ -3626,8 +3634,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) { @@ -3640,7 +3649,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) { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 7993936cd568..b92556bf9a20 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -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; } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index a7864b9607c8..028a0ec8648a 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -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; @@ -2153,11 +2153,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; @@ -2184,12 +2183,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); } @@ -2216,19 +2216,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) { @@ -3157,6 +3152,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); } @@ -3234,7 +3235,7 @@ class UserController implements Handler.Callback { mService.mAtmInternal.clearLockedTasks(reason); } - protected boolean isCallerRecents(int callingUid) { + boolean isCallerRecents(int callingUid) { return mService.mAtmInternal.isCallerRecents(callingUid); } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index f1a63bcb0602..6818d1f6851b 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -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); @@ -784,6 +886,23 @@ public class UserControllerTest { } @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; } |