From f5d24431b938c0d70aaa659d286ebf1d5c841414 Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Thu, 9 Mar 2023 15:06:47 -0800 Subject: [PATCH] Respect role granted app op permissions when resetting all op modes. We already respect role (or DPGP) granted runtime permissions when resetting permission states, and role won't proactively come and grant the permission again, so we should do the same for app ops to avoid breaking apps. Bug: 272371913 Test: manual Change-Id: Id561ca9c02a7f4c2a140434abf5503420490e443 --- core/java/android/app/AppOpsManager.java | 47 +++++++++++++++-- .../android/server/appop/AppOpsService.java | 50 +++++++++++++++++-- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 7c32c9cd634f8..181bd35acf62f 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2217,8 +2217,7 @@ public class AppOpsManager { /** Whether noting for an appop should be collected */ private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP]; - private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = { - // RUNTIME PERMISSIONS + private static final int[] RUNTIME_PERMISSION_OPS = { // Contacts OP_READ_CONTACTS, OP_WRITE_CONTACTS, @@ -2275,8 +2274,13 @@ public class AppOpsManager { OP_NEARBY_WIFI_DEVICES, // Notifications OP_POST_NOTIFICATION, + }; - // APPOP PERMISSIONS + /** + * Ops for app op permissions that are setting the per-package mode for certain reasons. Most + * app op permissions should set the per-UID mode instead. + */ + private static final int[] APP_OP_PERMISSION_PACKAGE_OPS = { OP_ACCESS_NOTIFICATIONS, OP_SYSTEM_ALERT_WINDOW, OP_WRITE_SETTINGS, @@ -2285,9 +2289,16 @@ public class AppOpsManager { OP_SMS_FINANCIAL_TRANSACTIONS, OP_MANAGE_IPSEC_TUNNELS, OP_INSTANT_APP_START_FOREGROUND, + OP_LOADER_USAGE_STATS + }; + + /** + * Ops for app op permissions that are setting the per-UID mode for certain reasons. This should + * be preferred over the per-package mode for new app op permissions. + */ + private static final int[] APP_OP_PERMISSION_UID_OPS = { OP_MANAGE_EXTERNAL_STORAGE, OP_INTERACT_ACROSS_PROFILES, - OP_LOADER_USAGE_STATS, OP_MANAGE_ONGOING_CALLS, OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, OP_SCHEDULE_EXACT_ALARM, @@ -2777,7 +2788,17 @@ public class AppOpsManager { sOpStrToOp.put(sAppOpInfos[i].name, i); } } - for (int op : RUNTIME_AND_APPOP_PERMISSIONS_OPS) { + for (int op : RUNTIME_PERMISSION_OPS) { + if (sAppOpInfos[op].permission != null) { + sPermToOp.put(sAppOpInfos[op].permission, op); + } + } + for (int op : APP_OP_PERMISSION_PACKAGE_OPS) { + if (sAppOpInfos[op].permission != null) { + sPermToOp.put(sAppOpInfos[op].permission, op); + } + } + for (int op : APP_OP_PERMISSION_UID_OPS) { if (sAppOpInfos[op].permission != null) { sPermToOp.put(sAppOpInfos[op].permission, op); } @@ -2946,6 +2967,22 @@ public class AppOpsManager { return !sAppOpInfos[op].disableReset; } + /** + * Retrieve whether the op is a per-package op for an app op permission. + * @hide + */ + public static boolean opIsPackageAppOpPermission(int op) { + return ArrayUtils.contains(APP_OP_PERMISSION_PACKAGE_OPS, op); + } + + /** + * Retrieve whether the op is a per-package op for an app op permission. + * @hide + */ + public static boolean opIsUidAppOpPermission(int op) { + return ArrayUtils.contains(APP_OP_PERMISSION_UID_OPS, op); + } + /** * Returns a listenerId suitable for use with {@link #noteOp(int, int, String, String, String)}. * diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 5bea614d471b3..fc22935736a35 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2096,7 +2096,9 @@ public class AppOpsService extends IAppOpsService.Stub { final int code = opModes.keyAt(j); if (AppOpsManager.opAllowsReset(code)) { int previousMode = opModes.valueAt(j); - uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code)); + int newMode = isUidOpGrantedByRole(uidState.uid, code) ? MODE_ALLOWED : + AppOpsManager.opToDefaultMode(code); + uidState.setUidMode(code, newMode); for (String packageName : getPackagesForUid(uidState.uid)) { callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, previousMode, @@ -2139,10 +2141,15 @@ public class AppOpsService extends IAppOpsService.Stub { deferResetOpToDpm(curOp.op, reqPackageName, reqUserId); continue; } - if (AppOpsManager.opAllowsReset(curOp.op) - && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) { + if (AppOpsManager.opAllowsReset(curOp.op)) { int previousMode = curOp.getMode(); - curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op)); + int newMode = isPackageOpGrantedByRole(packageName, uidState.uid, + curOp.op) ? MODE_ALLOWED : AppOpsManager.opToDefaultMode( + curOp.op); + if (previousMode == newMode) { + continue; + } + curOp.setMode(newMode); changed = true; uidChanged = true; final int uid = curOp.uidState.uid; @@ -2198,6 +2205,41 @@ public class AppOpsService extends IAppOpsService.Stub { } } + private boolean isUidOpGrantedByRole(int uid, int code) { + if (!AppOpsManager.opIsUidAppOpPermission(code)) { + return false; + } + PackageManager packageManager = mContext.getPackageManager(); + long token = Binder.clearCallingIdentity(); + try { + // Permissions are managed by UIDs, but unfortunately a package name is required in API. + String packageName = ArrayUtils.firstOrNull(packageManager.getPackagesForUid(uid)); + if (packageName == null) { + return false; + } + int permissionFlags = packageManager.getPermissionFlags(AppOpsManager.opToPermission( + code), packageName, UserHandle.getUserHandleForUid(uid)); + return (permissionFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE) != 0; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private boolean isPackageOpGrantedByRole(@NonNull String packageName, int uid, int code) { + if (!AppOpsManager.opIsPackageAppOpPermission(code)) { + return false; + } + PackageManager packageManager = mContext.getPackageManager(); + long token = Binder.clearCallingIdentity(); + try { + int permissionFlags = packageManager.getPermissionFlags(AppOpsManager.opToPermission( + code), packageName, UserHandle.getUserHandleForUid(uid)); + return (permissionFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE) != 0; + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean shouldDeferResetOpToDpm(int op) { // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission // pre-grants to a role-based mechanism or another general-purpose mechanism.