Merge "Preserve permissions when system apps leaving shared UID are uninstalled"

This commit is contained in:
John Wu
2021-12-21 22:10:37 +00:00
committed by Android (Google) Code Review
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,