Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a68e9791 authored by John Wu's avatar John Wu
Browse files

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
parent 3f265b79
Loading
Loading
Loading
Loading
+12 −7
Original line number Diff line number Diff line
@@ -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) {
+234 −13
Original line number Diff line number Diff line
@@ -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<String> NEARBY_DEVICES_PERMISSIONS = new ArrayList<>();

    // TODO: This is a placeholder. Replace with actual implementation
    private static final List<String> 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) {
    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<Boolean, Integer> 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<PermissionState> 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<Boolean, Integer> 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<AndroidPackage> 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,