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
This commit is contained in:
John Wu
2021-12-21 11:48:19 -08:00
parent 3f265b798e
commit a68e97916a
2 changed files with 246 additions and 20 deletions

View File

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

View File

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