From a68e97916ad40bb055698fcdc2bf48a550c3e767 Mon Sep 17 00:00:00 2001 From: John Wu Date: Tue, 21 Dec 2021 11:48:19 -0800 Subject: [PATCH] Preserve permissions when system apps leaving shared UID are uninstalled This is a follow up of ag/15936308. When a system app update is uninstalled, the permission states are preserved. However, in the case of system apps leaving shared UID through an update, due to the fact that the appId has changed, the permission states will be lost, causing issues specifically with certain permission flags. In this CL, we add a new method in PermissionManagerService to handle this specific case. The permissions are either cloned to the previous appId, or merged into the UidPermissionState of the shared UID group. Test: tracking with b/202993696 Bug: 179284822 Change-Id: I02894671f1b8357fbb347c8daf7c1167ae051ac6 --- .../server/pm/InstallPackageHelper.java | 19 +- .../PermissionManagerServiceImpl.java | 247 +++++++++++++++++- 2 files changed, 246 insertions(+), 20 deletions(-) diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 8e6746dca7fcf..8573585c3c6ba 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2992,7 +2992,8 @@ final class InstallPackageHelper { installPackageFromSystemLIF(stubPkg.getPath(), mPm.mUserManager.getUserIds() /*allUserHandles*/, null /*origUserHandles*/, - true /*writeSettings*/); + true /*writeSettings*/, + Process.INVALID_UID /*previousAppId*/); } catch (PackageManagerException pme) { // Serious WTF; we have to be able to install the stub Slog.wtf(TAG, "Failed to restore system package:" + stubPkg.getPackageName(), @@ -3118,8 +3119,11 @@ final class InstallPackageHelper { if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs); try { synchronized (mPm.mInstallLock) { + final int[] origUsers = outInfo == null ? null : outInfo.mOrigUsers; + final int previousAppId = disabledPs.getAppId() != deletedPs.getAppId() + ? deletedPs.getAppId() : Process.INVALID_UID; installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles, - outInfo == null ? null : outInfo.mOrigUsers, writeSettings); + origUsers, writeSettings, previousAppId); } } catch (PackageManagerException e) { Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": " @@ -3160,7 +3164,8 @@ final class InstallPackageHelper { */ @GuardedBy({"mPm.mLock", "mPm.mInstallLock"}) private void installPackageFromSystemLIF(@NonNull String codePathString, - @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings) + @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, + boolean writeSettings, int previousAppId) throws PackageManagerException { final File codePath = new File(codePathString); @ParsingPackageUtils.ParseFlags int parseFlags = @@ -3184,11 +3189,12 @@ final class InstallPackageHelper { mAppDataHelper.prepareAppDataAfterInstallLIF(pkg); setPackageInstalledForSystemPackage(pkg, allUserHandles, - origUserHandles, writeSettings); + origUserHandles, writeSettings, previousAppId); } private void setPackageInstalledForSystemPackage(@NonNull AndroidPackage pkg, - @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings) { + @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, + boolean writeSettings, int previousAppId) { // writer synchronized (mPm.mLock) { PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName()); @@ -3222,8 +3228,7 @@ final class InstallPackageHelper { // The method below will take care of removing obsolete permissions and granting // install permissions. - mPm.mPermissionManager.onPackageInstalled(pkg, - Process.INVALID_UID /* previousAppId */, + mPm.mPermissionManager.onPackageInstalled(pkg, previousAppId, PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT, UserHandle.USER_ALL); for (final int userId : allUserHandles) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index d1b99380b0932..c9fd12219562b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -23,6 +23,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; +import static android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE; import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; @@ -108,6 +109,7 @@ import android.util.DebugUtils; import android.util.EventLog; import android.util.IntArray; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -196,6 +198,9 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt /** All nearby devices permissions */ private static final List NEARBY_DEVICES_PERMISSIONS = new ArrayList<>(); + // TODO: This is a placeholder. Replace with actual implementation + private static final List NOTIFICATION_PERMISSIONS = new ArrayList<>(); + /** * All permissions that should be granted with the REVOKE_WHEN_REQUESTED flag, if they are * implicitly added to a package @@ -4636,23 +4641,231 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt return true; } - private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId, - @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, - @UserIdInt int[] userIds) { - // If previousAppId is not Process.INVALID_UID, the package is performing a migration out - // of a shared user group. Operations we need to do before calling updatePermissions(): - // - Retrieve the original uid permission state and create a copy of it as the new app's - // uid state. The new permission state will be properly updated in updatePermissions(). - // - Remove the app from the original shared user group. Other apps in the shared - // user group will perceive as if the original app is uninstalled. - if (previousAppId != Process.INVALID_UID) { - final PackageStateInternal ps = - mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); + private boolean isEffectivelyGranted(PermissionState state) { + final int flags = state.getFlags(); + final int denyMask = FLAG_PERMISSION_REVIEW_REQUIRED + | FLAG_PERMISSION_REVOKED_COMPAT + | FLAG_PERMISSION_ONE_TIME; + + if ((flags & FLAG_PERMISSION_SYSTEM_FIXED) != 0) { + return true; + } else if ((flags & FLAG_PERMISSION_POLICY_FIXED) != 0) { + return (flags & FLAG_PERMISSION_REVOKED_COMPAT) == 0 && state.isGranted(); + } else if ((flags & denyMask) != 0) { + return false; + } else { + return state.isGranted(); + } + } + + /** + * Merge srcState into destState. Return [granted, flags]. + */ + private Pair mergePermissionState(int appId, + PermissionState srcState, PermissionState destState) { + // This merging logic prioritizes the shared permission state (destState) over + // the current package's state (srcState), because an uninstallation of a previously + // unrelated app (the updated system app) should not affect the functionality of + // existing apps (other apps in the shared UID group). + + final int userSettableMask = FLAG_PERMISSION_USER_SET + | FLAG_PERMISSION_USER_FIXED + | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; + + final int defaultGrantMask = FLAG_PERMISSION_GRANTED_BY_DEFAULT + | FLAG_PERMISSION_GRANTED_BY_ROLE; + + final int priorityFixedMask = FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_POLICY_FIXED; + + final int priorityMask = defaultGrantMask | priorityFixedMask; + + final int destFlags = destState.getFlags(); + final boolean destIsGranted = isEffectivelyGranted(destState); + + final int srcFlags = srcState.getFlags(); + final boolean srcIsGranted = isEffectivelyGranted(srcState); + + final int combinedFlags = destFlags | srcFlags; + + /* Merge flags */ + + int newFlags = 0; + + // Inherit user set flags only from dest as we want to preserve the + // user preference of destState, not the one of the current package. + newFlags |= (destFlags & userSettableMask); + + // Inherit all exempt flags + newFlags |= (combinedFlags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT); + // If no exempt flags are set, set APPLY_RESTRICTION + if ((newFlags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { + newFlags |= FLAG_PERMISSION_APPLY_RESTRICTION; + } + + // Inherit all priority flags + newFlags |= (combinedFlags & priorityMask); + + // If no priority flags are set, inherit REVOKE_WHEN_REQUESTED + if ((combinedFlags & priorityMask) == 0) { + newFlags |= (combinedFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); + } + + // Handle REVIEW_REQUIRED + if ((newFlags & priorityFixedMask) == 0) { + if (NOTIFICATION_PERMISSIONS.contains(srcState.getName())) { + // For notification permissions, inherit from both states + // if no priority FIXED flags are set + newFlags |= (combinedFlags & FLAG_PERMISSION_REVIEW_REQUIRED); + } else if ((newFlags & priorityMask) == 0) { + // Else inherit from destState if no priority flags are set + newFlags |= (destFlags & FLAG_PERMISSION_REVIEW_REQUIRED); + } + } + + /* Determine effective grant state */ + + final boolean effectivelyGranted; + if ((newFlags & FLAG_PERMISSION_SYSTEM_FIXED) != 0) { + effectivelyGranted = true; + } else if ((destFlags & FLAG_PERMISSION_POLICY_FIXED) != 0) { + // If this flag comes from destState, preserve its state + effectivelyGranted = destIsGranted; + } else if ((srcFlags & FLAG_PERMISSION_POLICY_FIXED) != 0) { + effectivelyGranted = destIsGranted || srcIsGranted; + // If this flag comes from srcState, preserve flag only if + // there is no conflict + if (destIsGranted != srcIsGranted) { + newFlags &= ~FLAG_PERMISSION_POLICY_FIXED; + } + } else if ((destFlags & defaultGrantMask) != 0) { + // If a permission state has default grant flags and is not + // granted, this meant user has overridden the grant state. + // Respect the user's preference on destState. + // Due to this reason, if this flag comes from destState, + // preserve its state + effectivelyGranted = destIsGranted; + } else if ((srcFlags & defaultGrantMask) != 0) { + effectivelyGranted = destIsGranted || srcIsGranted; + } else if ((destFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { + // Similar reason to defaultGrantMask, if this flag comes + // from destState, preserve its state + effectivelyGranted = destIsGranted; + } else if ((srcFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { + effectivelyGranted = destIsGranted || srcIsGranted; + // If this flag comes from srcState, remove this flag if + // destState is already granted to prevent revocation. + if (destIsGranted) { + newFlags &= ~FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; + } + } else { + // If still not determined, fallback to destState. + effectivelyGranted = destIsGranted; + } + + /* Post-processing / fix ups */ + + if (!effectivelyGranted) { + // If not effectively granted, inherit AUTO_REVOKED + newFlags |= (combinedFlags & FLAG_PERMISSION_AUTO_REVOKED); + + // REVOKE_WHEN_REQUESTED make no sense when denied + newFlags &= ~FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; + } else { + // REVIEW_REQUIRED make no sense when granted + newFlags &= ~FLAG_PERMISSION_REVIEW_REQUIRED; + } + + if (effectivelyGranted != destIsGranted) { + // Remove user set flags if state changes + newFlags &= ~userSettableMask; + } + + // Fix permission state based on targetSdk of the shared UID + final boolean newGrantState; + if (!effectivelyGranted && isPermissionSplitFromNonRuntime( + srcState.getName(), + mPackageManagerInt.getUidTargetSdkVersion(appId))) { + // Even though effectively denied, it has to be set to granted + // for backwards compatibility + newFlags |= FLAG_PERMISSION_REVOKED_COMPAT; + newGrantState = true; + } else { + // Either it's effectively granted, or it targets a high enough API level + // to handle this permission properly + newGrantState = effectivelyGranted; + } + + return new Pair<>(newGrantState, newFlags); + } + + /** + * This method handles permission migration of packages leaving/joining shared UID + */ + private void handleAppIdMigration(@NonNull AndroidPackage pkg, int previousAppId) { + final PackageStateInternal ps = + mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); + + if (ps.getSharedUser() != null) { + // The package is joining a shared user group. This can only happen when a system + // app left shared UID with an update, and then the update is uninstalled. + // If no apps remain in its original shared UID group, clone the current + // permission state to the shared appId; or else, merge the current permission + // state into the shared UID state. + + synchronized (mLock) { + for (final int userId : getAllUserIds()) { + final UserPermissionState userState = mState.getOrCreateUserState(userId); + + // This is the permission state the package was using + final UidPermissionState uidState = userState.getUidState(previousAppId); + if (uidState == null) { + continue; + } + + // This is the shared UID permission state the package wants to join + final UidPermissionState sharedUidState = userState.getUidState(ps.getAppId()); + if (sharedUidState == null) { + // No apps remain in the shared UID group, clone permissions + userState.createUidStateWithExisting(ps.getAppId(), uidState); + } else { + final List states = uidState.getPermissionStates(); + final int count = states.size(); + for (int i = 0; i < count; ++i) { + final PermissionState srcState = states.get(i); + final PermissionState destState = + sharedUidState.getPermissionState(srcState.getName()); + if (destState != null) { + // Merge the 2 permission states + Pair newState = + mergePermissionState(ps.getAppId(), srcState, destState); + sharedUidState.putPermissionState(srcState.getPermission(), + newState.first, newState.second); + } else { + // Simply copy the permission state over + sharedUidState.putPermissionState(srcState.getPermission(), + srcState.isGranted(), srcState.getFlags()); + } + } + } + + // Remove permissions for the previous appId + userState.removeUidState(previousAppId); + } + } + } else { + // The package is migrating out of a shared user group. + // Operations we need to do before calling updatePermissions(): + // - Retrieve the original uid permission state and create a copy of it as the + // new app's uid state. The new permission state will be properly updated in + // updatePermissions(). + // - Remove the app from the original shared user group. Other apps in the shared + // user group will perceive as if the original app is uninstalled. + final List origSharedUserPackages = mPackageManagerInt.getPackagesForAppId(previousAppId); synchronized (mLock) { - // All users are affected for (final int userId : getAllUserIds()) { // Retrieve the original uid state final UserPermissionState userState = mState.getUserState(userId); @@ -4679,6 +4892,14 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } } } + } + + private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId, + @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, + @UserIdInt int[] userIds) { + if (previousAppId != Process.INVALID_UID) { + handleAppIdMigration(pkg, previousAppId); + } updatePermissions(pkg.getPackageName(), pkg); for (final int userId : userIds) { addAllowlistedRestrictedPermissionsInternal(pkg,