diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index ef30cb46318c2..f4743895686ce 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -52,14 +52,23 @@ public abstract class ActivityManagerInternal { * if in the same profile group. * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. */ - public static final int ALLOW_NON_FULL_IN_PROFILE = 1; + public static final int ALLOW_NON_FULL_IN_PROFILE_OR_FULL = 1; 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. */ - public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3; + public static final int ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL = 3; + /** + * Requires {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}, + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS}, or + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if in same profile group, + * otherwise {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. (so this is an extension + * to {@link #ALLOW_NON_FULL}) + */ + public static final int ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL = 4; /** * Verify that calling app has access to the given provider. diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 33a92e6ad0acc..1680963e26d12 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2559,12 +2559,12 @@ 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; + return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL; } if (callingPackage.equals(service.getComponent().getPackageName())) { - return ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE; + return ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL; } else { - return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; + return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL; } } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 0658e8139cc2c..19b671e46b71f 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -23,10 +23,11 @@ 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_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL; +import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_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_NON_FULL_IN_PROFILE_OR_FULL; import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; @@ -1909,11 +1910,12 @@ 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_ACROSS_PROFILES_IN_PROFILE_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_OR_FULL + || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL) { // We may or may not allow this depending on whether the two users are // in the same profile. allow = isSameProfileGroup; @@ -1940,12 +1942,15 @@ 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_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL + || isSameProfileGroup) { builder.append(" or "); builder.append(INTERACT_ACROSS_USERS); } if (isSameProfileGroup - && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + && (allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL + || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL)) { builder.append(" or "); builder.append(INTERACT_ACROSS_PROFILES); } @@ -1972,7 +1977,8 @@ 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_ACROSS_PROFILES_IN_PROFILE_OR_FULL + && allowMode != ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL) { return false; } if (!isSameProfileGroup) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index e6480fc6cde8c..74f3daf50079e 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -19,6 +19,7 @@ package com.android.server.appop; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; +import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL; import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP; import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; @@ -128,6 +129,7 @@ import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.EventLog; import android.util.KeyValueListParser; import android.util.LongSparseArray; import android.util.Pair; @@ -161,6 +163,7 @@ import com.android.server.LocalServices; import com.android.server.LockGuard; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemServiceManager; +import com.android.server.am.ActivityManagerService; import com.android.server.pm.PackageList; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -2197,8 +2200,11 @@ public class AppOpsService extends IAppOpsService.Stub { + " by uid " + Binder.getCallingUid()); } + int userId = UserHandle.getUserId(uid); + enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); verifyIncomingOp(code); + verifyIncomingUser(userId); code = AppOpsManager.opToSwitch(code); if (permissionPolicyCallback == null) { @@ -2443,8 +2449,12 @@ public class AppOpsService extends IAppOpsService.Stub { private void setMode(int code, int uid, @NonNull String packageName, int mode, @Nullable IAppOpsCallback permissionPolicyCallback) { enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); + + int userId = UserHandle.getUserId(uid); + verifyIncomingOp(code); - verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); + verifyIncomingUser(userId); + verifyIncomingPackage(packageName, userId); ArraySet repCbs = null; code = AppOpsManager.opToSwitch(code); @@ -2857,8 +2867,11 @@ public class AppOpsService extends IAppOpsService.Stub { private int checkOperationImpl(int code, int uid, String packageName, boolean raw) { + int userId = UserHandle.getUserId(uid); + verifyIncomingOp(code); - verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); + verifyIncomingUser(userId); + verifyIncomingPackage(packageName, userId); String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { @@ -2977,10 +2990,15 @@ public class AppOpsService extends IAppOpsService.Stub { String proxiedAttributionTag, int proxyUid, String proxyPackageName, String proxyAttributionTag, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage) { + int proxiedUserId = UserHandle.getUserId(proxiedUid); + int proxyUserId = UserHandle.getUserId(proxyUid); + verifyIncomingUid(proxyUid); verifyIncomingOp(code); - verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid)); - verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid)); + verifyIncomingUser(proxiedUserId); + verifyIncomingUser(proxyUserId); + verifyIncomingPackage(proxiedPackageName, proxiedUserId); + verifyIncomingPackage(proxyPackageName, proxyUserId); String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName); if (resolveProxyPackageName == null) { @@ -3030,9 +3048,12 @@ public class AppOpsService extends IAppOpsService.Stub { private int noteOperationImpl(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage) { + int userId = UserHandle.getUserId(uid); + verifyIncomingUid(uid); verifyIncomingOp(code); - verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); + verifyIncomingUser(userId); + verifyIncomingPackage(packageName, userId); String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { @@ -3409,9 +3430,12 @@ public class AppOpsService extends IAppOpsService.Stub { public int startOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage) { + int userId = UserHandle.getUserId(uid); + verifyIncomingUid(uid); verifyIncomingOp(code); - verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); + verifyIncomingUser(userId); + verifyIncomingPackage(packageName, userId); String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { @@ -3491,9 +3515,12 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void finishOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag) { + int userId = UserHandle.getUserId(uid); + verifyIncomingUid(uid); verifyIncomingOp(code); - verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); + verifyIncomingUser(userId); + verifyIncomingPackage(packageName, userId); String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { @@ -3722,6 +3749,33 @@ public class AppOpsService extends IAppOpsService.Stub { } } + private void verifyIncomingUser(@UserIdInt int userId) { + int callingUid = Binder.getCallingUid(); + int callingUserId = UserHandle.getUserId(callingUid); + int callingPid = Binder.getCallingPid(); + + if (callingUserId != userId) { + // Prevent endless loop between when checking appops inside of handleIncomingUser + if (Binder.getCallingPid() == ActivityManagerService.MY_PID) { + return; + } + long token = Binder.clearCallingIdentity(); + try { + try { + LocalServices.getService(ActivityManagerInternal.class).handleIncomingUser( + callingPid, callingUid, userId, /* allowAll */ false, + ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL, "appop operation", null); + } catch (Exception e) { + EventLog.writeEvent(0x534e4554, "153996875", "appop", userId); + + throw e; + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + private @Nullable UidState getUidStateLocked(int uid, boolean edit) { UidState uidState = mUidStates.get(uid); if (uidState == null) { @@ -5801,8 +5855,11 @@ public class AppOpsService extends IAppOpsService.Stub { return false; } } + int userId = UserHandle.getUserId(uid); + verifyIncomingOp(code); - verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); + verifyIncomingUser(userId); + verifyIncomingPackage(packageName, userId); final String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING index 84de25c06ebf2..a3e1b7a7e5c5e 100644 --- a/services/core/java/com/android/server/appop/TEST_MAPPING +++ b/services/core/java/com/android/server/appop/TEST_MAPPING @@ -6,6 +6,9 @@ { "name": "CtsAppOps2TestCases" }, + { + "name": "CtsAppOpHostTestCases" + }, { "name": "FrameworksServicesTests", "options": [ diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index be93b8f95b793..6e0efb09aff33 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -137,6 +137,7 @@ import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemConfig; import com.android.server.Watchdog; +import com.android.server.am.ActivityManagerService; import com.android.server.pm.ApexManager; import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.pm.PackageSetting; @@ -921,6 +922,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { } final int uid = UserHandle.getUid(userId, pkg.getUid()); + + try { + enforceCrossUserOrProfilePermission(Binder.getCallingUid(), UserHandle.getUserId(uid), + false, false, "checkPermissionInternal"); + } catch (Exception e) { + EventLog.writeEvent(0x534e4554, "153996875", "checkPermission", uid); + + throw e; + } + final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting( pkg.getPackageName()); if (ps == null) { @@ -4388,7 +4399,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } final int callingUserId = UserHandle.getUserId(callingUid); if (hasCrossUserPermission( - callingUid, callingUserId, userId, requireFullPermission, + Binder.getCallingPid(), callingUid, callingUserId, userId, requireFullPermission, requirePermissionWhenSameUser)) { return; } @@ -4415,37 +4426,54 @@ public class PermissionManagerService extends IPermissionManager.Stub { private void enforceCrossUserOrProfilePermission(int callingUid, int userId, boolean requireFullPermission, boolean checkShell, String message) { + int callingPid = Binder.getCallingPid(); + final int callingUserId = UserHandle.getUserId(callingUid); + if (userId < 0) { throw new IllegalArgumentException("Invalid userId " + userId); } - if (checkShell) { - PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt, - UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); - } - final int callingUserId = UserHandle.getUserId(callingUid); - if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission, - /*requirePermissionWhenSameUser= */ false)) { + + if (callingUserId == userId) { return; } - final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId); - if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight( - mContext, - android.Manifest.permission.INTERACT_ACROSS_PROFILES, - PermissionChecker.PID_UNKNOWN, - callingUid, - mPackageManagerInt.getPackage(callingUid).getPackageName()) - == PermissionChecker.PERMISSION_GRANTED) { + + // Prevent endless loop between when checking permission while checking a permission + if (callingPid == ActivityManagerService.MY_PID) { return; } - String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage( - message, requireFullPermission, isSameProfileGroup); - Slog.w(TAG, errorMessage); - throw new SecurityException(errorMessage); + + long token = Binder.clearCallingIdentity(); + try { + if (checkShell) { + PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt, + UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); + } + if (hasCrossUserPermission(callingPid, callingUid, callingUserId, userId, + requireFullPermission, /*requirePermissionWhenSameUser= */ false)) { + return; + } + final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId); + if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight( + mContext, + android.Manifest.permission.INTERACT_ACROSS_PROFILES, + PermissionChecker.PID_UNKNOWN, + callingUid, + mPackageManagerInt.getPackage(callingUid).getPackageName()) + == PermissionChecker.PERMISSION_GRANTED) { + return; + } + + String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage( + message, requireFullPermission, isSameProfileGroup); + Slog.w(TAG, errorMessage); + throw new SecurityException(errorMessage); + } finally { + Binder.restoreCallingIdentity(token); + } } - private boolean hasCrossUserPermission( - int callingUid, int callingUserId, int userId, boolean requireFullPermission, - boolean requirePermissionWhenSameUser) { + private boolean hasCrossUserPermission(int callingPid, int callingUid, int callingUserId, + int userId, boolean requireFullPermission, boolean requirePermissionWhenSameUser) { if (!requirePermissionWhenSameUser && userId == callingUserId) { return true; } @@ -4453,15 +4481,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { return true; } if (requireFullPermission) { - return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL); + return mContext.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, + callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; } - return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS); - } - - private boolean hasPermission(String permission) { - return mContext.checkCallingOrSelfPermission(permission) - == PackageManager.PERMISSION_GRANTED; + return mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS, + callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; } private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) { diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING index c0d71ac268530..65dc320eadc29 100644 --- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING @@ -17,14 +17,6 @@ } ] }, - { - "name": "CtsAppSecurityHostTestCases", - "options": [ - { - "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission" - } - ] - }, { "name": "CtsPermission2TestCases", "options": [ @@ -36,6 +28,17 @@ } ] }, + { + "name": "CtsPermissionHostTestCases" + }, + { + "name": "CtsAppSecurityHostTestCases", + "options": [ + { + "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission" + } + ] + }, { "name": "CtsStatsdHostTestCases", "options": [