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,