Merge "Check cross-user interactions for permissions and app-ops operations"

This commit is contained in:
Philip P. Moltmann
2020-07-30 15:39:58 +00:00
committed by Android (Google) Code Review
7 changed files with 162 additions and 60 deletions

View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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<ModeCallback> 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) {

View File

@@ -6,6 +6,9 @@
{
"name": "CtsAppOps2TestCases"
},
{
"name": "CtsAppOpHostTestCases"
},
{
"name": "FrameworksServicesTests",
"options": [

View File

@@ -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) {

View File

@@ -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": [