diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 4830691d472aa..0958bcb6cc148 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -16,12 +16,9 @@ package com.android.server.pm.permission; -import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; -import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.RECORD_AUDIO; import static android.Manifest.permission.UPDATE_APP_OPS_STATS; -import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; import static android.app.AppOpsManager.ATTRIBUTION_FLAGS_NONE; import static android.app.AppOpsManager.MODE_ALLOWED; @@ -29,43 +26,11 @@ import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED; import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED; -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_GRANTED_BY_DEFAULT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE; -import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; -import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; -import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; -import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; -import static android.content.pm.PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; -import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; -import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; -import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; -import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER; -import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM; -import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE; -import static android.content.pm.PackageManager.MASK_PERMISSION_FLAGS_ALL; -import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; -import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; -import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED; -import static android.permission.PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED; -import static com.android.server.pm.ApexManager.MATCH_ACTIVE_PACKAGE; -import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; -import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING; -import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS; -import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; -import static java.util.concurrent.TimeUnit.SECONDS; - import android.Manifest; import android.annotation.AppIdInt; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -73,119 +38,49 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.AppOpsManager.AttributionFlags; import android.app.IActivityManager; -import android.app.admin.DevicePolicyManagerInternal; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; import android.content.AttributionSource; import android.content.AttributionSourceState; import android.content.Context; import android.content.PermissionChecker; -import android.content.pm.ApplicationInfo; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.PermissionGroupInfoFlags; -import android.content.pm.PackageManager.PermissionInfoFlags; -import android.content.pm.PackageManager.PermissionWhitelistFlags; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; -import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedPermissionUtils; -import android.content.pm.permission.CompatibilityPermissionInfo; import android.content.pm.permission.SplitPermissionInfoParcelable; -import android.metrics.LogMaker; -import android.os.AsyncTask; import android.os.Binder; -import android.os.Build; -import android.os.Debug; -import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; -import android.os.Looper; -import android.os.Message; import android.os.Process; -import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.Trace; import android.os.UserHandle; -import android.os.UserManager; -import android.os.storage.StorageManager; import android.permission.IOnPermissionsChangeListener; import android.permission.IPermissionChecker; import android.permission.IPermissionManager; import android.permission.PermissionCheckerManager; -import android.permission.PermissionControllerManager; import android.permission.PermissionManager; import android.permission.PermissionManagerInternal; -import android.text.TextUtils; import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.DebugUtils; -import android.util.EventLog; -import android.util.IntArray; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.compat.IPlatformCompat; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.os.RoSystemProperties; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.CollectionUtils; -import com.android.internal.util.DumpUtils; -import com.android.internal.util.IntPair; -import com.android.internal.util.Preconditions; import com.android.internal.util.function.TriFunction; -import com.android.internal.util.function.pooled.PooledLambda; -import com.android.server.FgThread; import com.android.server.LocalServices; -import com.android.server.ServiceThread; -import com.android.server.SystemConfig; -import com.android.server.Watchdog; -import com.android.server.pm.ApexManager; -import com.android.server.pm.PackageSetting; import com.android.server.pm.UserManagerInternal; -import com.android.server.pm.UserManagerService; -import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; -import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider; -import com.android.server.pm.permission.PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener; -import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.policy.PermissionPolicyInternal; -import com.android.server.policy.SoftRestrictedPermissionPolicy; -import libcore.util.EmptyArray; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.WeakHashMap; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; @@ -194,68 +89,12 @@ import java.util.function.BiFunction; * Manages all permissions and handles permissions related tasks. */ public class PermissionManagerService extends IPermissionManager.Stub { - private static final String TAG = "PackageManager"; private static final String LOG_TAG = PermissionManagerService.class.getSimpleName(); - private static final long BACKUP_TIMEOUT_MILLIS = SECONDS.toMillis(60); - - // For automotive products, CarService enforces allow-listing of the privileged permissions - // com.android.car is the package name which declares auto specific permissions - private static final String CAR_PACKAGE_NAME = "com.android.car"; - - /** Cap the size of permission trees that 3rd party apps can define; in characters of text */ - private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; - /** Empty array to avoid allocations */ - private static final int[] EMPTY_INT_ARRAY = new int[0]; - - /** - * When these flags are set, the system should not automatically modify the permission grant - * state. - */ - private static final int BLOCKING_PERMISSION_FLAGS = FLAG_PERMISSION_SYSTEM_FIXED - | FLAG_PERMISSION_POLICY_FIXED - | FLAG_PERMISSION_GRANTED_BY_DEFAULT; - - /** Permission flags set by the user */ - private static final int USER_PERMISSION_FLAGS = FLAG_PERMISSION_USER_SET - | FLAG_PERMISSION_USER_FIXED; - - /** All storage permissions */ - private static final List STORAGE_PERMISSIONS = new ArrayList<>(); - /** All nearby devices permissions */ - private static final List NEARBY_DEVICES_PERMISSIONS = new ArrayList<>(); - - /** - * All permissions that should be granted with the REVOKE_WHEN_REQUESTED flag, if they are - * implicitly added to a package - */ - private static final List IMPLICIT_GRANTED_PERMISSIONS = new ArrayList<>(); - - /** If the permission of the value is granted, so is the key */ - private static final Map FULLER_PERMISSION_MAP = new HashMap<>(); - /** Map of IBinder -> Running AttributionSource */ private static final ConcurrentHashMap sRunningAttributionSources = new ConcurrentHashMap<>(); - static { - FULLER_PERMISSION_MAP.put(Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION); - FULLER_PERMISSION_MAP.put(Manifest.permission.INTERACT_ACROSS_USERS, - Manifest.permission.INTERACT_ACROSS_USERS_FULL); - STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE); - STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); - STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION); - NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE); - NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT); - NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN); - IMPLICIT_GRANTED_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS); - } - - /** Set of source package names for Privileged Permission Allowlist */ - private final ArraySet mPrivilegedPermissionAllowlistSourcePackageNames = - new ArraySet<>(); - /** Lock to protect internal data access */ private final Object mLock = new Object(); @@ -265,13 +104,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { /** Internal connection to the user manager */ private final UserManagerInternal mUserManagerInt; - @GuardedBy("mLock") - @NonNull - private final DevicePermissionState mState = new DevicePermissionState(); - - /** Permission controller: User space permission management */ - private PermissionControllerManager mPermissionControllerManager; - /** Map of OneTimePermissionUserManagers keyed by userId */ @GuardedBy("mLock") @NonNull @@ -281,130 +113,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { /** App ops manager */ private final AppOpsManager mAppOpsManager; - /** - * Built-in permissions. Read from system configuration files. Mapping is from - * UID to permission name. - */ - private final SparseArray> mSystemPermissions; - - /** Built-in group IDs given to all packages. Read from system configuration files. */ - @NonNull - private final int[] mGlobalGids; - - private final HandlerThread mHandlerThread; - private final Handler mHandler; private final Context mContext; - private final MetricsLogger mMetricsLogger = new MetricsLogger(); - private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); - - /** Internal storage for permissions and related settings */ - @GuardedBy("mLock") - @NonNull - private final PermissionRegistry mRegistry = new PermissionRegistry(); + private final PermissionManagerServiceImpl mPermissionManagerServiceImpl; @NonNull private final AttributionSourceRegistry mAttributionSourceRegistry; - @GuardedBy("mLock") - @Nullable - private ArraySet mPrivappPermissionsViolations; - - @GuardedBy("mLock") - private boolean mSystemReady; - - @GuardedBy("mLock") - private PermissionPolicyInternal mPermissionPolicyInternal; - - /** - * A permission backup might contain apps that are not installed. In this case we delay the - * restoration until the app is installed. - * - *

This array ({@code userId -> noDelayedBackupLeft}) is {@code true} for all the users where - * there is no more delayed backup left. - */ - @GuardedBy("mLock") - private final SparseBooleanArray mHasNoDelayedPermBackup = new SparseBooleanArray(); - - /** Listeners for permission state (granting and flags) changes */ - @GuardedBy("mLock") - final private ArrayList - mRuntimePermissionStateChangedListeners = new ArrayList<>(); - @GuardedBy("mLock") private CheckPermissionDelegate mCheckPermissionDelegate; - @NonNull - private final OnPermissionChangeListeners mOnPermissionChangeListeners; - @Nullable private HotwordDetectionServiceProvider mHotwordDetectionServiceProvider; - // TODO: Take a look at the methods defined in the callback. - // The callback was initially created to support the split between permission - // manager and the package manager. However, it's started to be used for other - // purposes. It may make sense to keep as an abstraction, but, the methods - // necessary to be overridden may be different than what was initially needed - // for the split. - private final PermissionCallback mDefaultPermissionCallback = new PermissionCallback() { - @Override - public void onGidsChanged(int appId, int userId) { - mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED)); - } - @Override - public void onPermissionGranted(int uid, int userId) { - mOnPermissionChangeListeners.onPermissionsChanged(uid); - - // Not critical; if this is lost, the application has to request again. - mPackageManagerInt.writeSettings(true); - } - @Override - public void onInstallPermissionGranted() { - mPackageManagerInt.writeSettings(true); - } - @Override - public void onPermissionRevoked(int uid, int userId, String reason) { - mOnPermissionChangeListeners.onPermissionsChanged(uid); - - // Critical; after this call the application should never have the permission - mPackageManagerInt.writeSettings(false); - final int appId = UserHandle.getAppId(uid); - if (reason == null) { - mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED)); - } else { - mHandler.post(() -> killUid(appId, userId, reason)); - } - } - @Override - public void onInstallPermissionRevoked() { - mPackageManagerInt.writeSettings(true); - } - @Override - public void onPermissionUpdated(int[] userIds, boolean sync) { - mPackageManagerInt.writePermissionSettings(userIds, !sync); - } - @Override - public void onInstallPermissionUpdated() { - mPackageManagerInt.writeSettings(true); - } - @Override - public void onPermissionRemoved() { - mPackageManagerInt.writeSettings(false); - } - public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync, - int uid) { - onPermissionUpdated(updatedUserIds, sync); - for (int i = 0; i < updatedUserIds.length; i++) { - int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid)); - mOnPermissionChangeListeners.onPermissionsChanged(userUid); - } - } - public void onInstallPermissionUpdatedNotifyListener(int uid) { - onInstallPermissionUpdated(); - mOnPermissionChangeListeners.onPermissionsChanged(uid); - } - }; - PermissionManagerService(@NonNull Context context, @NonNull ArrayMap availableFeatures) { // The package info cache is the cache for package and permission information. @@ -418,55 +138,15 @@ public class PermissionManagerService extends IPermissionManager.Stub { mUserManagerInt = LocalServices.getService(UserManagerInternal.class); mAppOpsManager = context.getSystemService(AppOpsManager.class); - mPrivilegedPermissionAllowlistSourcePackageNames.add(PLATFORM_PACKAGE_NAME); - // PackageManager.hasSystemFeature() is not used here because PackageManagerService - // isn't ready yet. - if (availableFeatures.containsKey(PackageManager.FEATURE_AUTOMOTIVE)) { - mPrivilegedPermissionAllowlistSourcePackageNames.add(CAR_PACKAGE_NAME); - } - - mHandlerThread = new ServiceThread(TAG, - Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper()); - Watchdog.getInstance().addThread(mHandler); - - SystemConfig systemConfig = SystemConfig.getInstance(); - mSystemPermissions = systemConfig.getSystemPermissions(); - mGlobalGids = systemConfig.getGlobalGids(); - mOnPermissionChangeListeners = new OnPermissionChangeListeners(FgThread.get().getLooper()); mAttributionSourceRegistry = new AttributionSourceRegistry(context); - // propagate permission configuration - final ArrayMap permConfig = - SystemConfig.getInstance().getPermissions(); - synchronized (mLock) { - for (int i=0; i @@ -519,511 +202,22 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - @NonNull - private String[] getAppOpPermissionPackagesInternal(@NonNull String permissionName) { - synchronized (mLock) { - final ArraySet packageNames = mRegistry.getAppOpPermissionPackages( - permissionName); - if (packageNames == null) { - return EmptyArray.STRING; - } - return packageNames.toArray(new String[0]); - } - } - - @Override - @NonNull - public ParceledListSlice getAllPermissionGroups( - @PermissionGroupInfoFlags int flags) { - final int callingUid = getCallingUid(); - if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { - return ParceledListSlice.emptyList(); - } - - final List out = new ArrayList<>(); - synchronized (mLock) { - for (ParsedPermissionGroup pg : mRegistry.getPermissionGroups()) { - out.add(PackageInfoUtils.generatePermissionGroupInfo(pg, flags)); - } - } - - final int callingUserId = UserHandle.getUserId(callingUid); - out.removeIf(it -> mPackageManagerInt.filterAppAccess(it.packageName, callingUid, - callingUserId)); - return new ParceledListSlice<>(out); - } - - - @Override - @Nullable - public PermissionGroupInfo getPermissionGroupInfo(String groupName, - @PermissionGroupInfoFlags int flags) { - final int callingUid = getCallingUid(); - if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { - return null; - } - - final PermissionGroupInfo permissionGroupInfo; - synchronized (mLock) { - final ParsedPermissionGroup permissionGroup = mRegistry.getPermissionGroup(groupName); - if (permissionGroup == null) { - return null; - } - permissionGroupInfo = PackageInfoUtils.generatePermissionGroupInfo(permissionGroup, - flags); - } - - final int callingUserId = UserHandle.getUserId(callingUid); - if (mPackageManagerInt.filterAppAccess(permissionGroupInfo.packageName, callingUid, - callingUserId)) { - EventLog.writeEvent(0x534e4554, "186113473", callingUid, groupName); - return null; - } - return permissionGroupInfo; - } - - - @Override - @Nullable - public PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName, - @PermissionInfoFlags int flags) { - final int callingUid = getCallingUid(); - if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { - return null; - } - - final AndroidPackage opPackage = mPackageManagerInt.getPackage(opPackageName); - final int targetSdkVersion = getPermissionInfoCallingTargetSdkVersion(opPackage, - callingUid); - final PermissionInfo permissionInfo; - synchronized (mLock) { - final Permission bp = mRegistry.getPermission(permName); - if (bp == null) { - return null; - } - permissionInfo = bp.generatePermissionInfo(flags, targetSdkVersion); - } - - final int callingUserId = UserHandle.getUserId(callingUid); - if (mPackageManagerInt.filterAppAccess(permissionInfo.packageName, callingUid, - callingUserId)) { - EventLog.writeEvent(0x534e4554, "183122164", callingUid, permName); - return null; - } - return permissionInfo; - } - - private int getPermissionInfoCallingTargetSdkVersion(@Nullable AndroidPackage pkg, int uid) { - final int appId = UserHandle.getAppId(uid); - if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID - || appId == Process.SHELL_UID) { - // System sees all flags. - return Build.VERSION_CODES.CUR_DEVELOPMENT; - } - if (pkg == null) { - return Build.VERSION_CODES.CUR_DEVELOPMENT; - } - return pkg.getTargetSdkVersion(); - } - - @Override - @Nullable - public ParceledListSlice queryPermissionsByGroup(String groupName, - @PermissionInfoFlags int flags) { - final int callingUid = getCallingUid(); - if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { - return null; - } - - final List out = new ArrayList<>(10); - synchronized (mLock) { - if (groupName != null && mRegistry.getPermissionGroup(groupName) == null) { - return null; - } - for (Permission bp : mRegistry.getPermissions()) { - if (Objects.equals(bp.getGroup(), groupName)) { - out.add(bp.generatePermissionInfo(flags)); - } - } - } - - final int callingUserId = UserHandle.getUserId(callingUid); - out.removeIf(it -> mPackageManagerInt.filterAppAccess(it.packageName, callingUid, - callingUserId)); - return new ParceledListSlice<>(out); - } - - @Override - public boolean addPermission(PermissionInfo info, boolean async) { - final int callingUid = getCallingUid(); - if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { - throw new SecurityException("Instant apps can't add permissions"); - } - if (info.labelRes == 0 && info.nonLocalizedLabel == null) { - throw new SecurityException("Label must be specified in permission"); - } - final boolean added; - final boolean changed; - synchronized (mLock) { - final Permission tree = mRegistry.enforcePermissionTree(info.name, callingUid); - Permission bp = mRegistry.getPermission(info.name); - added = bp == null; - int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel); - if (added) { - enforcePermissionCapLocked(info, tree); - bp = new Permission(info.name, tree.getPackageName(), Permission.TYPE_DYNAMIC); - } else if (!bp.isDynamic()) { - throw new SecurityException("Not allowed to modify non-dynamic permission " - + info.name); - } - changed = bp.addToTree(fixedLevel, info, tree); - if (added) { - mRegistry.addPermission(bp); - } - } - if (changed) { - mPackageManagerInt.writeSettings(async); - } - return added; - } - - @Override - public void removePermission(String permName) { - final int callingUid = getCallingUid(); - if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { - throw new SecurityException("Instant applications don't have access to this method"); - } - synchronized (mLock) { - mRegistry.enforcePermissionTree(permName, callingUid); - final Permission bp = mRegistry.getPermission(permName); - if (bp == null) { - return; - } - if (bp.isDynamic()) { - // TODO: switch this back to SecurityException - Slog.wtf(TAG, "Not allowed to modify non-dynamic permission " - + permName); - } - mRegistry.removePermission(permName); - } - mPackageManagerInt.writeSettings(false); - } - - @Override - public int getPermissionFlags(String packageName, String permName, int userId) { - final int callingUid = getCallingUid(); - return getPermissionFlagsInternal(packageName, permName, callingUid, userId); - } - - private int getPermissionFlagsInternal( - String packageName, String permName, int callingUid, int userId) { - if (!mUserManagerInt.exists(userId)) { - return 0; - } - - enforceGrantRevokeGetRuntimePermissionPermissions("getPermissionFlags"); - enforceCrossUserPermission(callingUid, userId, - true, // requireFullPermission - false, // checkShell - "getPermissionFlags"); - - final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); - if (pkg == null) { - return 0; - } - if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { - return 0; - } - - synchronized (mLock) { - if (mRegistry.getPermission(permName) == null) { - return 0; - } - - final UidPermissionState uidState = getUidStateLocked(pkg, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); - return 0; - } - - return uidState.getPermissionFlags(permName); - } - } - - @Override - public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { - final int callingUid = getCallingUid(); - boolean overridePolicy = false; - - if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) { - final long callingIdentity = Binder.clearCallingIdentity(); - try { - if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0) { - if (checkAdjustPolicyFlagPermission) { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, - "Need " + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY - + " to change policy flags"); - } else if (mPackageManagerInt.getUidTargetSdkVersion(callingUid) - >= Build.VERSION_CODES.Q) { - throw new IllegalArgumentException( - Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + " needs " - + " to be checked for packages targeting " - + Build.VERSION_CODES.Q + " or later when changing policy " - + "flags"); - } - overridePolicy = true; - } - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - } - - updatePermissionFlagsInternal( - packageName, permName, flagMask, flagValues, callingUid, userId, - overridePolicy, mDefaultPermissionCallback); - } - - private void updatePermissionFlagsInternal(String packageName, String permName, int flagMask, - int flagValues, int callingUid, int userId, boolean overridePolicy, - PermissionCallback callback) { - if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES - && PermissionManager.shouldTraceGrant(packageName, permName, userId)) { - Log.i(TAG, "System is updating flags for " + packageName + " " - + permName + " for user " + userId + " " - + DebugUtils.flagsToString( - PackageManager.class, "FLAG_PERMISSION_", flagMask) - + " := " - + DebugUtils.flagsToString( - PackageManager.class, "FLAG_PERMISSION_", flagValues) - + " on behalf of uid " + callingUid - + " " + mPackageManagerInt.getNameForUid(callingUid), - new RuntimeException()); - } - - if (!mUserManagerInt.exists(userId)) { - return; - } - - enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags"); - - enforceCrossUserPermission(callingUid, userId, - true, // requireFullPermission - true, // checkShell - "updatePermissionFlags"); - - if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0 && !overridePolicy) { - throw new SecurityException("updatePermissionFlags requires " - + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY); - } - - // Only the system can change these flags and nothing else. - if (callingUid != Process.SYSTEM_UID) { - flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; - flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; - flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; - flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; - flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; - flagValues &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; - flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; - flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; - flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; - } - - final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); - if (pkg == null) { - Log.e(TAG, "Unknown package: " + packageName); - return; - } - if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - - boolean isRequested = false; - // Fast path, the current package has requested the permission. - if (pkg.getRequestedPermissions().contains(permName)) { - isRequested = true; - } - if (!isRequested) { - // Slow path, go through all shared user packages. - String[] sharedUserPackageNames = - mPackageManagerInt.getSharedUserPackagesForPackage(packageName, userId); - for (String sharedUserPackageName : sharedUserPackageNames) { - AndroidPackage sharedUserPkg = mPackageManagerInt.getPackage( - sharedUserPackageName); - if (sharedUserPkg != null - && sharedUserPkg.getRequestedPermissions().contains(permName)) { - isRequested = true; - break; - } - } - } - - final boolean isRuntimePermission; - final boolean permissionUpdated; - synchronized (mLock) { - final Permission bp = mRegistry.getPermission(permName); - if (bp == null) { - throw new IllegalArgumentException("Unknown permission: " + permName); - } - - isRuntimePermission = bp.isRuntime(); - - final UidPermissionState uidState = getUidStateLocked(pkg, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); - return; - } - - if (!uidState.hasPermissionState(permName) && !isRequested) { - Log.e(TAG, "Permission " + permName + " isn't requested by package " + packageName); - return; - } - - permissionUpdated = uidState.updatePermissionFlags(bp, flagMask, flagValues); - } - - if (permissionUpdated && isRuntimePermission) { - notifyRuntimePermissionStateChanged(packageName, userId); - } - if (permissionUpdated && callback != null) { - // Install and runtime permissions are stored in different places, - // so figure out what permission changed and persist the change. - if (!isRuntimePermission) { - int userUid = UserHandle.getUid(userId, pkg.getUid()); - callback.onInstallPermissionUpdatedNotifyListener(userUid); - } else { - callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false, pkg.getUid()); - } - } - } - - /** - * Update the permission flags for all packages and runtime permissions of a user in order - * to allow device or profile owner to remove POLICY_FIXED. - */ - @Override - public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, - final int userId) { - final int callingUid = getCallingUid(); - if (!mUserManagerInt.exists(userId)) { - return; - } - - enforceGrantRevokeRuntimePermissionPermissions( - "updatePermissionFlagsForAllApps"); - enforceCrossUserPermission(callingUid, userId, - true, // requireFullPermission - true, // checkShell - "updatePermissionFlagsForAllApps"); - - // Only the system can change system fixed flags. - final int effectiveFlagMask = (callingUid != Process.SYSTEM_UID) - ? flagMask : flagMask & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; - final int effectiveFlagValues = (callingUid != Process.SYSTEM_UID) - ? flagValues : flagValues & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; - - final boolean[] changed = new boolean[1]; - mPackageManagerInt.forEachPackage(pkg -> { - synchronized (mLock) { - final UidPermissionState uidState = getUidStateLocked(pkg, userId); - if (uidState == null) { - Slog.e(TAG, - "Missing permissions state for " + pkg.getPackageName() + " and user " - + userId); - return; - } - changed[0] |= uidState.updatePermissionFlagsForAllPermissions( - effectiveFlagMask, effectiveFlagValues); - } - mOnPermissionChangeListeners.onPermissionsChanged(pkg.getUid()); - }); - - if (changed[0]) { - mPackageManagerInt.writePermissionSettings(new int[] { userId }, true); - } - } - private int checkPermission(String pkgName, String permName, @UserIdInt int userId) { // Not using Objects.requireNonNull() here for compatibility reasons. if (pkgName == null || permName == null) { return PackageManager.PERMISSION_DENIED; } - if (!mUserManagerInt.exists(userId)) { - return PackageManager.PERMISSION_DENIED; - } final CheckPermissionDelegate checkPermissionDelegate; synchronized (mLock) { checkPermissionDelegate = mCheckPermissionDelegate; } + if (checkPermissionDelegate == null) { - return checkPermissionImpl(pkgName, permName, userId); + return mPermissionManagerServiceImpl.checkPermission(pkgName, permName, userId); } return checkPermissionDelegate.checkPermission(pkgName, permName, userId, - this::checkPermissionImpl); - } - - private int checkPermissionImpl(String pkgName, String permName, int userId) { - final AndroidPackage pkg = mPackageManagerInt.getPackage(pkgName); - if (pkg == null) { - return PackageManager.PERMISSION_DENIED; - } - return checkPermissionInternal(pkg, true, permName, userId); - } - - private int checkPermissionInternal(@NonNull AndroidPackage pkg, boolean isPackageExplicit, - @NonNull String permissionName, @UserIdInt int userId) { - final int callingUid = getCallingUid(); - if (isPackageExplicit || pkg.getSharedUserId() == null) { - if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { - return PackageManager.PERMISSION_DENIED; - } - } else { - if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { - return PackageManager.PERMISSION_DENIED; - } - } - - final int uid = UserHandle.getUid(userId, pkg.getUid()); - final boolean isInstantApp = mPackageManagerInt.getInstantAppPackageName(uid) != null; - - synchronized (mLock) { - final UidPermissionState uidState = getUidStateLocked(pkg, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " - + userId); - return PackageManager.PERMISSION_DENIED; - } - - if (checkSinglePermissionInternalLocked(uidState, permissionName, isInstantApp)) { - return PackageManager.PERMISSION_GRANTED; - } - - final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); - if (fullerPermissionName != null && checkSinglePermissionInternalLocked(uidState, - fullerPermissionName, isInstantApp)) { - return PackageManager.PERMISSION_GRANTED; - } - } - - return PackageManager.PERMISSION_DENIED; - } - - @GuardedBy("mLock") - private boolean checkSinglePermissionInternalLocked(@NonNull UidPermissionState uidState, - @NonNull String permissionName, boolean isInstantApp) { - if (!uidState.isPermissionGranted(permissionName)) { - return false; - } - - if (isInstantApp) { - final Permission permission = mRegistry.getPermission(permissionName); - return permission != null && permission.isInstant(); - } - - return true; + mPermissionManagerServiceImpl::checkPermission); } private int checkUidPermission(int uid, String permName) { @@ -1031,332 +225,17 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (permName == null) { return PackageManager.PERMISSION_DENIED; } - final int userId = UserHandle.getUserId(uid); - if (!mUserManagerInt.exists(userId)) { - return PackageManager.PERMISSION_DENIED; - } final CheckPermissionDelegate checkPermissionDelegate; synchronized (mLock) { checkPermissionDelegate = mCheckPermissionDelegate; } + if (checkPermissionDelegate == null) { - return checkUidPermissionImpl(uid, permName); + return mPermissionManagerServiceImpl.checkUidPermission(uid, permName); } return checkPermissionDelegate.checkUidPermission(uid, permName, - this::checkUidPermissionImpl); - } - - private int checkUidPermissionImpl(int uid, String permName) { - final AndroidPackage pkg = mPackageManagerInt.getPackage(uid); - return checkUidPermissionInternal(pkg, uid, permName); - } - - /** - * Checks whether or not the given package has been granted the specified - * permission. If the given package is {@code null}, we instead check the - * system permissions for the given UID. - * - * @see SystemConfig#getSystemPermissions() - */ - private int checkUidPermissionInternal(@Nullable AndroidPackage pkg, int uid, - @NonNull String permissionName) { - if (pkg != null) { - final int userId = UserHandle.getUserId(uid); - return checkPermissionInternal(pkg, false, permissionName, userId); - } - - synchronized (mLock) { - if (checkSingleUidPermissionInternalLocked(uid, permissionName)) { - return PackageManager.PERMISSION_GRANTED; - } - - final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); - if (fullerPermissionName != null - && checkSingleUidPermissionInternalLocked(uid, fullerPermissionName)) { - return PackageManager.PERMISSION_GRANTED; - } - } - - return PackageManager.PERMISSION_DENIED; - } - - @GuardedBy("mLock") - private boolean checkSingleUidPermissionInternalLocked(int uid, - @NonNull String permissionName) { - ArraySet permissions = mSystemPermissions.get(uid); - return permissions != null && permissions.contains(permissionName); - } - - @Override - public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, - "addOnPermissionsChangeListener"); - - mOnPermissionChangeListeners.addListener(listener); - } - - @Override - public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) { - if (mPackageManagerInt.getInstantAppPackageName(Binder.getCallingUid()) != null) { - throw new SecurityException("Instant applications don't have access to this method"); - } - mOnPermissionChangeListeners.removeListener(listener); - } - - @Nullable - @Override - public List getAllowlistedRestrictedPermissions(@NonNull String packageName, - @PermissionWhitelistFlags int flags, @UserIdInt int userId) { - Objects.requireNonNull(packageName); - Preconditions.checkFlagsArgument(flags, - PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE - | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); - Preconditions.checkArgumentNonNegative(userId, null); - - if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS, - "getAllowlistedRestrictedPermissions for user " + userId); - } - - final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); - if (pkg == null) { - return null; - } - - final int callingUid = Binder.getCallingUid(); - if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) { - return null; - } - final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) - == PackageManager.PERMISSION_GRANTED; - final boolean isCallerInstallerOnRecord = - mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); - - if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 - && !isCallerPrivileged) { - throw new SecurityException("Querying system allowlist requires " - + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); - } - - if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE - | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) { - if (!isCallerPrivileged && !isCallerInstallerOnRecord) { - throw new SecurityException("Querying upgrade or installer allowlist" - + " requires being installer on record or " - + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); - } - } - - final long identity = Binder.clearCallingIdentity(); - try { - return getAllowlistedRestrictedPermissionsInternal(pkg, flags, userId); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Nullable - private List getAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg, - @PermissionWhitelistFlags int flags, @UserIdInt int userId) { - synchronized (mLock) { - final UidPermissionState uidState = getUidStateLocked(pkg, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " - + userId); - return null; - } - - int queryFlags = 0; - if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0) { - queryFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; - } - if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) { - queryFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; - } - if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) { - queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; - } - - ArrayList allowlistedPermissions = null; - - final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions()); - for (int i = 0; i < permissionCount; i++) { - final String permissionName = pkg.getRequestedPermissions().get(i); - final int currentFlags = - uidState.getPermissionFlags(permissionName); - if ((currentFlags & queryFlags) != 0) { - if (allowlistedPermissions == null) { - allowlistedPermissions = new ArrayList<>(); - } - allowlistedPermissions.add(permissionName); - } - } - - return allowlistedPermissions; - } - } - - @Override - public boolean addAllowlistedRestrictedPermission(@NonNull String packageName, - @NonNull String permName, @PermissionWhitelistFlags int flags, - @UserIdInt int userId) { - // Other argument checks are done in get/setAllowlistedRestrictedPermissions - Objects.requireNonNull(permName); - - if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) { - return false; - } - - List permissions = - getAllowlistedRestrictedPermissions(packageName, flags, userId); - if (permissions == null) { - permissions = new ArrayList<>(1); - } - if (permissions.indexOf(permName) < 0) { - permissions.add(permName); - return setAllowlistedRestrictedPermissions(packageName, permissions, - flags, userId); - } - return false; - } - - private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission( - @NonNull String permName) { - final String permissionPackageName; - final boolean isImmutablyRestrictedPermission; - synchronized (mLock) { - final Permission bp = mRegistry.getPermission(permName); - if (bp == null) { - Slog.w(TAG, "No such permissions: " + permName); - return false; - } - permissionPackageName = bp.getPackageName(); - isImmutablyRestrictedPermission = bp.isHardOrSoftRestricted() - && bp.isImmutablyRestricted(); - } - - final int callingUid = getCallingUid(); - final int callingUserId = UserHandle.getUserId(callingUid); - if (mPackageManagerInt.filterAppAccess(permissionPackageName, callingUid, callingUserId)) { - EventLog.writeEvent(0x534e4554, "186404356", callingUid, permName); - return false; - } - - if (isImmutablyRestrictedPermission && mContext.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Cannot modify allowlisting of an immutably " - + "restricted permission: " + permName); - } - - return true; - } - - @Override - public boolean removeAllowlistedRestrictedPermission(@NonNull String packageName, - @NonNull String permName, @PermissionWhitelistFlags int flags, - @UserIdInt int userId) { - // Other argument checks are done in get/setAllowlistedRestrictedPermissions - Objects.requireNonNull(permName); - - if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) { - return false; - } - - final List permissions = - getAllowlistedRestrictedPermissions(packageName, flags, userId); - if (permissions != null && permissions.remove(permName)) { - return setAllowlistedRestrictedPermissions(packageName, permissions, - flags, userId); - } - return false; - } - - private boolean setAllowlistedRestrictedPermissions(@NonNull String packageName, - @Nullable List permissions, @PermissionWhitelistFlags int flags, - @UserIdInt int userId) { - Objects.requireNonNull(packageName); - Preconditions.checkFlagsArgument(flags, - PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE - | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); - Preconditions.checkArgument(Integer.bitCount(flags) == 1); - Preconditions.checkArgumentNonNegative(userId, null); - - if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.INTERACT_ACROSS_USERS, - "setAllowlistedRestrictedPermissions for user " + userId); - } - - final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); - if (pkg == null) { - return false; - } - - final int callingUid = Binder.getCallingUid(); - if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) { - return false; - } - - final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) - == PackageManager.PERMISSION_GRANTED; - final boolean isCallerInstallerOnRecord = - mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); - - if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 && !isCallerPrivileged) { - throw new SecurityException("Modifying system allowlist requires " - + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); - } - - if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) { - if (!isCallerPrivileged && !isCallerInstallerOnRecord) { - throw new SecurityException("Modifying upgrade allowlist requires" - + " being installer on record or " - + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); - } - final List allowlistedPermissions = - getAllowlistedRestrictedPermissions(pkg.getPackageName(), flags, userId); - if (permissions == null || permissions.isEmpty()) { - if (allowlistedPermissions == null || allowlistedPermissions.isEmpty()) { - return true; - } - } else { - // Only the system can add and remove while the installer can only remove. - final int permissionCount = permissions.size(); - for (int i = 0; i < permissionCount; i++) { - if ((allowlistedPermissions == null - || !allowlistedPermissions.contains(permissions.get(i))) - && !isCallerPrivileged) { - throw new SecurityException("Adding to upgrade allowlist requires" - + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); - } - } - } - - if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) { - if (!isCallerPrivileged && !isCallerInstallerOnRecord) { - throw new SecurityException("Modifying installer allowlist requires" - + " being installer on record or " - + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); - } - } - } - - final long identity = Binder.clearCallingIdentity(); - try { - setAllowlistedRestrictedPermissionsInternal(pkg, permissions, flags, userId); - } finally { - Binder.restoreCallingIdentity(identity); - } - - return true; + mPermissionManagerServiceImpl::checkUidPermission); } @Override @@ -1438,744 +317,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - @Override - public void grantRuntimePermission(String packageName, String permName, final int userId) { - final int callingUid = Binder.getCallingUid(); - final boolean overridePolicy = - checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) - == PackageManager.PERMISSION_GRANTED; - - grantRuntimePermissionInternal(packageName, permName, overridePolicy, - callingUid, userId, mDefaultPermissionCallback); - } - - private void grantRuntimePermissionInternal(String packageName, String permName, - boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) { - if (PermissionManager.DEBUG_TRACE_GRANTS - && PermissionManager.shouldTraceGrant(packageName, permName, userId)) { - Log.i(PermissionManager.LOG_TAG_TRACE_GRANTS, "System is granting " + packageName + " " - + permName + " for user " + userId + " on behalf of uid " + callingUid - + " " + mPackageManagerInt.getNameForUid(callingUid), - new RuntimeException()); - } - if (!mUserManagerInt.exists(userId)) { - Log.e(TAG, "No such user:" + userId); - return; - } - - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, - "grantRuntimePermission"); - - enforceCrossUserPermission(callingUid, userId, - true, // requireFullPermission - true, // checkShell - "grantRuntimePermission"); - - final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); - final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(packageName); - if (pkg == null || ps == null) { - Log.e(TAG, "Unknown package: " + packageName); - return; - } - if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - - final boolean isRolePermission; - final boolean isSoftRestrictedPermission; - synchronized (mLock) { - final Permission permission = mRegistry.getPermission(permName); - if (permission == null) { - throw new IllegalArgumentException("Unknown permission: " + permName); - } - isRolePermission = permission.isRole(); - isSoftRestrictedPermission = permission.isSoftRestricted(); - } - final boolean mayGrantRolePermission = isRolePermission - && mayManageRolePermission(callingUid); - final boolean mayGrantSoftRestrictedPermission = isSoftRestrictedPermission - && SoftRestrictedPermissionPolicy.forPermission(mContext, - AndroidPackageUtils.generateAppInfoWithoutState(pkg), pkg, - UserHandle.of(userId), permName) - .mayGrantPermission(); - - final boolean isRuntimePermission; - final boolean permissionHasGids; - synchronized (mLock) { - final Permission bp = mRegistry.getPermission(permName); - if (bp == null) { - throw new IllegalArgumentException("Unknown permission: " + permName); - } - - isRuntimePermission = bp.isRuntime(); - permissionHasGids = bp.hasGids(); - if (isRuntimePermission || bp.isDevelopment()) { - // Good. - } else if (bp.isRole()) { - if (!mayGrantRolePermission) { - throw new SecurityException("Permission " + permName + " is managed by role"); - } - } else { - throw new SecurityException("Permission " + permName + " requested by " - + pkg.getPackageName() + " is not a changeable permission type"); - } - - final UidPermissionState uidState = getUidStateLocked(pkg, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " - + userId); - return; - } - - if (!(uidState.hasPermissionState(permName) - || pkg.getRequestedPermissions().contains(permName))) { - throw new SecurityException("Package " + pkg.getPackageName() - + " has not requested permission " + permName); - } - - // If a permission review is required for legacy apps we represent - // their permissions as always granted runtime ones since we need - // to keep the review required permission flag per user while an - // install permission's state is shared across all users. - if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) { - return; - } - - final int flags = uidState.getPermissionFlags(permName); - if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) { - Log.e(TAG, "Cannot grant system fixed permission " - + permName + " for package " + packageName); - return; - } - if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { - Log.e(TAG, "Cannot grant policy fixed permission " - + permName + " for package " + packageName); - return; - } - - if (bp.isHardRestricted() - && (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { - Log.e(TAG, "Cannot grant hard restricted non-exempt permission " - + permName + " for package " + packageName); - return; - } - - if (bp.isSoftRestricted() && !mayGrantSoftRestrictedPermission) { - Log.e(TAG, "Cannot grant soft restricted permission " + permName + " for package " - + packageName); - return; - } - - if (bp.isDevelopment() || bp.isRole()) { - // Development permissions must be handled specially, since they are not - // normal runtime permissions. For now they apply to all users. - // TODO(zhanghai): We are breaking the behavior above by making all permission state - // per-user. It isn't documented behavior and relatively rarely used anyway. - if (!uidState.grantPermission(bp)) { - return; - } - } else { - if (ps.getUserStateOrDefault(userId).isInstantApp() && !bp.isInstant()) { - throw new SecurityException("Cannot grant non-ephemeral permission" + permName - + " for package " + packageName); - } - - if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { - Slog.w(TAG, "Cannot grant runtime permission to a legacy app"); - return; - } - - if (!uidState.grantPermission(bp)) { - return; - } - } - } - - if (isRuntimePermission) { - logPermission(MetricsEvent.ACTION_PERMISSION_GRANTED, permName, packageName); - } - - final int uid = UserHandle.getUid(userId, pkg.getUid()); - if (callback != null) { - if (isRuntimePermission) { - callback.onPermissionGranted(uid, userId); - } else { - callback.onInstallPermissionGranted(); - } - if (permissionHasGids) { - callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId); - } - } - - if (isRuntimePermission) { - notifyRuntimePermissionStateChanged(packageName, userId); - } - } - - @Override - public void revokeRuntimePermission(String packageName, String permName, int userId, - String reason) { - final int callingUid = Binder.getCallingUid(); - final boolean overridePolicy = - checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) - == PackageManager.PERMISSION_GRANTED; - - revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId, - reason, mDefaultPermissionCallback); - } - - private void revokeRuntimePermissionInternal(String packageName, String permName, - boolean overridePolicy, int callingUid, final int userId, String reason, - PermissionCallback callback) { - if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES - && PermissionManager.shouldTraceGrant(packageName, permName, userId)) { - Log.i(TAG, "System is revoking " + packageName + " " - + permName + " for user " + userId + " on behalf of uid " + callingUid - + " " + mPackageManagerInt.getNameForUid(callingUid), - new RuntimeException()); - } - if (!mUserManagerInt.exists(userId)) { - Log.e(TAG, "No such user:" + userId); - return; - } - - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, - "revokeRuntimePermission"); - - enforceCrossUserPermission(callingUid, userId, - true, // requireFullPermission - true, // checkShell - "revokeRuntimePermission"); - - final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); - if (pkg == null) { - Log.e(TAG, "Unknown package: " + packageName); - return; - } - if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - - final boolean isRolePermission; - synchronized (mLock) { - final Permission permission = mRegistry.getPermission(permName); - if (permission == null) { - throw new IllegalArgumentException("Unknown permission: " + permName); - } - isRolePermission = permission.isRole(); - } - final boolean mayRevokeRolePermission = isRolePermission - // Allow ourselves to revoke role permissions due to definition changes. - && (callingUid == Process.myUid() || mayManageRolePermission(callingUid)); - - final boolean isRuntimePermission; - synchronized (mLock) { - final Permission bp = mRegistry.getPermission(permName); - if (bp == null) { - throw new IllegalArgumentException("Unknown permission: " + permName); - } - - isRuntimePermission = bp.isRuntime(); - if (isRuntimePermission || bp.isDevelopment()) { - // Good. - } else if (bp.isRole()) { - if (!mayRevokeRolePermission) { - throw new SecurityException("Permission " + permName + " is managed by role"); - } - } else { - throw new SecurityException("Permission " + permName + " requested by " - + pkg.getPackageName() + " is not a changeable permission type"); - } - - final UidPermissionState uidState = getUidStateLocked(pkg, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " - + userId); - return; - } - - if (!(uidState.hasPermissionState(permName) - || pkg.getRequestedPermissions().contains(permName))) { - throw new SecurityException("Package " + pkg.getPackageName() - + " has not requested permission " + permName); - } - - // If a permission review is required for legacy apps we represent - // their permissions as always granted runtime ones since we need - // to keep the review required permission flag per user while an - // install permission's state is shared across all users. - if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) { - return; - } - - final int flags = uidState.getPermissionFlags(permName); - // Only the system may revoke SYSTEM_FIXED permissions. - if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0 - && UserHandle.getCallingAppId() != Process.SYSTEM_UID) { - throw new SecurityException("Non-System UID cannot revoke system fixed permission " - + permName + " for package " + packageName); - } - if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { - throw new SecurityException("Cannot revoke policy fixed permission " - + permName + " for package " + packageName); - } - - // Development permissions must be handled specially, since they are not - // normal runtime permissions. For now they apply to all users. - // TODO(zhanghai): We are breaking the behavior above by making all permission state - // per-user. It isn't documented behavior and relatively rarely used anyway. - if (!uidState.revokePermission(bp)) { - return; - } - } - - if (isRuntimePermission) { - logPermission(MetricsEvent.ACTION_PERMISSION_REVOKED, permName, packageName); - } - - if (callback != null) { - if (isRuntimePermission) { - callback.onPermissionRevoked(UserHandle.getUid(userId, pkg.getUid()), userId, - reason); - } else { - mDefaultPermissionCallback.onInstallPermissionRevoked(); - } - } - - if (isRuntimePermission) { - notifyRuntimePermissionStateChanged(packageName, userId); - } - } - - private boolean mayManageRolePermission(int uid) { - final PackageManager packageManager = mContext.getPackageManager(); - final String[] packageNames = packageManager.getPackagesForUid(uid); - if (packageNames == null) { - return false; - } - final String permissionControllerPackageName = - packageManager.getPermissionControllerPackageName(); - return Arrays.asList(packageNames).contains(permissionControllerPackageName); - } - - /** - * Reverts user permission state changes (permissions and flags). - * - * @param pkg The package for which to reset. - * @param userId The device user for which to do a reset. - */ - private void resetRuntimePermissionsInternal(@NonNull AndroidPackage pkg, - @UserIdInt int userId) { - final String packageName = pkg.getPackageName(); - - // These are flags that can change base on user actions. - final int userSettableMask = FLAG_PERMISSION_USER_SET - | FLAG_PERMISSION_USER_FIXED - | FLAG_PERMISSION_REVOKED_COMPAT - | FLAG_PERMISSION_REVIEW_REQUIRED - | FLAG_PERMISSION_ONE_TIME - | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; - - final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED - | FLAG_PERMISSION_POLICY_FIXED; - - // Delay and combine non-async permission callbacks - final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions()); - final boolean[] permissionRemoved = new boolean[1]; - final ArraySet revokedPermissions = new ArraySet<>(); - final IntArray syncUpdatedUsers = new IntArray(permissionCount); - final IntArray asyncUpdatedUsers = new IntArray(permissionCount); - - PermissionCallback delayingPermCallback = new PermissionCallback() { - public void onGidsChanged(int appId, int userId) { - mDefaultPermissionCallback.onGidsChanged(appId, userId); - } - - public void onPermissionChanged() { - mDefaultPermissionCallback.onPermissionChanged(); - } - - public void onPermissionGranted(int uid, int userId) { - mDefaultPermissionCallback.onPermissionGranted(uid, userId); - } - - public void onInstallPermissionGranted() { - mDefaultPermissionCallback.onInstallPermissionGranted(); - } - - public void onPermissionRevoked(int uid, int userId, String reason) { - revokedPermissions.add(IntPair.of(uid, userId)); - - syncUpdatedUsers.add(userId); - } - - public void onInstallPermissionRevoked() { - mDefaultPermissionCallback.onInstallPermissionRevoked(); - } - - public void onPermissionUpdated(int[] updatedUserIds, boolean sync) { - for (int userId : updatedUserIds) { - if (sync) { - syncUpdatedUsers.add(userId); - asyncUpdatedUsers.remove(userId); - } else { - // Don't override sync=true by sync=false - if (syncUpdatedUsers.indexOf(userId) == -1) { - asyncUpdatedUsers.add(userId); - } - } - } - } - - public void onPermissionRemoved() { - permissionRemoved[0] = true; - } - - public void onInstallPermissionUpdated() { - mDefaultPermissionCallback.onInstallPermissionUpdated(); - } - - public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, - boolean sync, int uid) { - onPermissionUpdated(updatedUserIds, sync); - mOnPermissionChangeListeners.onPermissionsChanged(uid); - } - - public void onInstallPermissionUpdatedNotifyListener(int uid) { - mDefaultPermissionCallback.onInstallPermissionUpdatedNotifyListener(uid); - } - }; - - for (int i = 0; i < permissionCount; i++) { - final String permName = pkg.getRequestedPermissions().get(i); - - final boolean isRuntimePermission; - synchronized (mLock) { - final Permission permission = mRegistry.getPermission(permName); - if (permission == null) { - continue; - } - - if (permission.isRemoved()) { - continue; - } - isRuntimePermission = permission.isRuntime(); - } - - // If shared user we just reset the state to which only this app contributed. - final String[] pkgNames = mPackageManagerInt.getSharedUserPackagesForPackage( - pkg.getPackageName(), userId); - if (pkgNames.length > 0) { - boolean used = false; - for (String sharedPkgName : pkgNames) { - final AndroidPackage sharedPkg = - mPackageManagerInt.getPackage(sharedPkgName); - if (sharedPkg != null && !sharedPkg.getPackageName().equals(packageName) - && sharedPkg.getRequestedPermissions().contains(permName)) { - used = true; - break; - } - } - if (used) { - continue; - } - } - - final int oldFlags = - getPermissionFlagsInternal(packageName, permName, Process.SYSTEM_UID, userId); - - // Always clear the user settable flags. - // If permission review is enabled and this is a legacy app, mark the - // permission as requiring a review as this is the initial state. - final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId); - final int targetSdk = mPackageManagerInt.getUidTargetSdkVersion(uid); - final int flags = (targetSdk < Build.VERSION_CODES.M && isRuntimePermission) - ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKED_COMPAT - : 0; - - updatePermissionFlagsInternal( - packageName, permName, userSettableMask, flags, Process.SYSTEM_UID, userId, - false, delayingPermCallback); - - // Below is only runtime permission handling. - if (!isRuntimePermission) { - continue; - } - - // Never clobber system or policy. - if ((oldFlags & policyOrSystemFlags) != 0) { - continue; - } - - // If this permission was granted by default or role, make sure it is. - if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0 - || (oldFlags & FLAG_PERMISSION_GRANTED_BY_ROLE) != 0) { - // PermissionPolicyService will handle the app op for runtime permissions later. - grantRuntimePermissionInternal(packageName, permName, false, - Process.SYSTEM_UID, userId, delayingPermCallback); - // In certain cases we should leave the state unchanged: - // -- If permission review is enabled the permissions for a legacy apps - // are represented as constantly granted runtime ones - // -- If the permission was split from a non-runtime permission - } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0 - && !isPermissionSplitFromNonRuntime(permName, targetSdk)) { - // Otherwise, reset the permission. - revokeRuntimePermissionInternal(packageName, permName, false, Process.SYSTEM_UID, - userId, null, delayingPermCallback); - } - } - - // Execute delayed callbacks - if (permissionRemoved[0]) { - mDefaultPermissionCallback.onPermissionRemoved(); - } - - // Slight variation on the code in mPermissionCallback.onPermissionRevoked() as we cannot - // kill uid while holding mPackages-lock - if (!revokedPermissions.isEmpty()) { - int numRevokedPermissions = revokedPermissions.size(); - for (int i = 0; i < numRevokedPermissions; i++) { - int revocationUID = IntPair.first(revokedPermissions.valueAt(i)); - int revocationUserId = IntPair.second(revokedPermissions.valueAt(i)); - - mOnPermissionChangeListeners.onPermissionsChanged(revocationUID); - - // Kill app later as we are holding mPackages - mHandler.post(() -> killUid(UserHandle.getAppId(revocationUID), revocationUserId, - KILL_APP_REASON_PERMISSIONS_REVOKED)); - } - } - - mPackageManagerInt.writePermissionSettings(syncUpdatedUsers.toArray(), false); - mPackageManagerInt.writePermissionSettings(asyncUpdatedUsers.toArray(), true); - } - - /** - * Determine if the given permission should be treated as split from a - * non-runtime permission for an application targeting the given SDK level. - */ - private boolean isPermissionSplitFromNonRuntime(String permName, int targetSdk) { - final List splitPerms = getSplitPermissionInfos(); - final int size = splitPerms.size(); - for (int i = 0; i < size; i++) { - final PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(i); - if (targetSdk < splitPerm.getTargetSdk() - && splitPerm.getNewPermissions().contains(permName)) { - synchronized (mLock) { - final Permission perm = - mRegistry.getPermission(splitPerm.getSplitPermission()); - return perm != null && !perm.isRuntime(); - } - } - } - return false; - } - - /** - * This change makes it so that apps are told to show rationale for asking for background - * location access every time they request. - */ - @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) - private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L; - - @Override - public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - @UserIdInt int userId) { - final int callingUid = Binder.getCallingUid(); - if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "canShowRequestPermissionRationale for user " + userId); - } - - final int uid = - mPackageManagerInt.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId); - if (UserHandle.getAppId(callingUid) != UserHandle.getAppId(uid)) { - return false; - } - - if (checkPermission(packageName, permName, userId) - == PackageManager.PERMISSION_GRANTED) { - return false; - } - - final int flags; - - final long identity = Binder.clearCallingIdentity(); - try { - flags = getPermissionFlagsInternal(packageName, permName, callingUid, userId); - } finally { - Binder.restoreCallingIdentity(identity); - } - - final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED - | PackageManager.FLAG_PERMISSION_POLICY_FIXED - | PackageManager.FLAG_PERMISSION_USER_FIXED; - - if ((flags & fixedFlags) != 0) { - return false; - } - - synchronized (mLock) { - final Permission permission = mRegistry.getPermission(permName); - if (permission == null) { - return false; - } - if (permission.isHardRestricted() - && (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { - return false; - } - } - - final long token = Binder.clearCallingIdentity(); - try { - if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION) - && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID, - packageName, userId)) { - return true; - } - } catch (RemoteException e) { - Log.e(TAG, "Unable to check if compatibility change is enabled.", e); - } finally { - Binder.restoreCallingIdentity(token); - } - - return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0; - } - - @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { - if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "isPermissionRevokedByPolicy for user " + userId); - } - - if (checkPermission(packageName, permName, userId) == PackageManager.PERMISSION_GRANTED) { - return false; - } - - final int callingUid = Binder.getCallingUid(); - if (mPackageManagerInt.filterAppAccess(packageName, callingUid, userId)) { - return false; - } - - final long identity = Binder.clearCallingIdentity(); - try { - final int flags = getPermissionFlagsInternal(packageName, permName, callingUid, userId); - return (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Get the state of the runtime permissions as xml file. - * - *

Can not be called on main thread. - * - * @param userId The user ID the data should be extracted for - * - * @return The state as a xml file - */ - @Nullable - private byte[] backupRuntimePermissions(@UserIdInt int userId) { - CompletableFuture backup = new CompletableFuture<>(); - mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId), - mContext.getMainExecutor(), backup::complete); - - try { - return backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - Slog.e(TAG, "Cannot create permission backup for user " + userId, e); - return null; - } - } - - /** - * Restore a permission state previously backed up via {@link #backupRuntimePermissions}. - * - *

If not all state can be restored, the un-appliable state will be delayed and can be - * applied via {@link #restoreDelayedRuntimePermissions}. - * - * @param backup The state as an xml file - * @param userId The user ID the data should be restored for - */ - private void restoreRuntimePermissions(@NonNull byte[] backup, @UserIdInt int userId) { - synchronized (mLock) { - mHasNoDelayedPermBackup.delete(userId); - } - mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(backup, - UserHandle.of(userId)); - } - - /** - * Try to apply permission backup that was previously not applied. - * - *

Can not be called on main thread. - * - * @param packageName The package that is newly installed - * @param userId The user ID the package is installed for - * - * @see #restoreRuntimePermissions - */ - private void restoreDelayedRuntimePermissions(@NonNull String packageName, - @UserIdInt int userId) { - synchronized (mLock) { - if (mHasNoDelayedPermBackup.get(userId, false)) { - return; - } - } - mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName, - UserHandle.of(userId), mContext.getMainExecutor(), (hasMoreBackup) -> { - if (hasMoreBackup) { - return; - } - synchronized (mLock) { - mHasNoDelayedPermBackup.put(userId, true); - } - }); - } - - private void addOnRuntimePermissionStateChangedListener(@NonNull - OnRuntimePermissionStateChangedListener listener) { - synchronized (mLock) { - mRuntimePermissionStateChangedListeners.add(listener); - } - } - - private void removeOnRuntimePermissionStateChangedListener(@NonNull - OnRuntimePermissionStateChangedListener listener) { - synchronized (mLock) { - mRuntimePermissionStateChangedListeners.remove(listener); - } - } - - private void notifyRuntimePermissionStateChanged(@NonNull String packageName, - @UserIdInt int userId) { - FgThread.getHandler().sendMessage(PooledLambda.obtainMessage - (PermissionManagerService::doNotifyRuntimePermissionStateChanged, - PermissionManagerService.this, packageName, userId)); - } - - private void doNotifyRuntimePermissionStateChanged(@NonNull String packageName, - @UserIdInt int userId) { - final ArrayList listeners; - synchronized (mLock) { - if (mRuntimePermissionStateChangedListeners.isEmpty()) { - return; - } - listeners = new ArrayList<>(mRuntimePermissionStateChangedListeners); - } - final int listenerCount = listeners.size(); - for (int i = 0; i < listenerCount; i++) { - listeners.get(i).onRuntimePermissionStateChanged(packageName, userId); - } - } - private void startShellPermissionIdentityDelegationInternal(int uid, @NonNull String packageName, @Nullable List permissionNames) { synchronized (mLock) { @@ -2212,1150 +353,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { mCheckPermissionDelegate = delegate; } - /** - * If the app is updated, and has scoped storage permissions, then it is possible that the - * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. - * @param newPackage The new package that was installed - * @param oldPackage The old package that was updated - */ - private void revokeStoragePermissionsIfScopeExpandedInternal( - @NonNull AndroidPackage newPackage, - @NonNull AndroidPackage oldPackage) { - boolean downgradedSdk = oldPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q - && newPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q; - boolean upgradedSdk = oldPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q - && newPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q; - boolean newlyRequestsLegacy = !upgradedSdk && !oldPackage.isRequestLegacyExternalStorage() - && newPackage.isRequestLegacyExternalStorage(); - - if (!newlyRequestsLegacy && !downgradedSdk) { - return; - } - - final int callingUid = Binder.getCallingUid(); - for (int userId: getAllUserIds()) { - int numRequestedPermissions = newPackage.getRequestedPermissions().size(); - for (int i = 0; i < numRequestedPermissions; i++) { - PermissionInfo permInfo = getPermissionInfo( - newPackage.getRequestedPermissions().get(i), - newPackage.getPackageName(), 0); - if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) { - continue; - } - - EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(), - "Revoking permission " + permInfo.name + " from package " - + newPackage.getPackageName() + " as either the sdk downgraded " - + downgradedSdk + " or newly requested legacy full storage " - + newlyRequestsLegacy); - - try { - revokeRuntimePermissionInternal(newPackage.getPackageName(), permInfo.name, - false, callingUid, userId, null, mDefaultPermissionCallback); - } catch (IllegalStateException | SecurityException e) { - Log.e(TAG, "unable to revoke " + permInfo.name + " for " - + newPackage.getPackageName() + " user " + userId, e); - } - } - } - - } - - /** - * We might auto-grant permissions if any permission of the group is already granted. Hence if - * the group of a granted permission changes we need to revoke it to avoid having permissions of - * the new group auto-granted. - * - * @param newPackage The new package that was installed - * @param oldPackage The old package that was updated - */ - private void revokeRuntimePermissionsIfGroupChangedInternal(@NonNull AndroidPackage newPackage, - @NonNull AndroidPackage oldPackage) { - final int numOldPackagePermissions = ArrayUtils.size(oldPackage.getPermissions()); - final ArrayMap oldPermissionNameToGroupName - = new ArrayMap<>(numOldPackagePermissions); - - for (int i = 0; i < numOldPackagePermissions; i++) { - final ParsedPermission permission = oldPackage.getPermissions().get(i); - - if (permission.getParsedPermissionGroup() != null) { - oldPermissionNameToGroupName.put(permission.getName(), - permission.getParsedPermissionGroup().getName()); - } - } - - final int callingUid = Binder.getCallingUid(); - final int numNewPackagePermissions = ArrayUtils.size(newPackage.getPermissions()); - for (int newPermissionNum = 0; newPermissionNum < numNewPackagePermissions; - newPermissionNum++) { - final ParsedPermission newPermission = - newPackage.getPermissions().get(newPermissionNum); - final int newProtection = ParsedPermissionUtils.getProtection(newPermission); - - if ((newProtection & PermissionInfo.PROTECTION_DANGEROUS) != 0) { - final String permissionName = newPermission.getName(); - final String newPermissionGroupName = - newPermission.getParsedPermissionGroup() == null - ? null : newPermission.getParsedPermissionGroup().getName(); - final String oldPermissionGroupName = oldPermissionNameToGroupName.get( - permissionName); - - if (newPermissionGroupName != null - && !newPermissionGroupName.equals(oldPermissionGroupName)) { - final int[] userIds = mUserManagerInt.getUserIds(); - mPackageManagerInt.forEachPackage(pkg -> { - final String packageName = pkg.getPackageName(); - for (final int userId : userIds) { - final int permissionState = checkPermission(packageName, permissionName, - userId); - if (permissionState == PackageManager.PERMISSION_GRANTED) { - EventLog.writeEvent(0x534e4554, "72710897", - newPackage.getUid(), - "Revoking permission " + permissionName + - " from package " + packageName + - " as the group changed from " + oldPermissionGroupName + - " to " + newPermissionGroupName); - - try { - revokeRuntimePermissionInternal(packageName, permissionName, - false, callingUid, userId, null, - mDefaultPermissionCallback); - } catch (IllegalArgumentException e) { - Slog.e(TAG, "Could not revoke " + permissionName + " from " - + packageName, e); - } - } - } - }); - } - } - } - } - - /** - * If permissions are upgraded to runtime, or their owner changes to the system, then any - * granted permissions must be revoked. - * - * @param permissionsToRevoke A list of permission names to revoke - */ - private void revokeRuntimePermissionsIfPermissionDefinitionChangedInternal( - @NonNull List permissionsToRevoke) { - final int[] userIds = mUserManagerInt.getUserIds(); - final int numPermissions = permissionsToRevoke.size(); - final int callingUid = Binder.getCallingUid(); - - for (int permNum = 0; permNum < numPermissions; permNum++) { - final String permName = permissionsToRevoke.get(permNum); - final boolean isInternalPermission; - synchronized (mLock) { - final Permission bp = mRegistry.getPermission(permName); - if (bp == null || !(bp.isInternal() || bp.isRuntime())) { - continue; - } - isInternalPermission = bp.isInternal(); - } - mPackageManagerInt.forEachPackage(pkg -> { - final String packageName = pkg.getPackageName(); - final int appId = pkg.getUid(); - if (appId < Process.FIRST_APPLICATION_UID) { - // do not revoke from system apps - return; - } - for (final int userId : userIds) { - final int permissionState = checkPermissionImpl(packageName, permName, - userId); - final int flags = getPermissionFlags(packageName, permName, userId); - final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED - | FLAG_PERMISSION_POLICY_FIXED - | FLAG_PERMISSION_GRANTED_BY_DEFAULT - | FLAG_PERMISSION_GRANTED_BY_ROLE; - if (permissionState == PackageManager.PERMISSION_GRANTED - && (flags & flagMask) == 0) { - final int uid = UserHandle.getUid(userId, appId); - if (isInternalPermission) { - EventLog.writeEvent(0x534e4554, "195338390", uid, - "Revoking permission " + permName + " from package " - + packageName + " due to definition change"); - } else { - EventLog.writeEvent(0x534e4554, "154505240", uid, - "Revoking permission " + permName + " from package " - + packageName + " due to definition change"); - EventLog.writeEvent(0x534e4554, "168319670", uid, - "Revoking permission " + permName + " from package " - + packageName + " due to definition change"); - } - Slog.e(TAG, "Revoking permission " + permName + " from package " - + packageName + " due to definition change"); - try { - revokeRuntimePermissionInternal(packageName, permName, - false, callingUid, userId, null, mDefaultPermissionCallback); - } catch (Exception e) { - Slog.e(TAG, "Could not revoke " + permName + " from " - + packageName, e); - } - } - } - }); - } - } - - private List addAllPermissionsInternal(@NonNull AndroidPackage pkg) { - final int N = ArrayUtils.size(pkg.getPermissions()); - ArrayList definitionChangedPermissions = new ArrayList<>(); - for (int i=0; i Build.VERSION_CODES.LOLLIPOP_MR1) { - ComponentMutateUtils.setParsedPermissionGroup(p, - mRegistry.getPermissionGroup(p.getGroup())); - // Warn for a permission in an unknown group. - if (DEBUG_PERMISSIONS - && p.getGroup() != null && p.getParsedPermissionGroup() == null) { - Slog.i(TAG, "Permission " + p.getName() + " from package " - + p.getPackageName() + " in an unknown group " + p.getGroup()); - } - } - - permissionInfo = PackageInfoUtils.generatePermissionInfo(p, - PackageManager.GET_META_DATA); - oldPermission = p.isTree() ? mRegistry.getPermissionTree(p.getName()) - : mRegistry.getPermission(p.getName()); - } - // TODO(zhanghai): Maybe we should store whether a permission is owned by system inside - // itself. - final boolean isOverridingSystemPermission = Permission.isOverridingSystemPermission( - oldPermission, permissionInfo, mPackageManagerInt); - synchronized (mLock) { - final Permission permission = Permission.createOrUpdate(oldPermission, - permissionInfo, pkg, mRegistry.getPermissionTrees(), - isOverridingSystemPermission); - if (p.isTree()) { - mRegistry.addPermissionTree(permission); - } else { - mRegistry.addPermission(permission); - } - if (permission.isInstalled()) { - ComponentMutateUtils.setExactFlags(p, - p.getFlags() | PermissionInfo.FLAG_INSTALLED); - } - if (permission.isDefinitionChanged()) { - definitionChangedPermissions.add(p.getName()); - permission.setDefinitionChanged(false); - } - } - } - return definitionChangedPermissions; - } - - private void addAllPermissionGroupsInternal(@NonNull AndroidPackage pkg) { - synchronized (mLock) { - final int N = ArrayUtils.size(pkg.getPermissionGroups()); - StringBuilder r = null; - for (int i = 0; i < N; i++) { - final ParsedPermissionGroup pg = pkg.getPermissionGroups().get(i); - final ParsedPermissionGroup cur = mRegistry.getPermissionGroup(pg.getName()); - final String curPackageName = (cur == null) ? null : cur.getPackageName(); - final boolean isPackageUpdate = pg.getPackageName().equals(curPackageName); - if (cur == null || isPackageUpdate) { - mRegistry.addPermissionGroup(pg); - if (DEBUG_PACKAGE_SCANNING) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - if (isPackageUpdate) { - r.append("UPD:"); - } - r.append(pg.getName()); - } - } else { - Slog.w(TAG, "Permission group " + pg.getName() + " from package " - + pg.getPackageName() + " ignored: original from " - + cur.getPackageName()); - if (DEBUG_PACKAGE_SCANNING) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - r.append("DUP:"); - r.append(pg.getName()); - } - } - } - if (r != null && DEBUG_PACKAGE_SCANNING) { - Log.d(TAG, " Permission Groups: " + r); - } - } - } - - private void removeAllPermissionsInternal(@NonNull AndroidPackage pkg) { - synchronized (mLock) { - int N = ArrayUtils.size(pkg.getPermissions()); - StringBuilder r = null; - for (int i=0; i getGrantedPermissionsInternal(@NonNull String packageName, - @UserIdInt int userId) { - final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(packageName); - if (ps == null) { - return Collections.emptySet(); - } - - synchronized (mLock) { - final UidPermissionState uidState = getUidStateLocked(ps, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); - return Collections.emptySet(); - } - if (!ps.getUserStateOrDefault(userId).isInstantApp()) { - return uidState.getGrantedPermissions(); - } else { - // Install permission state is shared among all users, but instant app state is - // per-user, so we can only filter it here unless we make install permission state - // per-user as well. - final Set instantPermissions = - new ArraySet<>(uidState.getGrantedPermissions()); - instantPermissions.removeIf(permissionName -> { - Permission permission = mRegistry.getPermission(permissionName); - if (permission == null) { - return true; - } - if (!permission.isInstant()) { - EventLog.writeEvent(0x534e4554, "140256621", UserHandle.getUid(userId, - ps.getAppId()), permissionName); - return true; - } - return false; - }); - return instantPermissions; - } - } - } - - @NonNull - private int[] getPermissionGidsInternal(@NonNull String permissionName, @UserIdInt int userId) { - synchronized (mLock) { - Permission permission = mRegistry.getPermission(permissionName); - if (permission == null) { - return EmptyArray.INT; - } - return permission.computeGids(userId); - } - } - - /** - * Restore the permission state for a package. - * - *

    - *
  • During boot the state gets restored from the disk
  • - *
  • During app update the state gets restored from the last version of the app
  • - *
- * - * @param pkg the package the permissions belong to - * @param replace if the package is getting replaced (this might change the requested - * permissions of this package) - * @param packageOfInterest If this is the name of {@code pkg} add extra logging - * @param callback Result call back - * @param filterUserId If not {@link UserHandle.USER_ALL}, only restore the permission state for - * this particular user - */ - private void restorePermissionState(@NonNull AndroidPackage pkg, boolean replace, - @Nullable String packageOfInterest, @Nullable PermissionCallback callback, - @UserIdInt int filterUserId) { - // IMPORTANT: There are two types of permissions: install and runtime. - // Install time permissions are granted when the app is installed to - // all device users and users added in the future. Runtime permissions - // are granted at runtime explicitly to specific users. Normal and signature - // protected permissions are install time permissions. Dangerous permissions - // are install permissions if the app's target SDK is Lollipop MR1 or older, - // otherwise they are runtime permissions. This function does not manage - // runtime permissions except for the case an app targeting Lollipop MR1 - // being upgraded to target a newer SDK, in which case dangerous permissions - // are transformed from install time to runtime ones. - - final PackageStateInternal ps = - mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); - if (ps == null) { - return; - } - - final int[] userIds = filterUserId == UserHandle.USER_ALL ? getAllUserIds() - : new int[] { filterUserId }; - - boolean runtimePermissionsRevoked = false; - int[] updatedUserIds = EMPTY_INT_ARRAY; - - ArraySet isPrivilegedPermissionAllowlisted = null; - ArraySet shouldGrantSignaturePermission = null; - ArraySet shouldGrantInternalPermission = null; - ArraySet shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>(); - final List requestedPermissions = pkg.getRequestedPermissions(); - final int requestedPermissionsSize = requestedPermissions.size(); - for (int i = 0; i < requestedPermissionsSize; i++) { - final String permissionName = pkg.getRequestedPermissions().get(i); - - final Permission permission; - synchronized (mLock) { - permission = mRegistry.getPermission(permissionName); - } - if (permission == null) { - continue; - } - if (permission.isPrivileged() - && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) { - if (isPrivilegedPermissionAllowlisted == null) { - isPrivilegedPermissionAllowlisted = new ArraySet<>(); - } - isPrivilegedPermissionAllowlisted.add(permissionName); - } - if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission) - || shouldGrantPermissionByProtectionFlags(pkg, ps, permission, - shouldGrantPrivilegedPermissionIfWasGranted))) { - if (shouldGrantSignaturePermission == null) { - shouldGrantSignaturePermission = new ArraySet<>(); - } - shouldGrantSignaturePermission.add(permissionName); - } - if (permission.isInternal() - && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, - shouldGrantPrivilegedPermissionIfWasGranted)) { - if (shouldGrantInternalPermission == null) { - shouldGrantInternalPermission = new ArraySet<>(); - } - shouldGrantInternalPermission.add(permissionName); - } - } - - final SparseBooleanArray isPermissionPolicyInitialized = new SparseBooleanArray(); - if (mPermissionPolicyInternal != null) { - for (final int userId : userIds) { - if (mPermissionPolicyInternal.isInitialized(userId)) { - isPermissionPolicyInitialized.put(userId, true); - } - } - } - - synchronized (mLock) { - for (final int userId : userIds) { - final UserPermissionState userState = mState.getOrCreateUserState(userId); - final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId()); - - if (uidState.isMissing()) { - Collection uidRequestedPermissions; - int targetSdkVersion; - if (ps.getSharedUser() == null) { - uidRequestedPermissions = pkg.getRequestedPermissions(); - targetSdkVersion = pkg.getTargetSdkVersion(); - } else { - uidRequestedPermissions = new ArraySet<>(); - targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - List packages = ps.getSharedUser().getPackages(); - int packagesSize = packages.size(); - for (int i = 0; i < packagesSize; i++) { - AndroidPackage sharedUserPackage = packages.get(i); - uidRequestedPermissions.addAll( - sharedUserPackage.getRequestedPermissions()); - targetSdkVersion = Math.min(targetSdkVersion, - sharedUserPackage.getTargetSdkVersion()); - } - } - - for (String permissionName : uidRequestedPermissions) { - Permission permission = mRegistry.getPermission(permissionName); - if (permission == null) { - continue; - } - if (Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME) - && permission.isRuntime() && !permission.isRemoved()) { - if (permission.isHardOrSoftRestricted() - || permission.isImmutablyRestricted()) { - uidState.updatePermissionFlags(permission, - FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, - FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT); - } - if (targetSdkVersion < Build.VERSION_CODES.M) { - uidState.updatePermissionFlags(permission, - PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED - | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, - PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED - | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT); - uidState.grantPermission(permission); - } - } - } - - uidState.setMissing(false); - updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); - } - - UidPermissionState origState = uidState; - - boolean changedInstallPermission = false; - - if (replace) { - userState.setInstallPermissionsFixed(ps.getPackageName(), false); - if (ps.getSharedUser() == null) { - origState = new UidPermissionState(uidState); - uidState.reset(); - } else { - // We need to know only about runtime permission changes since the - // calling code always writes the install permissions state but - // the runtime ones are written only if changed. The only cases of - // changed runtime permissions here are promotion of an install to - // runtime and revocation of a runtime from a shared user. - if (revokeUnusedSharedUserPermissionsLocked( - ps.getSharedUser().getPackages(), uidState)) { - updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); - runtimePermissionsRevoked = true; - } - } - } - - ArraySet newImplicitPermissions = new ArraySet<>(); - final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")"; - - for (int i = 0; i < requestedPermissionsSize; i++) { - final String permName = requestedPermissions.get(i); - - final Permission bp = mRegistry.getPermission(permName); - final boolean appSupportsRuntimePermissions = - pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M; - String legacyActivityRecognitionPermission = null; - - if (DEBUG_INSTALL && bp != null) { - Log.i(TAG, "Package " + friendlyName - + " checking " + permName + ": " + bp); - } - - // TODO(zhanghai): I don't think we need to check source package setting if - // permission is present, because otherwise the permission should have been - // removed. - if (bp == null /*|| getSourcePackageSetting(bp) == null*/) { - if (packageOfInterest == null || packageOfInterest.equals( - pkg.getPackageName())) { - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Unknown permission " + permName - + " in package " + friendlyName); - } - } - continue; - } - - // Cache newImplicitPermissions before modifing permissionsState as for the - // shared uids the original and new state are the same object - if (!origState.hasPermissionState(permName) - && (pkg.getImplicitPermissions().contains(permName) - || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) { - if (pkg.getImplicitPermissions().contains(permName)) { - // If permName is an implicit permission, try to auto-grant - newImplicitPermissions.add(permName); - - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, permName + " is newly added for " + friendlyName); - } - } else { - // Special case for Activity Recognition permission. Even if AR - // permission is not an implicit permission we want to add it to the - // list (try to auto-grant it) if the app was installed on a device - // before AR permission was split, regardless of if the app now requests - // the new AR permission or has updated its target SDK and AR is no - // longer implicit to it. This is a compatibility workaround for apps - // when AR permission was split in Q. - // TODO(zhanghai): This calls into SystemConfig, which generally - // shouldn't cause deadlock, but maybe we should keep a cache of the - // split permission list and just eliminate the possibility. - final List permissionList = - getSplitPermissionInfos(); - int numSplitPerms = permissionList.size(); - for (int splitPermNum = 0; splitPermNum < numSplitPerms; - splitPermNum++) { - PermissionManager.SplitPermissionInfo sp = permissionList.get( - splitPermNum); - String splitPermName = sp.getSplitPermission(); - if (sp.getNewPermissions().contains(permName) - && origState.isPermissionGranted(splitPermName)) { - legacyActivityRecognitionPermission = splitPermName; - newImplicitPermissions.add(permName); - - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, permName + " is newly added for " - + friendlyName); - } - break; - } - } - } - } - - // TODO(b/140256621): The package instant app method has been removed - // as part of work in b/135203078, so this has been commented out in the - // meantime - // Limit ephemeral apps to ephemeral allowed permissions. - // if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) { - // if (DEBUG_PERMISSIONS) { - // Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() - // + " for package " + pkg.getPackageName()); - // } - // continue; - // } - - if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) { - if (DEBUG_PERMISSIONS) { - Log.i(TAG, "Denying runtime-only permission " + bp.getName() - + " for package " + friendlyName); - } - continue; - } - - final String perm = bp.getName(); - - // Keep track of app op permissions. - if (bp.isAppOp()) { - mRegistry.addAppOpPermissionPackage(perm, pkg.getPackageName()); - } - - boolean shouldGrantNormalPermission = true; - if (bp.isNormal() && !origState.isPermissionGranted(perm)) { - // If this is an existing, non-system package, then - // we can't add any new permissions to it. Runtime - // permissions can be added any time - they are dynamic. - if (!ps.isSystem() && userState.areInstallPermissionsFixed( - ps.getPackageName())) { - // Except... if this is a permission that was added - // to the platform (note: need to only do this when - // updating the platform). - if (!isCompatPlatformPermissionForPackage(perm, pkg)) { - shouldGrantNormalPermission = false; - } - } - } - - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Considering granting permission " + perm + " to package " - + pkg.getPackageName()); - } - - if ((bp.isNormal() && shouldGrantNormalPermission) - || (bp.isSignature() - && (!bp.isPrivileged() || CollectionUtils.contains( - isPrivilegedPermissionAllowlisted, permName)) - && (CollectionUtils.contains(shouldGrantSignaturePermission, - permName) - || (((bp.isPrivileged() && CollectionUtils.contains( - shouldGrantPrivilegedPermissionIfWasGranted, - permName)) || bp.isDevelopment() || bp.isRole()) - && origState.isPermissionGranted(permName)))) - || (bp.isInternal() - && (!bp.isPrivileged() || CollectionUtils.contains( - isPrivilegedPermissionAllowlisted, permName)) - && (CollectionUtils.contains(shouldGrantInternalPermission, - permName) - || (((bp.isPrivileged() && CollectionUtils.contains( - shouldGrantPrivilegedPermissionIfWasGranted, - permName)) || bp.isDevelopment() || bp.isRole()) - && origState.isPermissionGranted(permName))))) { - // Grant an install permission. - if (uidState.grantPermission(bp)) { - changedInstallPermission = true; - } - } else if (bp.isRuntime()) { - boolean hardRestricted = bp.isHardRestricted(); - boolean softRestricted = bp.isSoftRestricted(); - - // If permission policy is not ready we don't deal with restricted - // permissions as the policy may allowlist some permissions. Once - // the policy is initialized we would re-evaluate permissions. - final boolean permissionPolicyInitialized = - isPermissionPolicyInitialized.get(userId); - - PermissionState origPermState = origState.getPermissionState(perm); - int flags = origPermState != null ? origPermState.getFlags() : 0; - - boolean wasChanged = false; - - boolean restrictionExempt = - (origState.getPermissionFlags(bp.getName()) - & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; - boolean restrictionApplied = (origState.getPermissionFlags( - bp.getName()) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; - - if (appSupportsRuntimePermissions) { - // If hard restricted we don't allow holding it - if (permissionPolicyInitialized && hardRestricted) { - if (!restrictionExempt) { - if (origPermState != null && origPermState.isGranted() - && uidState.revokePermission(bp)) { - wasChanged = true; - } - if (!restrictionApplied) { - flags |= FLAG_PERMISSION_APPLY_RESTRICTION; - wasChanged = true; - } - } - // If soft restricted we allow holding in a restricted form - } else if (permissionPolicyInitialized && softRestricted) { - // Regardless if granted set the restriction flag as it - // may affect app treatment based on this permission. - if (!restrictionExempt && !restrictionApplied) { - flags |= FLAG_PERMISSION_APPLY_RESTRICTION; - wasChanged = true; - } - } - - // Remove review flag as it is not necessary anymore - if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED; - wasChanged = true; - } - - if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0 - && !isPermissionSplitFromNonRuntime(permName, - pkg.getTargetSdkVersion())) { - flags &= ~FLAG_PERMISSION_REVOKED_COMPAT; - wasChanged = true; - // Hard restricted permissions cannot be held. - } else if (!permissionPolicyInitialized - || (!hardRestricted || restrictionExempt)) { - if ((origPermState != null && origPermState.isGranted()) - || legacyActivityRecognitionPermission != null) { - if (!uidState.grantPermission(bp)) { - wasChanged = true; - } - } - } - } else { - if (origPermState == null) { - // New permission - if (PLATFORM_PACKAGE_NAME.equals( - bp.getPackageName())) { - if (!bp.isRemoved()) { - flags |= FLAG_PERMISSION_REVIEW_REQUIRED - | FLAG_PERMISSION_REVOKED_COMPAT; - wasChanged = true; - } - } - } - - if (!uidState.isPermissionGranted(bp.getName()) - && uidState.grantPermission(bp)) { - wasChanged = true; - } - - // If legacy app always grant the permission but if restricted - // and not exempt take a note a restriction should be applied. - if (permissionPolicyInitialized - && (hardRestricted || softRestricted) - && !restrictionExempt && !restrictionApplied) { - flags |= FLAG_PERMISSION_APPLY_RESTRICTION; - wasChanged = true; - } - } - - // If unrestricted or restriction exempt, don't apply restriction. - if (permissionPolicyInitialized) { - if (!(hardRestricted || softRestricted) || restrictionExempt) { - if (restrictionApplied) { - flags &= ~FLAG_PERMISSION_APPLY_RESTRICTION; - // Dropping restriction on a legacy app implies a review - if (!appSupportsRuntimePermissions) { - flags |= FLAG_PERMISSION_REVIEW_REQUIRED; - } - wasChanged = true; - } - } - } - - if (wasChanged) { - updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); - } - - uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, - flags); - } else { - if (DEBUG_PERMISSIONS) { - boolean wasGranted = uidState.isPermissionGranted(bp.getName()); - if (wasGranted || bp.isAppOp()) { - Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting") - + " permission " + perm - + " from package " + friendlyName - + " (protectionLevel=" + bp.getProtectionLevel() - + " flags=0x" - + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, - ps)) - + ")"); - } - } - if (uidState.removePermissionState(bp.getName())) { - changedInstallPermission = true; - } - } - } - - if ((changedInstallPermission || replace) - && !userState.areInstallPermissionsFixed(ps.getPackageName()) - && !ps.isSystem() || ps.getTransientState().isUpdatedSystemApp()) { - // This is the first that we have heard about this package, so the - // permissions we have now selected are fixed until explicitly - // changed. - userState.setInstallPermissionsFixed(ps.getPackageName(), true); - } - - updatedUserIds = revokePermissionsNoLongerImplicitLocked(uidState, pkg, - userId, updatedUserIds); - updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origState, - uidState, pkg, newImplicitPermissions, userId, updatedUserIds); - } - } - - updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds, - updatedUserIds); - - // TODO: Kill UIDs whose GIDs or runtime permissions changed. This might be more important - // for shared users. - // Persist the runtime permissions state for users with changes. If permissions - // were revoked because no app in the shared user declares them we have to - // write synchronously to avoid losing runtime permissions state. - if (callback != null) { - callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked); - } - - for (int userId : updatedUserIds) { - notifyRuntimePermissionStateChanged(pkg.getPackageName(), userId); - } - } - - /** - * Returns all relevant user ids. This list include the current set of created user ids as well - * as pre-created user ids. - * @return user ids for created users and pre-created users - */ - private int[] getAllUserIds() { - return UserManagerService.getInstance().getUserIdsIncludingPreCreated(); - } - - /** - * Revoke permissions that are not implicit anymore and that have - * {@link PackageManager#FLAG_PERMISSION_REVOKE_WHEN_REQUESTED} set. - * - * @param ps The state of the permissions of the package - * @param pkg The package that is currently looked at - * @param userIds All user IDs in the system, must be passed in because this method is locked - * @param updatedUserIds a list of user ids that needs to be amended if the permission state - * for a user is changed. - * - * @return The updated value of the {@code updatedUserIds} parameter - */ - @GuardedBy("mLock") - private @NonNull int[] revokePermissionsNoLongerImplicitLocked(@NonNull UidPermissionState ps, - @NonNull AndroidPackage pkg, int userId, @NonNull int[] updatedUserIds) { - String pkgName = pkg.getPackageName(); - boolean supportsRuntimePermissions = pkg.getTargetSdkVersion() - >= Build.VERSION_CODES.M; - - for (String permission : ps.getGrantedPermissions()) { - if (pkg.getRequestedPermissions().contains(permission) - && !pkg.getImplicitPermissions().contains(permission)) { - Permission bp = mRegistry.getPermission(permission); - if (bp != null && bp.isRuntime()) { - int flags = ps.getPermissionFlags(permission); - if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { - int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; - - // We're willing to preserve an implicit "Nearby devices" - // permission grant if this app was already able to interact - // with nearby devices via background location access - boolean preserveGrant = false; - if (ArrayUtils.contains(NEARBY_DEVICES_PERMISSIONS, permission) - && ps.isPermissionGranted( - android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) - && (ps.getPermissionFlags( - android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) - & (FLAG_PERMISSION_REVOKE_WHEN_REQUESTED - | FLAG_PERMISSION_REVOKED_COMPAT)) == 0) { - preserveGrant = true; - } - - if ((flags & BLOCKING_PERMISSION_FLAGS) == 0 - && supportsRuntimePermissions - && !preserveGrant) { - if (ps.revokePermission(bp)) { - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Revoking runtime permission " - + permission + " for " + pkgName - + " as it is now requested"); - } - } - - flagsToRemove |= USER_PERMISSION_FLAGS; - } - - ps.updatePermissionFlags(bp, flagsToRemove, 0); - updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); - } - } - } - } - - return updatedUserIds; - } - - /** - * {@code newPerm} is newly added; Inherit the state from {@code sourcePerms}. - * - *

A single new permission can be split off from several source permissions. In this case - * the most leniant state is inherited. - * - *

Warning: This does not handle foreground / background permissions - * - * @param sourcePerms The permissions to inherit from - * @param newPerm The permission to inherit to - * @param ps The permission state of the package - * @param pkg The package requesting the permissions - */ - @GuardedBy("mLock") - private void inheritPermissionStateToNewImplicitPermissionLocked( - @NonNull ArraySet sourcePerms, @NonNull String newPerm, - @NonNull UidPermissionState ps, @NonNull AndroidPackage pkg) { - String pkgName = pkg.getPackageName(); - boolean isGranted = false; - int flags = 0; - - int numSourcePerm = sourcePerms.size(); - for (int i = 0; i < numSourcePerm; i++) { - String sourcePerm = sourcePerms.valueAt(i); - if (ps.isPermissionGranted(sourcePerm)) { - if (!isGranted) { - flags = 0; - } - - isGranted = true; - flags |= ps.getPermissionFlags(sourcePerm); - } else { - if (!isGranted) { - flags |= ps.getPermissionFlags(sourcePerm); - } - } - } - - if (isGranted) { - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, newPerm + " inherits runtime perm grant from " + sourcePerms - + " for " + pkgName); - } - - ps.grantPermission(mRegistry.getPermission(newPerm)); - } - - // Add permission flags - ps.updatePermissionFlags(mRegistry.getPermission(newPerm), flags, flags); - } - - /** - * When the app has requested legacy storage we might need to update - * {@link android.app.AppOpsManager#OP_LEGACY_STORAGE}. Hence force an update in - * {@link com.android.server.policy.PermissionPolicyService#synchronizePackagePermissionsAndAppOpsForUser(Context, String, int)} - * - * @param pkg The package for which the permissions are updated - * @param replace If the app is being replaced - * @param userIds All user IDs in the system, must be passed in because this method is locked - * @param updatedUserIds The ids of the users that already changed. - * - * @return The ids of the users that are changed - */ - private @NonNull int[] checkIfLegacyStorageOpsNeedToBeUpdated(@NonNull AndroidPackage pkg, - boolean replace, @NonNull int[] userIds, @NonNull int[] updatedUserIds) { - if (replace && pkg.isRequestLegacyExternalStorage() && ( - pkg.getRequestedPermissions().contains(READ_EXTERNAL_STORAGE) - || pkg.getRequestedPermissions().contains(WRITE_EXTERNAL_STORAGE))) { - return userIds.clone(); - } - - return updatedUserIds; - } - - /** - * Set the state of a implicit permission that is seen for the first time. - * - * @param origPs The permission state of the package before the split - * @param ps The new permission state - * @param pkg The package the permission belongs to - * @param userId The user ID - * @param updatedUserIds List of users for which the permission state has already been changed - * - * @return List of users for which the permission state has been changed - */ - @GuardedBy("mLock") - private @NonNull int[] setInitialGrantForNewImplicitPermissionsLocked( - @NonNull UidPermissionState origPs, @NonNull UidPermissionState ps, - @NonNull AndroidPackage pkg, @NonNull ArraySet newImplicitPermissions, - @UserIdInt int userId, @NonNull int[] updatedUserIds) { - String pkgName = pkg.getPackageName(); - ArrayMap> newToSplitPerms = new ArrayMap<>(); - - final List permissionList = - getSplitPermissionInfos(); - int numSplitPerms = permissionList.size(); - for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { - PermissionManager.SplitPermissionInfo spi = permissionList.get(splitPermNum); - - List newPerms = spi.getNewPermissions(); - int numNewPerms = newPerms.size(); - for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) { - String newPerm = newPerms.get(newPermNum); - - ArraySet splitPerms = newToSplitPerms.get(newPerm); - if (splitPerms == null) { - splitPerms = new ArraySet<>(); - newToSplitPerms.put(newPerm, splitPerms); - } - - splitPerms.add(spi.getSplitPermission()); - } - } - - int numNewImplicitPerms = newImplicitPermissions.size(); - for (int newImplicitPermNum = 0; newImplicitPermNum < numNewImplicitPerms; - newImplicitPermNum++) { - String newPerm = newImplicitPermissions.valueAt(newImplicitPermNum); - ArraySet sourcePerms = newToSplitPerms.get(newPerm); - - if (sourcePerms != null) { - Permission bp = mRegistry.getPermission(newPerm); - if (bp == null) { - throw new IllegalStateException("Unknown new permission in split permission: " - + newPerm); - } - if (bp.isRuntime()) { - - if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) { - ps.updatePermissionFlags(bp, - FLAG_PERMISSION_REVOKE_WHEN_REQUESTED, - FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); - } - updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); - - if (!origPs.hasPermissionState(sourcePerms)) { - boolean inheritsFromInstallPerm = false; - for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size(); - sourcePermNum++) { - final String sourcePerm = sourcePerms.valueAt(sourcePermNum); - Permission sourceBp = mRegistry.getPermission(sourcePerm); - if (sourceBp == null) { - throw new IllegalStateException("Unknown source permission in split" - + " permission: " + sourcePerm); - } - if (!sourceBp.isRuntime()) { - inheritsFromInstallPerm = true; - break; - } - } - - if (!inheritsFromInstallPerm) { - // Both permissions are new so nothing to inherit. - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms - + " for " + pkgName + " as split permission is also new"); - } - continue; - } - } - - // Inherit from new install or existing runtime permissions - inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms, newPerm, ps, - pkg); - } - } else if (IMPLICIT_GRANTED_PERMISSIONS.contains(newPerm) - && !origPs.hasPermissionState(newPerm)) { - Permission bp = mRegistry.getPermission(newPerm); - if (bp == null) { - throw new IllegalStateException("Unknown new permission " + newPerm); - } - if ((ps.getPermissionState(newPerm).getFlags() - & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - // No need to grant if review is required - continue; - } - updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); - ps.updatePermissionFlags(bp, - FLAG_PERMISSION_REVOKE_WHEN_REQUESTED, - FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); - ps.grantPermission(bp); - } - } - - return updatedUserIds; - } - - @NonNull - @Override - public List getSplitPermissions() { - return PermissionManager.splitPermissionInfoListToParcelableList(getSplitPermissionInfos()); - } - - @NonNull - private List getSplitPermissionInfos() { - return SystemConfig.getInstance().getSplitPermissions(); - } - @NonNull private OneTimePermissionUserManager getOneTimePermissionUserManager(@UserIdInt int userId) { OneTimePermissionUserManager oneTimePermissionUserManager; @@ -3452,1675 +449,125 @@ public class PermissionManagerService extends IPermissionManager.Stub { return result; } - private static boolean isCompatPlatformPermissionForPackage(String perm, AndroidPackage pkg) { - boolean allowed = false; - for (int i = 0, size = CompatibilityPermissionInfo.COMPAT_PERMS.length; i < size; i++) { - final CompatibilityPermissionInfo info = CompatibilityPermissionInfo.COMPAT_PERMS[i]; - if (info.getName().equals(perm) - && pkg.getTargetSdkVersion() < info.getSdkVersion()) { - allowed = true; - Log.i(TAG, "Auto-granting " + perm + " to old pkg " - + pkg.getPackageName()); - break; - } - } - return allowed; + /* Start of delegate methods to PermissionManagerServiceInterface */ + + @Override + public ParceledListSlice getAllPermissionGroups(int flags) { + return new ParceledListSlice<>(mPermissionManagerServiceImpl.getAllPermissionGroups(flags)); } - private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg, - @NonNull PackageStateInternal packageSetting, @NonNull Permission permission) { - if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) { - return true; - } - final String packageName = pkg.getPackageName(); - if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) { - return true; - } - if (!pkg.isPrivileged()) { - return true; - } - if (!mPrivilegedPermissionAllowlistSourcePackageNames - .contains(permission.getPackageName())) { - return true; - } - final String permissionName = permission.getName(); - final ApexManager apexManager = ApexManager.getInstance(); - final String containingApexPackageName = - apexManager.getActiveApexPackageNameContainingPackage(packageName); - if (isInSystemConfigPrivAppPermissions(pkg, permissionName, - containingApexPackageName)) { - return true; - } - if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName, - containingApexPackageName)) { - return false; - } - // Updated system apps do not need to be allowlisted - if (packageSetting.getTransientState().isUpdatedSystemApp()) { - // Let shouldGrantPermissionByProtectionFlags() decide whether the privileged permission - // can be granted, because an updated system app may be in a shared UID, and in case a - // new privileged permission is requested by the updated system app but not the factory - // app, although this app and permission combination isn't in the allowlist and can't - // get the permission this way, other apps in the shared UID may still get it. A proper - // fix for this would be to perform the reconciliation by UID, but for now let's keep - // the old workaround working, which is to keep granted privileged permissions still - // granted. - return true; - } - // Only enforce the allowlist on boot - if (!mSystemReady) { - final boolean isInUpdatedApex = containingApexPackageName != null - && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName, - MATCH_ACTIVE_PACKAGE)); - // Apps that are in updated apexs' do not need to be allowlisted - if (!isInUpdatedApex) { - Slog.w(TAG, "Privileged permission " + permissionName + " for package " - + packageName + " (" + pkg.getPath() - + ") not in privapp-permissions allowlist"); - if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { - synchronized (mLock) { - if (mPrivappPermissionsViolations == null) { - mPrivappPermissionsViolations = new ArraySet<>(); - } - mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): " - + permissionName); - } - } - } - } - return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE; + @Override + public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) { + return mPermissionManagerServiceImpl.getPermissionGroupInfo(groupName, flags); } - private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg, - @NonNull String permission, String containingApexPackageName) { - final SystemConfig systemConfig = SystemConfig.getInstance(); - final Set permissions; - if (pkg.isVendor()) { - permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName()); - } else if (pkg.isProduct()) { - permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName()); - } else if (pkg.isSystemExt()) { - permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName()); - } else if (containingApexPackageName != null) { - final Set privAppPermissions = systemConfig.getPrivAppPermissions( - pkg.getPackageName()); - final Set apexPermissions = systemConfig.getApexPrivAppPermissions( - containingApexPackageName, pkg.getPackageName()); - if (privAppPermissions != null) { - // TODO(andreionea): Remove check as soon as all apk-in-apex - // permission allowlists are migrated. - Slog.w(TAG, "Package " + pkg.getPackageName() + " is an APK in APEX," - + " but has permission allowlist on the system image. Please bundle the" - + " allowlist in the " + containingApexPackageName + " APEX instead."); - if (apexPermissions != null) { - permissions = new ArraySet<>(privAppPermissions); - permissions.addAll(apexPermissions); - } else { - permissions = privAppPermissions; - } - } else { - permissions = apexPermissions; - } - } else { - permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName()); - } - return CollectionUtils.contains(permissions, permission); + @Override + public PermissionInfo getPermissionInfo(String permissionName, String packageName, int flags) { + return mPermissionManagerServiceImpl.getPermissionInfo(permissionName, packageName, flags); } - private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg, - @NonNull String permission, String containingApexPackageName) { - final SystemConfig systemConfig = SystemConfig.getInstance(); - final Set permissions; - if (pkg.isVendor()) { - permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName()); - } else if (pkg.isProduct()) { - permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName()); - } else if (pkg.isSystemExt()) { - permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName()); - } else if (containingApexPackageName != null) { - permissions = systemConfig.getApexPrivAppDenyPermissions(containingApexPackageName, - pkg.getPackageName()); - } else { - permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName()); - } - return CollectionUtils.contains(permissions, permission); - } - - private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg, - @NonNull Permission bp) { - // expect single system package - String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM)); - final AndroidPackage systemPackage = - mPackageManagerInt.getPackage(systemPackageName); - // check if the package is allow to use this signature permission. A package is allowed to - // use a signature permission if: - // - it has the same set of signing certificates as the source package - // - or its signing certificate was rotated from the source package's certificate - // - or its signing certificate is a previous signing certificate of the defining - // package, and the defining package still trusts the old certificate for permissions - // - or it shares a common signing certificate in its lineage with the defining package, - // and the defining package still trusts the old certificate for permissions - // - or it shares the above relationships with the system package - final SigningDetails sourceSigningDetails = - getSourcePackageSigningDetails(bp); - return sourceSigningDetails.hasCommonSignerWithCapability( - pkg.getSigningDetails(), - SigningDetails.CertCapabilities.PERMISSION) - || pkg.getSigningDetails().hasAncestorOrSelf(systemPackage.getSigningDetails()) - || systemPackage.getSigningDetails().checkCapability( - pkg.getSigningDetails(), - SigningDetails.CertCapabilities.PERMISSION); - } - - private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg, - @NonNull PackageStateInternal pkgSetting, @NonNull Permission bp, - @NonNull ArraySet shouldGrantPrivilegedPermissionIfWasGranted) { - boolean allowed = false; - final boolean isPrivilegedPermission = bp.isPrivileged(); - final boolean isOemPermission = bp.isOem(); - if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) { - final String permissionName = bp.getName(); - // For updated system applications, a privileged/oem permission - // is granted only if it had been defined by the original application. - if (pkgSetting.getTransientState().isUpdatedSystemApp()) { - final PackageSetting disabledPs = mPackageManagerInt - .getDisabledSystemPackage(pkg.getPackageName()); - final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.getPkg(); - if (disabledPkg != null - && ((isPrivilegedPermission && disabledPkg.isPrivileged()) - || (isOemPermission && canGrantOemPermission(disabledPkg, - permissionName)))) { - if (disabledPkg.getRequestedPermissions().contains(permissionName)) { - allowed = true; - } else { - // If the original was granted this permission, we take - // that grant decision as read and propagate it to the - // update. - shouldGrantPrivilegedPermissionIfWasGranted.add(permissionName); - } - } - } else { - allowed = (isPrivilegedPermission && pkg.isPrivileged()) - || (isOemPermission && canGrantOemPermission(pkg, permissionName)); - } - // In any case, don't grant a privileged permission to privileged vendor apps, if - // the permission's protectionLevel does not have the extra 'vendorPrivileged' - // flag. - if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) { - Slog.w(TAG, "Permission " + permissionName - + " cannot be granted to privileged vendor apk " + pkg.getPackageName() - + " because it isn't a 'vendorPrivileged' permission."); - allowed = false; - } - } - if (!allowed && bp.isPre23() && pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { - // If this was a previously normal/dangerous permission that got moved - // to a system permission as part of the runtime permission redesign, then - // we still want to blindly grant it to old apps. - allowed = true; - } - // TODO (moltmann): The installer now shares the platforms signature. Hence it does not - // need a separate flag anymore. Hence we need to check which - // permissions are needed by the permission controller - if (!allowed && bp.isInstaller() - && (ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM), - pkg.getPackageName()) || ArrayUtils.contains( - mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER, - UserHandle.USER_SYSTEM), pkg.getPackageName()))) { - // If this permission is to be granted to the system installer and - // this app is an installer, then it gets the permission. - allowed = true; - } - if (!allowed && bp.isVerifier() - && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM), - pkg.getPackageName())) { - // If this permission is to be granted to the system verifier and - // this app is a verifier, then it gets the permission. - allowed = true; - } - if (!allowed && bp.isPreInstalled() - && pkg.isSystem()) { - // Any pre-installed system app is allowed to get this permission. - allowed = true; - } - if (!allowed && bp.isKnownSigner()) { - // If the permission is to be granted to a known signer then check if any of this - // app's signing certificates are in the trusted certificate digest Set. - allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts()); - } - // Deferred to be checked under permission data lock inside restorePermissionState(). - //if (!allowed && bp.isDevelopment()) { - // // For development permissions, a development permission - // // is granted only if it was already granted. - // allowed = origPermissions.isPermissionGranted(permissionName); - //} - if (!allowed && bp.isSetup() - && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM), - pkg.getPackageName())) { - // If this permission is to be granted to the system setup wizard and - // this app is a setup wizard, then it gets the permission. - allowed = true; - } - if (!allowed && bp.isSystemTextClassifier() - && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER, - UserHandle.USER_SYSTEM), pkg.getPackageName())) { - // Special permissions for the system default text classifier. - allowed = true; - } - if (!allowed && bp.isConfigurator() - && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_CONFIGURATOR, - UserHandle.USER_SYSTEM), pkg.getPackageName())) { - // Special permissions for the device configurator. - allowed = true; - } - if (!allowed && bp.isIncidentReportApprover() - && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER, - UserHandle.USER_SYSTEM), pkg.getPackageName())) { - // If this permission is to be granted to the incident report approver and - // this app is the incident report approver, then it gets the permission. - allowed = true; - } - if (!allowed && bp.isAppPredictor() - && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM), - pkg.getPackageName())) { - // Special permissions for the system app predictor. - allowed = true; - } - if (!allowed && bp.isCompanion() - && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_COMPANION, UserHandle.USER_SYSTEM), - pkg.getPackageName())) { - // Special permissions for the system companion device manager. - allowed = true; - } - if (!allowed && bp.isRetailDemo() - && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM), - pkg.getPackageName()) && isProfileOwner(pkg.getUid())) { - // Special permission granted only to the OEM specified retail demo app - allowed = true; - } - if (!allowed && bp.isRecents() - && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( - PackageManagerInternal.PACKAGE_RECENTS, UserHandle.USER_SYSTEM), - pkg.getPackageName())) { - // Special permission for the recents app. - allowed = true; - } - return allowed; - } - - @NonNull - private SigningDetails getSourcePackageSigningDetails( - @NonNull Permission bp) { - final PackageStateInternal ps = getSourcePackageSetting(bp); - if (ps == null) { - return SigningDetails.UNKNOWN; - } - return ps.getSigningDetails(); - } - - @Nullable - private PackageStateInternal getSourcePackageSetting(@NonNull Permission bp) { - final String sourcePackageName = bp.getPackageName(); - return mPackageManagerInt.getPackageStateInternal(sourcePackageName); - } - - private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) { - if (!pkg.isOem()) { - return false; - } - // all oem permissions must explicitly be granted or denied - final Boolean granted = - SystemConfig.getInstance().getOemPermissions(pkg.getPackageName()).get(permission); - if (granted == null) { - throw new IllegalStateException("OEM permission" + permission + " requested by package " - + pkg.getPackageName() + " must be explicitly declared granted or not"); - } - return Boolean.TRUE == granted; - } - - private static boolean isProfileOwner(int uid) { - DevicePolicyManagerInternal dpmInternal = - LocalServices.getService(DevicePolicyManagerInternal.class); - //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode. - if (dpmInternal != null) { - return dpmInternal.isActiveProfileOwner(uid) || dpmInternal.isActiveDeviceOwner(uid); - } - return false; - } - - private boolean isPermissionsReviewRequiredInternal(@NonNull String packageName, - @UserIdInt int userId) { - final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); - if (pkg == null) { - return false; - } - - // Permission review applies only to apps not supporting the new permission model. - if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) { - return false; - } - - // Legacy apps have the permission and get user consent on launch. - synchronized (mLock) { - final UidPermissionState uidState = getUidStateLocked(pkg, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " - + userId); - return false; - } - return uidState.isPermissionsReviewRequired(); - } - } - - private void grantRequestedRuntimePermissionsInternal(@NonNull AndroidPackage pkg, - @Nullable List permissions, int userId) { - final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED - | PackageManager.FLAG_PERMISSION_POLICY_FIXED; - - final int compatFlags = PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED - | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; - - final boolean supportsRuntimePermissions = pkg.getTargetSdkVersion() - >= Build.VERSION_CODES.M; - - final boolean instantApp = mPackageManagerInt.isInstantApp(pkg.getPackageName(), userId); - - final int myUid = Process.myUid(); - - for (String permission : pkg.getRequestedPermissions()) { - final boolean shouldGrantPermission; - synchronized (mLock) { - final Permission bp = mRegistry.getPermission(permission); - shouldGrantPermission = bp != null && (bp.isRuntime() || bp.isDevelopment()) - && (!instantApp || bp.isInstant()) - && (supportsRuntimePermissions || !bp.isRuntimeOnly()) - && (permissions == null || permissions.contains(permission)); - } - if (shouldGrantPermission) { - final int flags = getPermissionFlagsInternal(pkg.getPackageName(), permission, - myUid, userId); - if (supportsRuntimePermissions) { - // Installer cannot change immutable permissions. - if ((flags & immutableFlags) == 0) { - grantRuntimePermissionInternal(pkg.getPackageName(), permission, false, - myUid, userId, mDefaultPermissionCallback); - } - } else { - // In permission review mode we clear the review flag and the revoked compat - // flag when we are asked to install the app with all permissions granted. - if ((flags & compatFlags) != 0) { - updatePermissionFlagsInternal(pkg.getPackageName(), permission, compatFlags, - 0, myUid, userId, false, mDefaultPermissionCallback); - } - } - } - } - } - - private void setAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg, - @Nullable List permissions, @PermissionWhitelistFlags int allowlistFlags, - @UserIdInt int userId) { - ArraySet oldGrantedRestrictedPermissions = null; - boolean updatePermissions = false; - final int permissionCount = pkg.getRequestedPermissions().size(); - final int myUid = Process.myUid(); - - for (int j = 0; j < permissionCount; j++) { - final String permissionName = pkg.getRequestedPermissions().get(j); - - final boolean isGranted; - synchronized (mLock) { - final Permission bp = mRegistry.getPermission(permissionName); - if (bp == null || !bp.isHardOrSoftRestricted()) { - continue; - } - - final UidPermissionState uidState = getUidStateLocked(pkg, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() - + " and user " + userId); - continue; - } - isGranted = uidState.isPermissionGranted(permissionName); - } - - if (isGranted) { - if (oldGrantedRestrictedPermissions == null) { - oldGrantedRestrictedPermissions = new ArraySet<>(); - } - oldGrantedRestrictedPermissions.add(permissionName); - } - - final int oldFlags = getPermissionFlagsInternal(pkg.getPackageName(), permissionName, - myUid, userId); - - int newFlags = oldFlags; - int mask = 0; - int allowlistFlagsCopy = allowlistFlags; - while (allowlistFlagsCopy != 0) { - final int flag = 1 << Integer.numberOfTrailingZeros(allowlistFlagsCopy); - allowlistFlagsCopy &= ~flag; - switch (flag) { - case FLAG_PERMISSION_WHITELIST_SYSTEM: { - mask |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; - if (permissions != null && permissions.contains(permissionName)) { - newFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; - } else { - newFlags &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; - } - } - break; - case FLAG_PERMISSION_WHITELIST_UPGRADE: { - mask |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; - if (permissions != null && permissions.contains(permissionName)) { - newFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; - } else { - newFlags &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; - } - } - break; - case FLAG_PERMISSION_WHITELIST_INSTALLER: { - mask |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; - if (permissions != null && permissions.contains(permissionName)) { - newFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; - } else { - newFlags &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; - } - } - break; - } - } - - if (oldFlags == newFlags) { - continue; - } - - updatePermissions = true; - - final boolean wasAllowlisted = (oldFlags - & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0; - final boolean isAllowlisted = (newFlags - & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0; - - // If the permission is policy fixed as granted but it is no longer - // on any of the allowlists we need to clear the policy fixed flag - // as allowlisting trumps policy i.e. policy cannot grant a non - // grantable permission. - if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { - if (!isAllowlisted && isGranted) { - mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED; - newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED; - } - } - - // If we are allowlisting an app that does not support runtime permissions - // we need to make sure it goes through the permission review UI at launch. - if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M - && !wasAllowlisted && isAllowlisted) { - mask |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; - newFlags |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; - } - - updatePermissionFlagsInternal(pkg.getPackageName(), permissionName, mask, newFlags, - myUid, userId, false, null /*callback*/); - } - - if (updatePermissions) { - // Update permission of this app to take into account the new allowlist state. - restorePermissionState(pkg, false, pkg.getPackageName(), mDefaultPermissionCallback, - userId); - - // If this resulted in losing a permission we need to kill the app. - if (oldGrantedRestrictedPermissions == null) { - return; - } - - final int oldGrantedCount = oldGrantedRestrictedPermissions.size(); - for (int j = 0; j < oldGrantedCount; j++) { - final String permissionName = oldGrantedRestrictedPermissions.valueAt(j); - // Sometimes we create a new permission state instance during update. - final boolean isGranted; - synchronized (mLock) { - final UidPermissionState uidState = getUidStateLocked(pkg, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() - + " and user " + userId); - continue; - } - isGranted = uidState.isPermissionGranted(permissionName); - } - if (!isGranted) { - mDefaultPermissionCallback.onPermissionRevoked(pkg.getUid(), userId, null); - break; - } - } - } - } - - private void revokeSharedUserPermissionsForLeavingPackageInternal( - @Nullable AndroidPackage pkg, int appId, @NonNull List sharedUserPkgs, - @UserIdInt int userId) { - if (pkg == null) { - Slog.i(TAG, "Trying to update info for null package. Just ignoring"); - return; - } - - // No shared user packages - if (sharedUserPkgs.isEmpty()) { - return; - } - - PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage( - pkg.getPackageName()); - boolean isShadowingSystemPkg = disabledPs != null && disabledPs.getAppId() == pkg.getUid(); - - boolean shouldKillUid = false; - // Update permissions - for (String eachPerm : pkg.getRequestedPermissions()) { - // Check if another package in the shared user needs the permission. - boolean used = false; - for (AndroidPackage sharedUserpkg : sharedUserPkgs) { - if (sharedUserpkg != null - && !sharedUserpkg.getPackageName().equals(pkg.getPackageName()) - && sharedUserpkg.getRequestedPermissions().contains(eachPerm)) { - used = true; - break; - } - } - if (used) { - continue; - } - - // If the package is shadowing a disabled system package, - // do not drop permissions that the shadowed package requests. - if (isShadowingSystemPkg - && disabledPs.getPkg().getRequestedPermissions().contains(eachPerm)) { - continue; - } - - synchronized (mLock) { - UidPermissionState uidState = getUidStateLocked(appId, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() - + " and user " + userId); - continue; - } - - Permission bp = mRegistry.getPermission(eachPerm); - if (bp == null) { - continue; - } - - // TODO(zhanghai): Why are we only killing the UID when GIDs changed, instead of any - // permission change? - if (uidState.removePermissionState(bp.getName()) && bp.hasGids()) { - shouldKillUid = true; - } - } - } - - // If gids changed, kill all affected packages. - if (shouldKillUid) { - mHandler.post(() -> { - // This has to happen with no lock held. - killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED); - }); - } - } - - @GuardedBy("mLock") - private boolean revokeUnusedSharedUserPermissionsLocked( - List pkgList, UidPermissionState uidState) { - // Collect all used permissions in the UID - final ArraySet usedPermissions = new ArraySet<>(); - if (pkgList == null || pkgList.size() == 0) { - return false; - } - for (AndroidPackage pkg : pkgList) { - if (pkg.getRequestedPermissions().isEmpty()) { - continue; - } - final int requestedPermCount = pkg.getRequestedPermissions().size(); - for (int j = 0; j < requestedPermCount; j++) { - String permission = pkg.getRequestedPermissions().get(j); - Permission bp = mRegistry.getPermission(permission); - if (bp != null) { - usedPermissions.add(permission); - } - } - } - - boolean runtimePermissionChanged = false; - - // Prune permissions - final List permissionStates = uidState.getPermissionStates(); - final int permissionStatesSize = permissionStates.size(); - for (int i = permissionStatesSize - 1; i >= 0; i--) { - PermissionState permissionState = permissionStates.get(i); - if (!usedPermissions.contains(permissionState.getName())) { - Permission bp = mRegistry.getPermission(permissionState.getName()); - if (bp != null) { - if (uidState.removePermissionState(bp.getName()) && bp.isRuntime()) { - runtimePermissionChanged = true; - } - } - } - } - - return runtimePermissionChanged; - } - - /** - * Update permissions when a package changed. - * - *

    - *
  1. Reconsider the ownership of permission
  2. - *
  3. Update the state (grant, flags) of the permissions
  4. - *
- * - * @param packageName The package that is updated - * @param pkg The package that is updated, or {@code null} if package is deleted - */ - private void updatePermissions(@NonNull String packageName, @Nullable AndroidPackage pkg) { - // If the package is being deleted, update the permissions of all the apps - final int flags = - (pkg == null ? UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG - : UPDATE_PERMISSIONS_REPLACE_PKG); - updatePermissions( - packageName, pkg, getVolumeUuidForPackage(pkg), flags, mDefaultPermissionCallback); - } - - /** - * Update all permissions for all apps. - * - *

    - *
  1. Reconsider the ownership of permission
  2. - *
  3. Update the state (grant, flags) of the permissions
  4. - *
- * - * @param volumeUuid The volume UUID of the packages to be updated - * @param fingerprintChanged whether the current build fingerprint is different from what it was - * when this volume was last mounted - */ - private void updateAllPermissions(@NonNull String volumeUuid, boolean fingerprintChanged) { - PackageManager.corkPackageInfoCache(); // Prevent invalidation storm - try { - final int flags = UPDATE_PERMISSIONS_ALL | - (fingerprintChanged - ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL - : 0); - updatePermissions(null, null, volumeUuid, flags, mDefaultPermissionCallback); - } finally { - PackageManager.uncorkPackageInfoCache(); - } - } - - /** - * Update all packages on the volume, beside the changing package. If the changing - * package is set too, all packages are updated. - */ - private static final int UPDATE_PERMISSIONS_ALL = 1 << 0; - /** The changing package is replaced. Requires the changing package to be set */ - private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1 << 1; - /** - * Schedule all packages beside the changing package for replacement. Requires - * UPDATE_PERMISSIONS_ALL to be set - */ - private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1 << 2; - - @IntDef(flag = true, prefix = { "UPDATE_PERMISSIONS_" }, value = { - UPDATE_PERMISSIONS_ALL, UPDATE_PERMISSIONS_REPLACE_PKG, - UPDATE_PERMISSIONS_REPLACE_ALL }) - @Retention(RetentionPolicy.SOURCE) - private @interface UpdatePermissionFlags {} - - /** - * Update permissions when packages changed. - * - *

    - *
  1. Reconsider the ownership of permission
  2. - *
  3. Update the state (grant, flags) of the permissions
  4. - *
- * - *

Meaning of combination of package parameters: - * - * - * - * - *
changingPkgName != nullchangingPkgName == null
changingPkg != nullpackage is updatedinvalid
changingPkg == nullpackage is deletedall packages are - * updated
- * - * @param changingPkgName The package that is updated, or {@code null} if all packages should be - * updated - * @param changingPkg The package that is updated, or {@code null} if all packages should be - * updated or package is deleted - * @param replaceVolumeUuid The volume of the packages to be updated are on, {@code null} for - * all volumes - * @param flags Control permission for which apps should be updated - * @param callback Callback to call after permission changes - */ - private void updatePermissions(final @Nullable String changingPkgName, - final @Nullable AndroidPackage changingPkg, - final @Nullable String replaceVolumeUuid, - @UpdatePermissionFlags int flags, - final @Nullable PermissionCallback callback) { - // TODO: Most of the methods exposing BasePermission internals [source package name, - // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't - // have package settings, we should make note of it elsewhere [map between - // source package name and BasePermission] and cycle through that here. Then we - // define a single method on BasePermission that takes a PackageSetting, changing - // package name and a package. - // NOTE: With this approach, we also don't need to tree trees differently than - // normal permissions. Today, we need two separate loops because these BasePermission - // objects are stored separately. - // Make sure there are no dangling permission trees. - boolean permissionTreesSourcePackageChanged = updatePermissionTreeSourcePackage( - changingPkgName, changingPkg); - // Make sure all dynamic permissions have been assigned to a package, - // and make sure there are no dangling permissions. - boolean permissionSourcePackageChanged = updatePermissionSourcePackage(changingPkgName, - callback); - - if (permissionTreesSourcePackageChanged | permissionSourcePackageChanged) { - // Permission ownership has changed. This e.g. changes which packages can get signature - // permissions - Slog.i(TAG, "Permission ownership changed. Updating all permissions."); - flags |= UPDATE_PERMISSIONS_ALL; - } - - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "restorePermissionState"); - // Now update the permissions for all packages. - if ((flags & UPDATE_PERMISSIONS_ALL) != 0) { - final boolean replaceAll = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0); - mPackageManagerInt.forEachPackage((AndroidPackage pkg) -> { - if (pkg == changingPkg) { - return; - } - // Only replace for packages on requested volume - final String volumeUuid = getVolumeUuidForPackage(pkg); - final boolean replace = replaceAll && Objects.equals(replaceVolumeUuid, volumeUuid); - restorePermissionState(pkg, replace, changingPkgName, callback, - UserHandle.USER_ALL); - }); - } - - if (changingPkg != null) { - // Only replace for packages on requested volume - final String volumeUuid = getVolumeUuidForPackage(changingPkg); - final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0) - && Objects.equals(replaceVolumeUuid, volumeUuid); - restorePermissionState(changingPkg, replace, changingPkgName, callback, - UserHandle.USER_ALL); - } - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - - /** - * Update which app declares a permission. - * - * @param packageName The package that is updated, or {@code null} if all packages should be - * updated - * - * @return {@code true} if a permission source package might have changed - */ - private boolean updatePermissionSourcePackage(@Nullable String packageName, - final @Nullable PermissionCallback callback) { - // Always need update if packageName is null - if (packageName == null) { - return true; - } - - boolean changed = false; - Set needsUpdate = null; - synchronized (mLock) { - for (final Permission bp : mRegistry.getPermissions()) { - if (bp.isDynamic()) { - bp.updateDynamicPermission(mRegistry.getPermissionTrees()); - } - if (!packageName.equals(bp.getPackageName())) { - // Not checking sourcePackageSetting because it can be null when - // the permission source package is the target package and the target package is - // being uninstalled, - continue; - } - // The target package is the source of the current permission - // Set to changed for either install or uninstall - changed = true; - if (needsUpdate == null) { - needsUpdate = new ArraySet<>(); - } - needsUpdate.add(bp); - } - } - if (needsUpdate != null) { - final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); - for (final Permission bp : needsUpdate) { - // If the target package is being uninstalled, we need to revoke this permission - // From all other packages - if (pkg == null || !hasPermission(pkg, bp.getName())) { - if (!isPermissionDeclaredByDisabledSystemPkg(bp)) { - Slog.i(TAG, "Removing permission " + bp.getName() - + " that used to be declared by " + bp.getPackageName()); - if (bp.isRuntime()) { - final int[] userIds = mUserManagerInt.getUserIds(); - final int numUserIds = userIds.length; - for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) { - final int userId = userIds[userIdNum]; - mPackageManagerInt.forEachPackage((AndroidPackage p) -> - revokePermissionFromPackageForUser(p.getPackageName(), - bp.getName(), true, userId, callback)); - } - } else { - mPackageManagerInt.forEachPackage(p -> { - final int[] userIds = mUserManagerInt.getUserIds(); - synchronized (mLock) { - for (final int userId : userIds) { - final UidPermissionState uidState = getUidStateLocked(p, - userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for " - + p.getPackageName() + " and user " + userId); - continue; - } - uidState.removePermissionState(bp.getName()); - } - } - }); - } - } - synchronized (mLock) { - mRegistry.removePermission(bp.getName()); - } - continue; - } - final AndroidPackage sourcePkg = - mPackageManagerInt.getPackage(bp.getPackageName()); - final PackageStateInternal sourcePs = - mPackageManagerInt.getPackageStateInternal(bp.getPackageName()); - synchronized (mLock) { - if (sourcePkg != null && sourcePs != null) { - continue; - } - Slog.w(TAG, "Removing dangling permission: " + bp.getName() - + " from package " + bp.getPackageName()); - mRegistry.removePermission(bp.getName()); - } - } - } - return changed; - } - - private boolean isPermissionDeclaredByDisabledSystemPkg(@NonNull Permission permission) { - final PackageSetting disabledSourcePs = mPackageManagerInt.getDisabledSystemPackage( - permission.getPackageName()); - if (disabledSourcePs != null && disabledSourcePs.getPkg() != null) { - final String permissionName = permission.getName(); - final List sourcePerms = disabledSourcePs.getPkg().getPermissions(); - for (ParsedPermission sourcePerm : sourcePerms) { - if (TextUtils.equals(permissionName, sourcePerm.getName()) - && permission.getProtectionLevel() == sourcePerm.getProtectionLevel()) { - return true; - } - } - } - return false; - } - - /** - * Revoke a runtime permission from a package for a given user ID. - */ - private void revokePermissionFromPackageForUser(@NonNull String pName, - @NonNull String permissionName, boolean overridePolicy, int userId, - @Nullable PermissionCallback callback) { - final ApplicationInfo appInfo = - mPackageManagerInt.getApplicationInfo(pName, 0, - Process.SYSTEM_UID, UserHandle.USER_SYSTEM); - if (appInfo != null - && appInfo.targetSdkVersion < Build.VERSION_CODES.M) { - return; - } - - if (checkPermissionImpl(pName, permissionName, userId) - == PackageManager.PERMISSION_GRANTED) { - try { - revokeRuntimePermissionInternal( - pName, permissionName, - overridePolicy, - Process.SYSTEM_UID, - userId, - null, callback); - } catch (IllegalArgumentException e) { - Slog.e(TAG, - "Failed to revoke " - + permissionName - + " from " - + pName, - e); - } - } - } - /** - * Update which app owns a permission trees. - * - *

Possible parameter combinations - * - * - * - * - *
packageName != nullpackageName == null
pkg != nullpackage is updatedinvalid
pkg == nullpackage is deletedall packages are updated
- * - * @param packageName The package that is updated, or {@code null} if all packages should be - * updated - * @param pkg The package that is updated, or {@code null} if all packages should be updated or - * package is deleted - * - * @return {@code true} if a permission tree ownership might have changed - */ - private boolean updatePermissionTreeSourcePackage(@Nullable String packageName, - @Nullable AndroidPackage pkg) { - // Always need update if packageName is null - if (packageName == null) { - return true; - } - boolean changed = false; - - Set needsUpdate = null; - synchronized (mLock) { - final Iterator it = mRegistry.getPermissionTrees().iterator(); - while (it.hasNext()) { - final Permission bp = it.next(); - if (!packageName.equals(bp.getPackageName())) { - // Not checking sourcePackageSetting because it can be null when - // the permission source package is the target package and the target package is - // being uninstalled, - continue; - } - // The target package is the source of the current permission tree - // Set to changed for either install or uninstall - changed = true; - if (pkg == null || !hasPermission(pkg, bp.getName())) { - Slog.i(TAG, "Removing permission tree " + bp.getName() - + " that used to be declared by " + bp.getPackageName()); - it.remove(); - } - if (needsUpdate == null) { - needsUpdate = new ArraySet<>(); - } - needsUpdate.add(bp); - } - } - if (needsUpdate != null) { - for (final Permission bp : needsUpdate) { - final AndroidPackage sourcePkg = - mPackageManagerInt.getPackage(bp.getPackageName()); - final PackageStateInternal sourcePs = - mPackageManagerInt.getPackageStateInternal(bp.getPackageName()); - synchronized (mLock) { - if (sourcePkg != null && sourcePs != null) { - continue; - } - Slog.w(TAG, "Removing dangling permission tree: " + bp.getName() - + " from package " + bp.getPackageName()); - mRegistry.removePermission(bp.getName()); - } - } - } - return changed; - } - - private void enforceGrantRevokeRuntimePermissionPermissions(String message) { - if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS) - != PackageManager.PERMISSION_GRANTED - && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException(message + " requires " - + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or " - + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); - } - } - - private void enforceGrantRevokeGetRuntimePermissionPermissions(@NonNull String message) { - if (mContext.checkCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) - != PackageManager.PERMISSION_GRANTED - && mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS) - != PackageManager.PERMISSION_GRANTED - && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException(message + " requires " - + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or " - + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS + " or " - + Manifest.permission.GET_RUNTIME_PERMISSIONS); - } - } - - /** - * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS - * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userId} is not for the caller. - * - * @param checkShell whether to prevent shell from access if there's a debugging restriction - * @param message the message to log on security exception - */ - private void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, - boolean requireFullPermission, boolean checkShell, @Nullable String message) { - if (userId < 0) { - throw new IllegalArgumentException("Invalid userId " + userId); - } - if (checkShell) { - enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); - } - final int callingUserId = UserHandle.getUserId(callingUid); - if (checkCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission)) { - return; - } - String errorMessage = buildInvalidCrossUserPermissionMessage( - callingUid, userId, message, requireFullPermission); - Slog.w(TAG, errorMessage); - throw new SecurityException(errorMessage); - } - - /** - * Enforces that if the caller is shell, it does not have the provided user restriction. - */ - private void enforceShellRestriction(@NonNull String restriction, int callingUid, - @UserIdInt int userId) { - if (callingUid == Process.SHELL_UID) { - if (userId >= 0 && mUserManagerInt.hasUserRestriction(restriction, userId)) { - throw new SecurityException("Shell does not have permission to access user " - + userId); - } else if (userId < 0) { - Slog.e(LOG_TAG, "Unable to check shell permission for user " - + userId + "\n\t" + Debug.getCallers(3)); - } - } - } - - private boolean checkCrossUserPermission(int callingUid, @UserIdInt int callingUserId, - @UserIdInt int userId, boolean requireFullPermission) { - if (userId == callingUserId) { - return true; - } - if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) { - return true; - } - if (requireFullPermission) { - return checkCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - } - return checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - || checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS); - } - - private boolean checkCallingOrSelfPermission(String permission) { - return mContext.checkCallingOrSelfPermission(permission) - == PackageManager.PERMISSION_GRANTED; - } - - @NonNull - private static String buildInvalidCrossUserPermissionMessage(int callingUid, - @UserIdInt int userId, @Nullable String message, boolean requireFullPermission) { - StringBuilder builder = new StringBuilder(); - if (message != null) { - builder.append(message); - builder.append(": "); - } - builder.append("UID "); - builder.append(callingUid); - builder.append(" requires "); - builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - if (!requireFullPermission) { - builder.append(" or "); - builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS); - } - builder.append(" to access user "); - builder.append(userId); - builder.append("."); - return builder.toString(); - } - - @GuardedBy("mLock") - private int calculateCurrentPermissionFootprintLocked(@NonNull Permission permissionTree) { - int size = 0; - for (final Permission permission : mRegistry.getPermissions()) { - size += permissionTree.calculateFootprint(permission); - } - return size; - } - - @GuardedBy("mLock") - private void enforcePermissionCapLocked(PermissionInfo info, Permission tree) { - // We calculate the max size of permissions defined by this uid and throw - // if that plus the size of 'info' would exceed our stated maximum. - if (tree.getUid() != Process.SYSTEM_UID) { - final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree); - if (curTreeSize + info.calculateFootprint() > MAX_PERMISSION_TREE_FOOTPRINT) { - throw new SecurityException("Permission tree size cap exceeded"); - } - } - } - - private void systemReady() { - // Now that we've scanned all packages, and granted any default - // permissions, ensure permissions are updated. Beware of dragons if you - // try optimizing this. - updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false); - - final PermissionPolicyInternal permissionPolicyInternal = LocalServices.getService( - PermissionPolicyInternal.class); - permissionPolicyInternal.setOnInitializedCallback(userId -> - // The SDK updated case is already handled when we run during the ctor. - updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false) - ); - - mSystemReady = true; - - synchronized (mLock) { - if (mPrivappPermissionsViolations != null) { - throw new IllegalStateException("Signature|privileged permissions not in " - + "privapp-permissions allowlist: " + mPrivappPermissionsViolations); - } - } - - mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class); - mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class); - } - - private static String getVolumeUuidForPackage(AndroidPackage pkg) { - if (pkg == null) { - return StorageManager.UUID_PRIVATE_INTERNAL; - } - if (pkg.isExternalStorage()) { - if (TextUtils.isEmpty(pkg.getVolumeUuid())) { - return StorageManager.UUID_PRIMARY_PHYSICAL; - } else { - return pkg.getVolumeUuid(); - } - } else { - return StorageManager.UUID_PRIVATE_INTERNAL; - } - } - - private static boolean hasPermission(AndroidPackage pkg, String permName) { - if (pkg.getPermissions().isEmpty()) { - return false; - } - - for (int i = pkg.getPermissions().size() - 1; i >= 0; i--) { - if (pkg.getPermissions().get(i).getName().equals(permName)) { - return true; - } - } - return false; - } - - /** - * Log that a permission request was granted/revoked. - * - * @param action the action performed - * @param name name of the permission - * @param packageName package permission is for - */ - private void logPermission(int action, @NonNull String name, @NonNull String packageName) { - final LogMaker log = new LogMaker(action); - log.setPackageName(packageName); - log.addTaggedData(MetricsEvent.FIELD_PERMISSION, name); - - mMetricsLogger.write(log); - } - - @GuardedBy("mLock") - @Nullable - private UidPermissionState getUidStateLocked(@NonNull PackageStateInternal ps, - @UserIdInt int userId) { - return getUidStateLocked(ps.getAppId(), userId); - } - - @GuardedBy("mLock") - @Nullable - private UidPermissionState getUidStateLocked(@NonNull AndroidPackage pkg, - @UserIdInt int userId) { - return getUidStateLocked(pkg.getUid(), userId); - } - - @GuardedBy("mLock") - @Nullable - private UidPermissionState getUidStateLocked(@AppIdInt int appId, @UserIdInt int userId) { - final UserPermissionState userState = mState.getUserState(userId); - if (userState == null) { + @Override + public ParceledListSlice queryPermissionsByGroup(String groupName, int flags) { + List permissionInfo = + mPermissionManagerServiceImpl.queryPermissionsByGroup(groupName, flags); + if (permissionInfo == null) { return null; } - return userState.getUidState(appId); + + return new ParceledListSlice<>(permissionInfo); } - private void removeUidStateAndResetPackageInstallPermissionsFixed(@AppIdInt int appId, - @NonNull String packageName, @UserIdInt int userId) { - synchronized (mLock) { - final UserPermissionState userState = mState.getUserState(userId); - if (userState == null) { - return; - } - userState.removeUidState(appId); - userState.setInstallPermissionsFixed(packageName, false); - } + @Override + public boolean addPermission(PermissionInfo permissionInfo, boolean async) { + return mPermissionManagerServiceImpl.addPermission(permissionInfo, async); } - private void readLegacyPermissionState() { - final int[] userIds = getAllUserIds(); - mPackageManagerInt.forEachPackageSetting(ps -> { - final int appId = ps.getAppId(); - final LegacyPermissionState legacyState = ps.getLegacyPermissionState(); - - synchronized (mLock) { - for (final int userId : userIds) { - final UserPermissionState userState = mState.getOrCreateUserState(userId); - - userState.setInstallPermissionsFixed(ps.getPackageName(), - ps.isInstallPermissionsFixed()); - final UidPermissionState uidState = userState.getOrCreateUidState(appId); - uidState.reset(); - uidState.setMissing(legacyState.isMissing(userId)); - readLegacyPermissionStatesLocked(uidState, - legacyState.getPermissionStates(userId)); - } - } - }); + @Override + public void removePermission(String permissionName) { + mPermissionManagerServiceImpl.removePermission(permissionName); } - @GuardedBy("mLock") - private void readLegacyPermissionStatesLocked(@NonNull UidPermissionState uidState, - @NonNull Collection permissionStates) { - for (final LegacyPermissionState.PermissionState permissionState : permissionStates) { - final String permissionName = permissionState.getName(); - final Permission permission = mRegistry.getPermission(permissionName); - if (permission == null) { - Slog.w(TAG, "Unknown permission: " + permissionName); - continue; - } - uidState.putPermissionState(permission, permissionState.isGranted(), - permissionState.getFlags()); - } + @Override + public int getPermissionFlags(String packageName, String permissionName, int userId) { + return mPermissionManagerServiceImpl + .getPermissionFlags(packageName, permissionName, userId); } - private void writeLegacyPermissionState() { - final int[] userIds; - synchronized (mLock) { - userIds = mState.getUserIds(); - } - mPackageManagerInt.forEachPackageSetting(ps -> { - ps.setInstallPermissionsFixed(false); - final LegacyPermissionState legacyState = ps.getLegacyPermissionState(); - legacyState.reset(); - final int appId = ps.getAppId(); - - synchronized (mLock) { - for (final int userId : userIds) { - final UserPermissionState userState = mState.getUserState(userId); - if (userState == null) { - Slog.e(TAG, "Missing user state for " + userId); - continue; - } - - if (userState.areInstallPermissionsFixed(ps.getPackageName())) { - ps.setInstallPermissionsFixed(true); - } - - final UidPermissionState uidState = userState.getUidState(appId); - if (uidState == null) { - Slog.e(TAG, "Missing permission state for " + ps.getPackageName() - + " and user " + userId); - continue; - } - - legacyState.setMissing(uidState.isMissing(), userId); - final List permissionStates = uidState.getPermissionStates(); - final int permissionStatesSize = permissionStates.size(); - for (int i = 0; i < permissionStatesSize; i++) { - final PermissionState permissionState = permissionStates.get(i); - - final LegacyPermissionState.PermissionState legacyPermissionState = - new LegacyPermissionState.PermissionState(permissionState.getName(), - permissionState.getPermission().isRuntime(), - permissionState.isGranted(), permissionState.getFlags()); - legacyState.putPermissionState(legacyPermissionState, userId); - } - } - } - }); + @Override + public void updatePermissionFlags(String packageName, String permissionName, int flagMask, + int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { + mPermissionManagerServiceImpl.updatePermissionFlags(packageName, permissionName, flagMask, + flagValues, checkAdjustPolicyFlagPermission, userId); } - private void readLegacyPermissions(@NonNull LegacyPermissionSettings legacyPermissionSettings) { - for (int readPermissionOrPermissionTree = 0; readPermissionOrPermissionTree < 2; - readPermissionOrPermissionTree++) { - final List legacyPermissions = readPermissionOrPermissionTree == 0 - ? legacyPermissionSettings.getPermissions() - : legacyPermissionSettings.getPermissionTrees(); - synchronized (mLock) { - final int legacyPermissionsSize = legacyPermissions.size(); - for (int i = 0; i < legacyPermissionsSize; i++) { - final LegacyPermission legacyPermission = legacyPermissions.get(i); - final Permission permission = new Permission( - legacyPermission.getPermissionInfo(), legacyPermission.getType()); - if (readPermissionOrPermissionTree == 0) { - // Config permissions are currently read in PermissionManagerService - // constructor. The old behavior was to add other attributes to the config - // permission in LegacyPermission.read(), so equivalently we can add the - // GIDs to the new permissions here, since config permissions created in - // PermissionManagerService constructor get only their names and GIDs there. - final Permission configPermission = mRegistry.getPermission( - permission.getName()); - if (configPermission != null - && configPermission.getType() == Permission.TYPE_CONFIG) { - permission.setGids(configPermission.getRawGids(), - configPermission.areGidsPerUser()); - } - mRegistry.addPermission(permission); - } else { - mRegistry.addPermissionTree(permission); - } - } - } - } + @Override + public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) { + mPermissionManagerServiceImpl.updatePermissionFlagsForAllApps(flagMask, flagValues, userId); } - private void writeLegacyPermissions( - @NonNull LegacyPermissionSettings legacyPermissionSettings) { - for (int writePermissionOrPermissionTree = 0; writePermissionOrPermissionTree < 2; - writePermissionOrPermissionTree++) { - final List legacyPermissions = new ArrayList<>(); - synchronized (mLock) { - final Collection permissions = writePermissionOrPermissionTree == 0 - ? mRegistry.getPermissions() : mRegistry.getPermissionTrees(); - for (final Permission permission : permissions) { - // We don't need to provide UID and GIDs, which are only retrieved when dumping. - final LegacyPermission legacyPermission = new LegacyPermission( - permission.getPermissionInfo(), permission.getType(), 0, - EmptyArray.INT); - legacyPermissions.add(legacyPermission); - } - } - if (writePermissionOrPermissionTree == 0) { - legacyPermissionSettings.replacePermissions(legacyPermissions); - } else { - legacyPermissionSettings.replacePermissionTrees(legacyPermissions); - } - } + @Override + public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) { + mPermissionManagerServiceImpl.addOnPermissionsChangeListener(listener); } - private void onPackageAddedInternal(@NonNull AndroidPackage pkg, boolean isInstantApp, - @Nullable AndroidPackage oldPkg) { - if (!pkg.getAdoptPermissions().isEmpty()) { - // This package wants to adopt ownership of permissions from - // another package. - for (int i = pkg.getAdoptPermissions().size() - 1; i >= 0; i--) { - final String origName = pkg.getAdoptPermissions().get(i); - if (canAdoptPermissionsInternal(origName, pkg)) { - Slog.i(TAG, "Adopting permissions from " + origName + " to " - + pkg.getPackageName()); - synchronized (mLock) { - mRegistry.transferPermissions(origName, pkg.getPackageName()); - } - } - } - } - - // Don't allow ephemeral applications to define new permissions groups. - if (isInstantApp) { - Slog.w(TAG, "Permission groups from package " + pkg.getPackageName() - + " ignored: instant apps cannot define new permission groups."); - } else { - addAllPermissionGroupsInternal(pkg); - } - - // If a permission has had its defining app changed, or it has had its protection - // upgraded, we need to revoke apps that hold it - final List permissionsWithChangedDefinition; - // Don't allow ephemeral applications to define new permissions. - if (isInstantApp) { - permissionsWithChangedDefinition = null; - Slog.w(TAG, "Permissions from package " + pkg.getPackageName() - + " ignored: instant apps cannot define new permissions."); - } else { - permissionsWithChangedDefinition = addAllPermissionsInternal(pkg); - } - - boolean hasOldPkg = oldPkg != null; - boolean hasPermissionDefinitionChanges = - !CollectionUtils.isEmpty(permissionsWithChangedDefinition); - if (hasOldPkg || hasPermissionDefinitionChanges) { - // We need to call revokeRuntimePermissionsIfGroupChanged async as permission - // revoke callbacks from this method might need to kill apps which need the - // mPackages lock on a different thread. This would dead lock. - AsyncTask.execute(() -> { - if (hasOldPkg) { - revokeRuntimePermissionsIfGroupChangedInternal(pkg, oldPkg); - revokeStoragePermissionsIfScopeExpandedInternal(pkg, oldPkg); - } - if (hasPermissionDefinitionChanges) { - revokeRuntimePermissionsIfPermissionDefinitionChangedInternal( - permissionsWithChangedDefinition); - } - }); - } + @Override + public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) { + mPermissionManagerServiceImpl.removeOnPermissionsChangeListener(listener); } - private boolean canAdoptPermissionsInternal(@NonNull String oldPackageName, - @NonNull AndroidPackage newPkg) { - final PackageStateInternal oldPs = - mPackageManagerInt.getPackageStateInternal(oldPackageName); - if (oldPs == null) { - return false; - } - if (!oldPs.isSystem()) { - Slog.w(TAG, "Unable to update from " + oldPs.getPackageName() - + " to " + newPkg.getPackageName() - + ": old package not in system partition"); - return false; - } - if (mPackageManagerInt.getPackage(oldPs.getPackageName()) != null) { - Slog.w(TAG, "Unable to update from " + oldPs.getPackageName() - + " to " + newPkg.getPackageName() - + ": old package still exists"); - return false; - } - return true; + @Override + public List getAllowlistedRestrictedPermissions(String packageName, + int flags, int userId) { + return mPermissionManagerServiceImpl.getAllowlistedRestrictedPermissions(packageName, + flags, userId); } - 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()); - 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); - if (userState == null) { - continue; - } - final UidPermissionState prevUidState = userState.getUidState(previousAppId); - if (prevUidState == null) { - continue; - } - - // Insert new uid state by cloning the original one - userState.createUidStateWithExisting(ps.getAppId(), prevUidState); - - // Remove original app ID from original shared user group - // Should match the implementation of onPackageUninstalledInternal(...) - if (origSharedUserPackages.isEmpty()) { - removeUidStateAndResetPackageInstallPermissionsFixed( - previousAppId, pkg.getPackageName(), userId); - } else { - revokeSharedUserPermissionsForLeavingPackageInternal(pkg, previousAppId, - origSharedUserPackages, userId); - } - } - } - } - updatePermissions(pkg.getPackageName(), pkg); - for (final int userId : userIds) { - addAllowlistedRestrictedPermissionsInternal(pkg, - params.getAllowlistedRestrictedPermissions(), - FLAG_PERMISSION_WHITELIST_INSTALLER, userId); - final int autoRevokePermissionsMode = params.getAutoRevokePermissionsMode(); - if (autoRevokePermissionsMode == AppOpsManager.MODE_ALLOWED - || autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED) { - setAutoRevokeExemptedInternal(pkg, - autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED, userId); - } - grantRequestedRuntimePermissionsInternal(pkg, params.getGrantedPermissions(), userId); - } + @Override + public boolean addAllowlistedRestrictedPermission(String packageName, String permissionName, + int flags, int userId) { + return mPermissionManagerServiceImpl.addAllowlistedRestrictedPermission(packageName, + permissionName, flags, userId); } - private void addAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg, - @NonNull List allowlistedRestrictedPermissions, - @PermissionWhitelistFlags int flags, @UserIdInt int userId) { - List permissions = getAllowlistedRestrictedPermissionsInternal(pkg, flags, userId); - if (permissions != null) { - ArraySet permissionSet = new ArraySet<>(permissions); - permissionSet.addAll(allowlistedRestrictedPermissions); - permissions = new ArrayList<>(permissionSet); - } else { - permissions = allowlistedRestrictedPermissions; - } - setAllowlistedRestrictedPermissionsInternal(pkg, permissions, flags, userId); + @Override + public boolean removeAllowlistedRestrictedPermission(String packageName, String permissionName, + int flags, int userId) { + return mPermissionManagerServiceImpl.removeAllowlistedRestrictedPermission(packageName, + permissionName, flags, userId); } - private void onPackageRemovedInternal(@NonNull AndroidPackage pkg) { - removeAllPermissionsInternal(pkg); + @Override + public void grantRuntimePermission(String packageName, String permissionName, int userId) { + mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, userId); } - private void onPackageUninstalledInternal(@NonNull String packageName, int appId, - @Nullable AndroidPackage pkg, @NonNull List sharedUserPkgs, - @UserIdInt int[] userIds) { - // TODO: Handle the case when a system app upgrade is uninstalled and need to rejoin - // a shared UID permission state. - - // TODO: Move these checks to check PackageState to be more reliable. - // System packages should always have an available APK. - if (pkg != null && pkg.isSystem() - // We may be fully removing invalid system packages during boot, and in that case we - // do want to remove their permission state. So make sure that the package is only - // being marked as uninstalled instead of fully removed. - && mPackageManagerInt.getPackage(packageName) != null) { - // If we are only marking a system package as uninstalled, we need to keep its - // pregranted permission state so that it still works once it gets reinstalled, thus - // only reset the user modifications to its permission state. - for (final int userId : userIds) { - resetRuntimePermissionsInternal(pkg, userId); - } - return; - } - updatePermissions(packageName, null); - for (final int userId : userIds) { - if (sharedUserPkgs.isEmpty()) { - removeUidStateAndResetPackageInstallPermissionsFixed(appId, packageName, userId); - } else { - // Remove permissions associated with package. Since runtime - // permissions are per user we have to kill the removed package - // or packages running under the shared user of the removed - // package if revoking the permissions requested only by the removed - // package is successful and this causes a change in gids. - revokeSharedUserPermissionsForLeavingPackageInternal(pkg, appId, - sharedUserPkgs, userId); - } - } + @Override + public void revokeRuntimePermission(String packageName, String permissionName, int userId, + String reason) { + mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName, userId, + reason); } - @NonNull - private List getLegacyPermissions() { - synchronized (mLock) { - final List legacyPermissions = new ArrayList<>(); - for (final Permission permission : mRegistry.getPermissions()) { - final LegacyPermission legacyPermission = new LegacyPermission( - permission.getPermissionInfo(), permission.getType(), permission.getUid(), - permission.getRawGids()); - legacyPermissions.add(legacyPermission); - } - return legacyPermissions; - } + @Override + public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName, + int userId) { + return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName, + permissionName, userId); } - @NonNull - private Map> getAllAppOpPermissionPackages() { - synchronized (mLock) { - final ArrayMap> appOpPermissionPackages = - mRegistry.getAllAppOpPermissionPackages(); - final Map> deepClone = new ArrayMap<>(); - final int appOpPermissionPackagesSize = appOpPermissionPackages.size(); - for (int i = 0; i < appOpPermissionPackagesSize; i++) { - final String appOpPermission = appOpPermissionPackages.keyAt(i); - final ArraySet packageNames = appOpPermissionPackages.valueAt(i); - deepClone.put(appOpPermission, new ArraySet<>(packageNames)); - } - return deepClone; - } + @Override + public boolean isPermissionRevokedByPolicy(String packageName, String permissionName, + int userId) { + return mPermissionManagerServiceImpl + .isPermissionRevokedByPolicy(packageName, permissionName, userId); } - @NonNull - private LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) { - final LegacyPermissionState legacyState = new LegacyPermissionState(); - synchronized (mLock) { - final int[] userIds = mState.getUserIds(); - for (final int userId : userIds) { - final UidPermissionState uidState = getUidStateLocked(appId, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " - + userId); - continue; - } - - final List permissionStates = uidState.getPermissionStates(); - final int permissionStatesSize = permissionStates.size(); - for (int i = 0; i < permissionStatesSize; i++) { - final PermissionState permissionState = permissionStates.get(i); - - final LegacyPermissionState.PermissionState legacyPermissionState = - new LegacyPermissionState.PermissionState(permissionState.getName(), - permissionState.getPermission().isRuntime(), - permissionState.isGranted(), permissionState.getFlags()); - legacyState.putPermissionState(legacyPermissionState, userId); - } - } - } - return legacyState; + @Override + public List getSplitPermissions() { + return mPermissionManagerServiceImpl.getSplitPermissions(); } - @NonNull - private int[] getGidsForUid(int uid) { - final int appId = UserHandle.getAppId(uid); - final int userId = UserHandle.getUserId(uid); - synchronized (mLock) { - final UidPermissionState uidState = getUidStateLocked(appId, userId); - if (uidState == null) { - Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " - + userId); - return EMPTY_INT_ARRAY; - } - return uidState.computeGids(mGlobalGids, userId); - } - } + /* End of delegate methods to PermissionManagerServiceInterface */ private class PermissionManagerServiceInternalImpl implements PermissionManagerServiceInternal { @Override @@ -5135,140 +582,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { return PermissionManagerService.this.checkUidPermission(uid, permissionName); } - @Override - public void onSystemReady() { - PermissionManagerService.this.systemReady(); - } - - @Override - public boolean isPermissionsReviewRequired(@NonNull String packageName, - @UserIdInt int userId) { - Objects.requireNonNull(packageName, "packageName"); - // TODO(b/173235285): Some caller may pass USER_ALL as userId. - //Preconditions.checkArgumentNonnegative(userId, "userId"); - return isPermissionsReviewRequiredInternal(packageName, userId); - } - - @Override - public void readLegacyPermissionStateTEMP() { - PermissionManagerService.this.readLegacyPermissionState(); - } - @Override - public void writeLegacyPermissionStateTEMP() { - PermissionManagerService.this.writeLegacyPermissionState(); - } - @Override - public void onUserRemoved(@UserIdInt int userId) { - Preconditions.checkArgumentNonNegative(userId, "userId"); - PermissionManagerService.this.onUserRemoved(userId); - } - @NonNull - @Override - public Set getGrantedPermissions(@NonNull String packageName, - @UserIdInt int userId) { - Objects.requireNonNull(packageName, "packageName"); - Preconditions.checkArgumentNonNegative(userId, "userId"); - return getGrantedPermissionsInternal(packageName, userId); - } - @NonNull - @Override - public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) { - Objects.requireNonNull(permissionName, "permissionName"); - Preconditions.checkArgumentNonNegative(userId, "userId"); - return getPermissionGidsInternal(permissionName, userId); - } - @NonNull - @Override - public String[] getAppOpPermissionPackages(@NonNull String permissionName) { - Objects.requireNonNull(permissionName, "permissionName"); - return PermissionManagerService.this.getAppOpPermissionPackagesInternal(permissionName); - } - @Override - public void onStorageVolumeMounted(@Nullable String volumeUuid, boolean fingerprintChanged) { - updateAllPermissions(volumeUuid, fingerprintChanged); - } - @Override - public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) { - Objects.requireNonNull(pkg, "pkg"); - Preconditions.checkArgumentNonNegative(userId, "userId"); - resetRuntimePermissionsInternal(pkg, userId); - } - - @Override - public Permission getPermissionTEMP(String permName) { - synchronized (mLock) { - return mRegistry.getPermission(permName); - } - } - - @Override - public @NonNull ArrayList getAllPermissionsWithProtection( - @PermissionInfo.Protection int protection) { - ArrayList matchingPermissions = new ArrayList<>(); - - synchronized (mLock) { - for (final Permission permission : mRegistry.getPermissions()) { - if (permission.getProtection() == protection) { - matchingPermissions.add(permission.generatePermissionInfo(0)); - } - } - } - - return matchingPermissions; - } - - @Override - public @NonNull ArrayList getAllPermissionsWithProtectionFlags( - @PermissionInfo.ProtectionFlags int protectionFlags) { - ArrayList matchingPermissions = new ArrayList<>(); - - synchronized (mLock) { - for (final Permission permission : mRegistry.getPermissions()) { - if ((permission.getProtectionFlags() & protectionFlags) == protectionFlags) { - matchingPermissions.add(permission.generatePermissionInfo(0)); - } - } - } - - return matchingPermissions; - } - - @Nullable - @Override - public byte[] backupRuntimePermissions(@UserIdInt int userId) { - Preconditions.checkArgumentNonNegative(userId, "userId"); - return PermissionManagerService.this.backupRuntimePermissions(userId); - } - - @Override - public void restoreRuntimePermissions(@NonNull byte[] backup, @UserIdInt int userId) { - Objects.requireNonNull(backup, "backup"); - Preconditions.checkArgumentNonNegative(userId, "userId"); - PermissionManagerService.this.restoreRuntimePermissions(backup, userId); - } - - @Override - public void restoreDelayedRuntimePermissions(@NonNull String packageName, - @UserIdInt int userId) { - Objects.requireNonNull(packageName, "packageName"); - Preconditions.checkArgumentNonNegative(userId, "userId"); - PermissionManagerService.this.restoreDelayedRuntimePermissions(packageName, userId); - } - - @Override - public void addOnRuntimePermissionStateChangedListener( - OnRuntimePermissionStateChangedListener listener) { - PermissionManagerService.this.addOnRuntimePermissionStateChangedListener( - listener); - } - - @Override - public void removeOnRuntimePermissionStateChangedListener( - OnRuntimePermissionStateChangedListener listener) { - PermissionManagerService.this.removeOnRuntimePermissionStateChangedListener( - listener); - } - @Override public void startShellPermissionIdentityDelegation(int uid, @NonNull String packageName, @Nullable List permissionNames) { @@ -5287,87 +600,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { return getDelegatedShellPermissionsInternal(); } - @Override - public void onUserCreated(@UserIdInt int userId) { - Preconditions.checkArgumentNonNegative(userId, "userId"); - // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG - updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, true); - } - - @Override - public void readLegacyPermissionsTEMP( - @NonNull LegacyPermissionSettings legacyPermissionSettings) { - PermissionManagerService.this.readLegacyPermissions(legacyPermissionSettings); - } - - @Override - public void writeLegacyPermissionsTEMP( - @NonNull LegacyPermissionSettings legacyPermissionSettings) { - PermissionManagerService.this.writeLegacyPermissions(legacyPermissionSettings); - } - - @Override - public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp, - @Nullable AndroidPackage oldPkg) { - Objects.requireNonNull(pkg); - onPackageAddedInternal(pkg, isInstantApp, oldPkg); - } - - @Override - public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId, - @NonNull PackageInstalledParams params, @UserIdInt int userId) { - Objects.requireNonNull(pkg, "pkg"); - Objects.requireNonNull(params, "params"); - Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM - || userId == UserHandle.USER_ALL, "userId"); - final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds() - : new int[] { userId }; - onPackageInstalledInternal(pkg, previousAppId, params, userIds); - } - - @Override - public void onPackageRemoved(@NonNull AndroidPackage pkg) { - Objects.requireNonNull(pkg); - onPackageRemovedInternal(pkg); - } - - @Override - public void onPackageUninstalled(@NonNull String packageName, int appId, - @Nullable AndroidPackage pkg, @NonNull List sharedUserPkgs, - @UserIdInt int userId) { - Objects.requireNonNull(packageName, "packageName"); - Objects.requireNonNull(sharedUserPkgs, "sharedUserPkgs"); - Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM - || userId == UserHandle.USER_ALL, "userId"); - final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds() - : new int[] { userId }; - onPackageUninstalledInternal(packageName, appId, pkg, sharedUserPkgs, userIds); - } - - @NonNull - @Override - public List getLegacyPermissions() { - return PermissionManagerService.this.getLegacyPermissions(); - } - - @NonNull - @Override - public Map> getAllAppOpPermissionPackages() { - return PermissionManagerService.this.getAllAppOpPermissionPackages(); - } - - @NonNull - @Override - public LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) { - return PermissionManagerService.this.getLegacyPermissionState(appId); - } - - @NonNull - @Override - public int[] getGidsForUid(int uid) { - return PermissionManagerService.this.getGidsForUid(uid); - } - @Override public void setHotwordDetectionServiceProvider(HotwordDetectionServiceProvider provider) { mHotwordDetectionServiceProvider = provider; @@ -5377,84 +609,174 @@ public class PermissionManagerService extends IPermissionManager.Stub { public HotwordDetectionServiceProvider getHotwordDetectionServiceProvider() { return mHotwordDetectionServiceProvider; } - } - /** - * Callbacks invoked when interesting actions have been taken on a permission. - *

- * NOTE: The current arguments are merely to support the existing use cases. This - * needs to be properly thought out with appropriate arguments for each of the - * callback methods. - */ - private static class PermissionCallback { - public void onGidsChanged(@AppIdInt int appId, @UserIdInt int userId) {} - public void onPermissionChanged() {} - public void onPermissionGranted(int uid, @UserIdInt int userId) {} - public void onInstallPermissionGranted() {} - public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason) {} - public void onInstallPermissionRevoked() {} - public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {} - public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync, - int uid) { - onPermissionUpdated(updatedUserIds, sync); + /* Start of delegate methods to PermissionManagerServiceInterface */ + + @NonNull + @Override + public int[] getGidsForUid(int uid) { + return mPermissionManagerServiceImpl.getGidsForUid(uid); } - public void onPermissionRemoved() {} - public void onInstallPermissionUpdated() {} - public void onInstallPermissionUpdatedNotifyListener(int uid) { - onInstallPermissionUpdated(); - } - } - private static final class OnPermissionChangeListeners extends Handler { - private static final int MSG_ON_PERMISSIONS_CHANGED = 1; - - private final RemoteCallbackList mPermissionListeners = - new RemoteCallbackList<>(); - - OnPermissionChangeListeners(Looper looper) { - super(looper); + @NonNull + @Override + public Map> getAllAppOpPermissionPackages() { + return mPermissionManagerServiceImpl.getAllAppOpPermissionPackages(); } @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ON_PERMISSIONS_CHANGED: { - final int uid = msg.arg1; - handleOnPermissionsChanged(uid); - } break; - } + public void onUserCreated(@UserIdInt int userId) { + mPermissionManagerServiceImpl.onUserCreated(userId); } - public void addListener(IOnPermissionsChangeListener listener) { - mPermissionListeners.register(listener); + @NonNull + @Override + public List getLegacyPermissions() { + return mPermissionManagerServiceImpl.getLegacyPermissions(); } - public void removeListener(IOnPermissionsChangeListener listener) { - mPermissionListeners.unregister(listener); + @NonNull + @Override + public LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) { + return mPermissionManagerServiceImpl.getLegacyPermissionState(appId); } - public void onPermissionsChanged(int uid) { - if (mPermissionListeners.getRegisteredCallbackCount() > 0) { - obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget(); - } + @Nullable + @Override + public byte[] backupRuntimePermissions(@UserIdInt int userId) { + return mPermissionManagerServiceImpl.backupRuntimePermissions(userId); } - private void handleOnPermissionsChanged(int uid) { - final int count = mPermissionListeners.beginBroadcast(); - try { - for (int i = 0; i < count; i++) { - IOnPermissionsChangeListener callback = mPermissionListeners - .getBroadcastItem(i); - try { - callback.onPermissionsChanged(uid); - } catch (RemoteException e) { - Log.e(TAG, "Permission listener is dead", e); - } - } - } finally { - mPermissionListeners.finishBroadcast(); - } + @Override + public void restoreRuntimePermissions(@NonNull byte[] backup, @UserIdInt int userId) { + mPermissionManagerServiceImpl.restoreRuntimePermissions(backup, userId); } + + @Override + public void restoreDelayedRuntimePermissions(@NonNull String packageName, + @UserIdInt int userId) { + mPermissionManagerServiceImpl.restoreDelayedRuntimePermissions(packageName, userId); + } + + @Override + public void readLegacyPermissionsTEMP( + @NonNull LegacyPermissionSettings legacyPermissionSettings) { + mPermissionManagerServiceImpl.readLegacyPermissionsTEMP(legacyPermissionSettings); + } + + @Override + public void writeLegacyPermissionsTEMP( + @NonNull LegacyPermissionSettings legacyPermissionSettings) { + mPermissionManagerServiceImpl.writeLegacyPermissionsTEMP(legacyPermissionSettings); + } + + @Override + public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp, + @Nullable AndroidPackage oldPkg) { + mPermissionManagerServiceImpl.onPackageAdded(pkg, isInstantApp, oldPkg); + } + + @Override + public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId, + @NonNull PackageInstalledParams params, @UserIdInt int userId) { + mPermissionManagerServiceImpl.onPackageInstalled(pkg, previousAppId, params, userId); + } + + @Override + public void onPackageRemoved(@NonNull AndroidPackage pkg) { + mPermissionManagerServiceImpl.onPackageRemoved(pkg); + } + + @Override + public void onPackageUninstalled(@NonNull String packageName, int appId, + @Nullable AndroidPackage pkg, @NonNull List sharedUserPkgs, + @UserIdInt int userId) { + mPermissionManagerServiceImpl.onPackageUninstalled(packageName, appId, + pkg, sharedUserPkgs, userId); + } + + @Override + public void addOnRuntimePermissionStateChangedListener( + OnRuntimePermissionStateChangedListener listener) { + mPermissionManagerServiceImpl.addOnRuntimePermissionStateChangedListener(listener); + } + + @Override + public void removeOnRuntimePermissionStateChangedListener( + OnRuntimePermissionStateChangedListener listener) { + mPermissionManagerServiceImpl.removeOnRuntimePermissionStateChangedListener(listener); + } + + @Override + public void onSystemReady() { + mPermissionManagerServiceImpl.onSystemReady(); + } + + @Override + public boolean isPermissionsReviewRequired(@NonNull String packageName, + @UserIdInt int userId) { + return mPermissionManagerServiceImpl.isPermissionsReviewRequired(packageName, userId); + } + + @Override + public void readLegacyPermissionStateTEMP() { + mPermissionManagerServiceImpl.readLegacyPermissionStateTEMP(); + } + @Override + public void writeLegacyPermissionStateTEMP() { + mPermissionManagerServiceImpl.writeLegacyPermissionStateTEMP(); + } + @Override + public void onUserRemoved(@UserIdInt int userId) { + mPermissionManagerServiceImpl.onUserRemoved(userId); + } + @NonNull + @Override + public Set getGrantedPermissions(@NonNull String packageName, + @UserIdInt int userId) { + return mPermissionManagerServiceImpl.getGrantedPermissions(packageName, userId); + } + @NonNull + @Override + public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) { + return mPermissionManagerServiceImpl.getPermissionGids(permissionName, userId); + } + @NonNull + @Override + public String[] getAppOpPermissionPackages(@NonNull String permissionName) { + return mPermissionManagerServiceImpl.getAppOpPermissionPackages(permissionName); + } + @Override + public void onStorageVolumeMounted(@Nullable String volumeUuid, + boolean fingerprintChanged) { + mPermissionManagerServiceImpl.onStorageVolumeMounted(volumeUuid, fingerprintChanged); + } + @Override + public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) { + mPermissionManagerServiceImpl.resetRuntimePermissions(pkg, userId); + } + + @Override + public Permission getPermissionTEMP(String permName) { + return mPermissionManagerServiceImpl.getPermissionTEMP(permName); + } + + @NonNull + @Override + public ArrayList getAllPermissionsWithProtection( + @PermissionInfo.Protection int protection) { + return mPermissionManagerServiceImpl.getAllPermissionsWithProtection(protection); + } + + @NonNull + @Override + public ArrayList getAllPermissionsWithProtectionFlags( + @PermissionInfo.ProtectionFlags int protectionFlags) { + return mPermissionManagerServiceImpl + .getAllPermissionsWithProtectionFlags(protectionFlags); + } + + /* End of delegate methods to PermissionManagerServiceInterface */ } /** diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java new file mode 100644 index 0000000000000..d1b99380b0932 --- /dev/null +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -0,0 +1,5060 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.permission; + +import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +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_GRANTED_BY_DEFAULT; +import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE; +import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; +import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; +import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; +import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; +import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; +import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; +import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; +import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; +import static android.content.pm.PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; +import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; +import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; +import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; +import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER; +import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM; +import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE; +import static android.content.pm.PackageManager.MASK_PERMISSION_FLAGS_ALL; +import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; +import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; +import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED; +import static android.permission.PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED; + +import static com.android.server.pm.ApexManager.MATCH_ACTIVE_PACKAGE; +import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; +import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING; +import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS; +import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import android.Manifest; +import android.annotation.AppIdInt; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.app.IActivityManager; +import android.app.admin.DevicePolicyManagerInternal; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.FeatureInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.content.pm.SigningDetails; +import android.content.pm.parsing.component.ComponentMutateUtils; +import android.content.pm.parsing.component.ParsedPermission; +import android.content.pm.parsing.component.ParsedPermissionGroup; +import android.content.pm.parsing.component.ParsedPermissionUtils; +import android.content.pm.permission.CompatibilityPermissionInfo; +import android.content.pm.permission.SplitPermissionInfoParcelable; +import android.metrics.LogMaker; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Build; +import android.os.Debug; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.Trace; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.storage.StorageManager; +import android.permission.IOnPermissionsChangeListener; +import android.permission.PermissionControllerManager; +import android.permission.PermissionManager; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.DebugUtils; +import android.util.EventLog; +import android.util.IntArray; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.compat.IPlatformCompat; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.os.RoSystemProperties; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.CollectionUtils; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.IntPair; +import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; +import com.android.server.FgThread; +import com.android.server.LocalServices; +import com.android.server.ServiceThread; +import com.android.server.SystemConfig; +import com.android.server.Watchdog; +import com.android.server.pm.ApexManager; +import com.android.server.pm.PackageSetting; +import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.UserManagerService; +import com.android.server.pm.parsing.PackageInfoUtils; +import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.policy.PermissionPolicyInternal; +import com.android.server.policy.SoftRestrictedPermissionPolicy; + +import libcore.util.EmptyArray; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * PermissionManagerServiceImpl. + */ +public class PermissionManagerServiceImpl implements PermissionManagerServiceInterface { + + private static final String TAG = "PackageManager"; + private static final String LOG_TAG = PermissionManagerServiceImpl.class.getSimpleName(); + + private static final long BACKUP_TIMEOUT_MILLIS = SECONDS.toMillis(60); + + // For automotive products, CarService enforces allow-listing of the privileged permissions + // com.android.car is the package name which declares auto specific permissions + private static final String CAR_PACKAGE_NAME = "com.android.car"; + + /** Cap the size of permission trees that 3rd party apps can define; in characters of text */ + private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; + /** Empty array to avoid allocations */ + private static final int[] EMPTY_INT_ARRAY = new int[0]; + + /** + * When these flags are set, the system should not automatically modify the permission grant + * state. + */ + private static final int BLOCKING_PERMISSION_FLAGS = FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_POLICY_FIXED + | FLAG_PERMISSION_GRANTED_BY_DEFAULT; + + /** Permission flags set by the user */ + private static final int USER_PERMISSION_FLAGS = FLAG_PERMISSION_USER_SET + | FLAG_PERMISSION_USER_FIXED; + + /** All storage permissions */ + private static final List STORAGE_PERMISSIONS = new ArrayList<>(); + /** All nearby devices permissions */ + private static final List NEARBY_DEVICES_PERMISSIONS = new ArrayList<>(); + + /** + * All permissions that should be granted with the REVOKE_WHEN_REQUESTED flag, if they are + * implicitly added to a package + */ + private static final List IMPLICIT_GRANTED_PERMISSIONS = new ArrayList<>(); + + /** If the permission of the value is granted, so is the key */ + private static final Map FULLER_PERMISSION_MAP = new HashMap<>(); + + static { + FULLER_PERMISSION_MAP.put(Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + FULLER_PERMISSION_MAP.put(Manifest.permission.INTERACT_ACROSS_USERS, + Manifest.permission.INTERACT_ACROSS_USERS_FULL); + STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE); + STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION); + NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE); + NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT); + NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN); + IMPLICIT_GRANTED_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS); + } + + /** Set of source package names for Privileged Permission Allowlist */ + private final ArraySet mPrivilegedPermissionAllowlistSourcePackageNames = + new ArraySet<>(); + + /** Lock to protect internal data access */ + private final Object mLock = new Object(); + + /** Internal connection to the package manager */ + private final PackageManagerInternal mPackageManagerInt; + + /** Internal connection to the user manager */ + private final UserManagerInternal mUserManagerInt; + + @GuardedBy("mLock") + @NonNull + private final DevicePermissionState mState = new DevicePermissionState(); + + /** Permission controller: User space permission management */ + private PermissionControllerManager mPermissionControllerManager; + + /** App ops manager */ + private final AppOpsManager mAppOpsManager; + + /** + * Built-in permissions. Read from system configuration files. Mapping is from + * UID to permission name. + */ + private final SparseArray> mSystemPermissions; + + /** Built-in group IDs given to all packages. Read from system configuration files. */ + @NonNull + private final int[] mGlobalGids; + + private final HandlerThread mHandlerThread; + private final Handler mHandler; + private final Context mContext; + private final MetricsLogger mMetricsLogger = new MetricsLogger(); + private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + + /** Internal storage for permissions and related settings */ + @GuardedBy("mLock") + @NonNull + private final PermissionRegistry mRegistry = new PermissionRegistry(); + + @GuardedBy("mLock") + @Nullable + private ArraySet mPrivappPermissionsViolations; + + @GuardedBy("mLock") + private boolean mSystemReady; + + @GuardedBy("mLock") + private PermissionPolicyInternal mPermissionPolicyInternal; + + /** + * A permission backup might contain apps that are not installed. In this case we delay the + * restoration until the app is installed. + * + *

This array ({@code userId -> noDelayedBackupLeft}) is {@code true} for all the users where + * there is no more delayed backup left. + */ + @GuardedBy("mLock") + private final SparseBooleanArray mHasNoDelayedPermBackup = new SparseBooleanArray(); + + /** Listeners for permission state (granting and flags) changes */ + @GuardedBy("mLock") + private final ArrayList + mRuntimePermissionStateChangedListeners = new ArrayList<>(); + + @NonNull + private final OnPermissionChangeListeners mOnPermissionChangeListeners; + + // TODO: Take a look at the methods defined in the callback. + // The callback was initially created to support the split between permission + // manager and the package manager. However, it's started to be used for other + // purposes. It may make sense to keep as an abstraction, but, the methods + // necessary to be overridden may be different than what was initially needed + // for the split. + private final PermissionCallback mDefaultPermissionCallback = new PermissionCallback() { + @Override + public void onGidsChanged(int appId, int userId) { + mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED)); + } + @Override + public void onPermissionGranted(int uid, int userId) { + mOnPermissionChangeListeners.onPermissionsChanged(uid); + + // Not critical; if this is lost, the application has to request again. + mPackageManagerInt.writeSettings(true); + } + @Override + public void onInstallPermissionGranted() { + mPackageManagerInt.writeSettings(true); + } + @Override + public void onPermissionRevoked(int uid, int userId, String reason) { + mOnPermissionChangeListeners.onPermissionsChanged(uid); + + // Critical; after this call the application should never have the permission + mPackageManagerInt.writeSettings(false); + final int appId = UserHandle.getAppId(uid); + if (reason == null) { + mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED)); + } else { + mHandler.post(() -> killUid(appId, userId, reason)); + } + } + @Override + public void onInstallPermissionRevoked() { + mPackageManagerInt.writeSettings(true); + } + @Override + public void onPermissionUpdated(int[] userIds, boolean sync) { + mPackageManagerInt.writePermissionSettings(userIds, !sync); + } + @Override + public void onInstallPermissionUpdated() { + mPackageManagerInt.writeSettings(true); + } + @Override + public void onPermissionRemoved() { + mPackageManagerInt.writeSettings(false); + } + public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync, + int uid) { + onPermissionUpdated(updatedUserIds, sync); + for (int i = 0; i < updatedUserIds.length; i++) { + int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid)); + mOnPermissionChangeListeners.onPermissionsChanged(userUid); + } + } + public void onInstallPermissionUpdatedNotifyListener(int uid) { + onInstallPermissionUpdated(); + mOnPermissionChangeListeners.onPermissionsChanged(uid); + } + }; + + public PermissionManagerServiceImpl(@NonNull Context context, + @NonNull ArrayMap availableFeatures) { + // The package info cache is the cache for package and permission information. + // Disable the package info and package permission caches locally but leave the + // checkPermission cache active. + PackageManager.invalidatePackageInfoCache(); + PermissionManager.disablePackageNamePermissionCache(); + + mContext = context; + mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class); + mUserManagerInt = LocalServices.getService(UserManagerInternal.class); + mAppOpsManager = context.getSystemService(AppOpsManager.class); + + mPrivilegedPermissionAllowlistSourcePackageNames.add(PLATFORM_PACKAGE_NAME); + // PackageManager.hasSystemFeature() is not used here because PackageManagerService + // isn't ready yet. + if (availableFeatures.containsKey(PackageManager.FEATURE_AUTOMOTIVE)) { + mPrivilegedPermissionAllowlistSourcePackageNames.add(CAR_PACKAGE_NAME); + } + + mHandlerThread = new ServiceThread(TAG, + Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + Watchdog.getInstance().addThread(mHandler); + + SystemConfig systemConfig = SystemConfig.getInstance(); + mSystemPermissions = systemConfig.getSystemPermissions(); + mGlobalGids = systemConfig.getGlobalGids(); + mOnPermissionChangeListeners = new OnPermissionChangeListeners(FgThread.get().getLooper()); + + // propagate permission configuration + final ArrayMap permConfig = + SystemConfig.getInstance().getPermissions(); + synchronized (mLock) { + for (int i = 0; i < permConfig.size(); i++) { + final SystemConfig.PermissionEntry perm = permConfig.valueAt(i); + Permission bp = mRegistry.getPermission(perm.name); + if (bp == null) { + bp = new Permission(perm.name, "android", Permission.TYPE_CONFIG); + mRegistry.addPermission(bp); + } + if (perm.gids != null) { + bp.setGids(perm.gids, perm.perUser); + } + } + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { + return; + } + + mContext.getSystemService(PermissionControllerManager.class).dump(fd, args); + } + + /** + * This method should typically only be used when granting or revoking + * permissions, since the app may immediately restart after this call. + *

+ * If you're doing surgery on app code/data, use {@link PackageFreezer} to + * guard your work against the app being relaunched. + */ + private static void killUid(int appId, int userId, String reason) { + final long identity = Binder.clearCallingIdentity(); + try { + IActivityManager am = ActivityManager.getService(); + if (am != null) { + try { + am.killUidForPermissionChange(appId, userId, reason); + } catch (RemoteException e) { + /* ignore - same process */ + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @NonNull + private String[] getAppOpPermissionPackagesInternal(@NonNull String permissionName) { + synchronized (mLock) { + final ArraySet packageNames = mRegistry.getAppOpPermissionPackages( + permissionName); + if (packageNames == null) { + return EmptyArray.STRING; + } + return packageNames.toArray(new String[0]); + } + } + + @Override + @NonNull + public List getAllPermissionGroups( + @PackageManager.PermissionGroupInfoFlags int flags) { + final int callingUid = Binder.getCallingUid(); + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + return Collections.emptyList(); + } + + final List out = new ArrayList<>(); + synchronized (mLock) { + for (ParsedPermissionGroup pg : mRegistry.getPermissionGroups()) { + out.add(PackageInfoUtils.generatePermissionGroupInfo(pg, flags)); + } + } + + final int callingUserId = UserHandle.getUserId(callingUid); + out.removeIf(it -> mPackageManagerInt.filterAppAccess(it.packageName, callingUid, + callingUserId)); + return out; + } + + @Override + @Nullable + public PermissionGroupInfo getPermissionGroupInfo(String groupName, + @PackageManager.PermissionGroupInfoFlags int flags) { + final int callingUid = Binder.getCallingUid(); + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + return null; + } + + final PermissionGroupInfo permissionGroupInfo; + synchronized (mLock) { + final ParsedPermissionGroup permissionGroup = mRegistry.getPermissionGroup(groupName); + if (permissionGroup == null) { + return null; + } + permissionGroupInfo = PackageInfoUtils.generatePermissionGroupInfo(permissionGroup, + flags); + } + + final int callingUserId = UserHandle.getUserId(callingUid); + if (mPackageManagerInt.filterAppAccess(permissionGroupInfo.packageName, callingUid, + callingUserId)) { + EventLog.writeEvent(0x534e4554, "186113473", callingUid, groupName); + return null; + } + return permissionGroupInfo; + } + + @Override + @Nullable + public PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName, + @PackageManager.PermissionInfoFlags int flags) { + final int callingUid = Binder.getCallingUid(); + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + return null; + } + + final AndroidPackage opPackage = mPackageManagerInt.getPackage(opPackageName); + final int targetSdkVersion = getPermissionInfoCallingTargetSdkVersion(opPackage, + callingUid); + final PermissionInfo permissionInfo; + synchronized (mLock) { + final Permission bp = mRegistry.getPermission(permName); + if (bp == null) { + return null; + } + permissionInfo = bp.generatePermissionInfo(flags, targetSdkVersion); + } + + final int callingUserId = UserHandle.getUserId(callingUid); + if (mPackageManagerInt.filterAppAccess(permissionInfo.packageName, callingUid, + callingUserId)) { + EventLog.writeEvent(0x534e4554, "183122164", callingUid, permName); + return null; + } + return permissionInfo; + } + + private int getPermissionInfoCallingTargetSdkVersion(@Nullable AndroidPackage pkg, int uid) { + final int appId = UserHandle.getAppId(uid); + if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID + || appId == Process.SHELL_UID) { + // System sees all flags. + return Build.VERSION_CODES.CUR_DEVELOPMENT; + } + if (pkg == null) { + return Build.VERSION_CODES.CUR_DEVELOPMENT; + } + return pkg.getTargetSdkVersion(); + } + + @Override + @Nullable + public List queryPermissionsByGroup(String groupName, + @PackageManager.PermissionInfoFlags int flags) { + final int callingUid = Binder.getCallingUid(); + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + return null; + } + + final List out = new ArrayList<>(10); + synchronized (mLock) { + if (groupName != null && mRegistry.getPermissionGroup(groupName) == null) { + return null; + } + for (Permission bp : mRegistry.getPermissions()) { + if (Objects.equals(bp.getGroup(), groupName)) { + out.add(bp.generatePermissionInfo(flags)); + } + } + } + + final int callingUserId = UserHandle.getUserId(callingUid); + out.removeIf(it -> mPackageManagerInt.filterAppAccess(it.packageName, callingUid, + callingUserId)); + return out; + } + + @Override + public boolean addPermission(PermissionInfo info, boolean async) { + final int callingUid = Binder.getCallingUid(); + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + throw new SecurityException("Instant apps can't add permissions"); + } + if (info.labelRes == 0 && info.nonLocalizedLabel == null) { + throw new SecurityException("Label must be specified in permission"); + } + final boolean added; + final boolean changed; + synchronized (mLock) { + final Permission tree = mRegistry.enforcePermissionTree(info.name, callingUid); + Permission bp = mRegistry.getPermission(info.name); + added = bp == null; + int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel); + if (added) { + enforcePermissionCapLocked(info, tree); + bp = new Permission(info.name, tree.getPackageName(), Permission.TYPE_DYNAMIC); + } else if (!bp.isDynamic()) { + throw new SecurityException("Not allowed to modify non-dynamic permission " + + info.name); + } + changed = bp.addToTree(fixedLevel, info, tree); + if (added) { + mRegistry.addPermission(bp); + } + } + if (changed) { + mPackageManagerInt.writeSettings(async); + } + return added; + } + + @Override + public void removePermission(String permName) { + final int callingUid = Binder.getCallingUid(); + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + throw new SecurityException("Instant applications don't have access to this method"); + } + synchronized (mLock) { + mRegistry.enforcePermissionTree(permName, callingUid); + final Permission bp = mRegistry.getPermission(permName); + if (bp == null) { + return; + } + if (bp.isDynamic()) { + // TODO: switch this back to SecurityException + Slog.wtf(TAG, "Not allowed to modify non-dynamic permission " + + permName); + } + mRegistry.removePermission(permName); + } + mPackageManagerInt.writeSettings(false); + } + + @Override + public int getPermissionFlags(String packageName, String permName, int userId) { + final int callingUid = Binder.getCallingUid(); + return getPermissionFlagsInternal(packageName, permName, callingUid, userId); + } + + private int getPermissionFlagsInternal( + String packageName, String permName, int callingUid, int userId) { + if (!mUserManagerInt.exists(userId)) { + return 0; + } + + enforceGrantRevokeGetRuntimePermissionPermissions("getPermissionFlags"); + enforceCrossUserPermission(callingUid, userId, + true, // requireFullPermission + false, // checkShell + "getPermissionFlags"); + + final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + if (pkg == null) { + return 0; + } + if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { + return 0; + } + + synchronized (mLock) { + if (mRegistry.getPermission(permName) == null) { + return 0; + } + + final UidPermissionState uidState = getUidStateLocked(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); + return 0; + } + + return uidState.getPermissionFlags(permName); + } + } + + @Override + public void updatePermissionFlags(String packageName, String permName, int flagMask, + int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { + final int callingUid = Binder.getCallingUid(); + boolean overridePolicy = false; + + if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0) { + if (checkAdjustPolicyFlagPermission) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, + "Need " + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + + " to change policy flags"); + } else if (mPackageManagerInt.getUidTargetSdkVersion(callingUid) + >= Build.VERSION_CODES.Q) { + throw new IllegalArgumentException( + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + " needs " + + " to be checked for packages targeting " + + Build.VERSION_CODES.Q + " or later when changing policy " + + "flags"); + } + overridePolicy = true; + } + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + updatePermissionFlagsInternal( + packageName, permName, flagMask, flagValues, callingUid, userId, + overridePolicy, mDefaultPermissionCallback); + } + + private void updatePermissionFlagsInternal(String packageName, String permName, int flagMask, + int flagValues, int callingUid, int userId, boolean overridePolicy, + PermissionCallback callback) { + if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES + && PermissionManager.shouldTraceGrant(packageName, permName, userId)) { + Log.i(TAG, "System is updating flags for " + packageName + " " + + permName + " for user " + userId + " " + + DebugUtils.flagsToString( + PackageManager.class, "FLAG_PERMISSION_", flagMask) + + " := " + + DebugUtils.flagsToString( + PackageManager.class, "FLAG_PERMISSION_", flagValues) + + " on behalf of uid " + callingUid + + " " + mPackageManagerInt.getNameForUid(callingUid), + new RuntimeException()); + } + + if (!mUserManagerInt.exists(userId)) { + return; + } + + enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags"); + + enforceCrossUserPermission(callingUid, userId, + true, // requireFullPermission + true, // checkShell + "updatePermissionFlags"); + + if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0 && !overridePolicy) { + throw new SecurityException("updatePermissionFlags requires " + + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY); + } + + // Only the system can change these flags and nothing else. + if (callingUid != Process.SYSTEM_UID) { + flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; + flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; + flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; + flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; + flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; + flagValues &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; + flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; + flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; + flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; + } + + final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + if (pkg == null) { + Log.e(TAG, "Unknown package: " + packageName); + return; + } + if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + + boolean isRequested = false; + // Fast path, the current package has requested the permission. + if (pkg.getRequestedPermissions().contains(permName)) { + isRequested = true; + } + if (!isRequested) { + // Slow path, go through all shared user packages. + String[] sharedUserPackageNames = + mPackageManagerInt.getSharedUserPackagesForPackage(packageName, userId); + for (String sharedUserPackageName : sharedUserPackageNames) { + AndroidPackage sharedUserPkg = mPackageManagerInt.getPackage( + sharedUserPackageName); + if (sharedUserPkg != null + && sharedUserPkg.getRequestedPermissions().contains(permName)) { + isRequested = true; + break; + } + } + } + + final boolean isRuntimePermission; + final boolean permissionUpdated; + synchronized (mLock) { + final Permission bp = mRegistry.getPermission(permName); + if (bp == null) { + throw new IllegalArgumentException("Unknown permission: " + permName); + } + + isRuntimePermission = bp.isRuntime(); + + final UidPermissionState uidState = getUidStateLocked(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); + return; + } + + if (!uidState.hasPermissionState(permName) && !isRequested) { + Log.e(TAG, "Permission " + permName + " isn't requested by package " + packageName); + return; + } + + permissionUpdated = uidState.updatePermissionFlags(bp, flagMask, flagValues); + } + + if (permissionUpdated && isRuntimePermission) { + notifyRuntimePermissionStateChanged(packageName, userId); + } + if (permissionUpdated && callback != null) { + // Install and runtime permissions are stored in different places, + // so figure out what permission changed and persist the change. + if (!isRuntimePermission) { + int userUid = UserHandle.getUid(userId, pkg.getUid()); + callback.onInstallPermissionUpdatedNotifyListener(userUid); + } else { + callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false, pkg.getUid()); + } + } + } + + /** + * Update the permission flags for all packages and runtime permissions of a user in order + * to allow device or profile owner to remove POLICY_FIXED. + */ + @Override + public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, + final int userId) { + final int callingUid = Binder.getCallingUid(); + if (!mUserManagerInt.exists(userId)) { + return; + } + + enforceGrantRevokeRuntimePermissionPermissions( + "updatePermissionFlagsForAllApps"); + enforceCrossUserPermission(callingUid, userId, + true, // requireFullPermission + true, // checkShell + "updatePermissionFlagsForAllApps"); + + // Only the system can change system fixed flags. + final int effectiveFlagMask = (callingUid != Process.SYSTEM_UID) + ? flagMask : flagMask & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; + final int effectiveFlagValues = (callingUid != Process.SYSTEM_UID) + ? flagValues : flagValues & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; + + final boolean[] changed = new boolean[1]; + mPackageManagerInt.forEachPackage(pkg -> { + synchronized (mLock) { + final UidPermissionState uidState = getUidStateLocked(pkg, userId); + if (uidState == null) { + Slog.e(TAG, + "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); + return; + } + changed[0] |= uidState.updatePermissionFlagsForAllPermissions( + effectiveFlagMask, effectiveFlagValues); + } + mOnPermissionChangeListeners.onPermissionsChanged(pkg.getUid()); + }); + + if (changed[0]) { + mPackageManagerInt.writePermissionSettings(new int[] { userId }, true); + } + } + + @Override + public int checkPermission(String pkgName, String permName, int userId) { + if (!mUserManagerInt.exists(userId)) { + return PackageManager.PERMISSION_DENIED; + } + + final AndroidPackage pkg = mPackageManagerInt.getPackage(pkgName); + if (pkg == null) { + return PackageManager.PERMISSION_DENIED; + } + return checkPermissionInternal(pkg, true, permName, userId); + } + + private int checkPermissionInternal(@NonNull AndroidPackage pkg, boolean isPackageExplicit, + @NonNull String permissionName, @UserIdInt int userId) { + final int callingUid = Binder.getCallingUid(); + if (isPackageExplicit || pkg.getSharedUserId() == null) { + if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { + return PackageManager.PERMISSION_DENIED; + } + } else { + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + return PackageManager.PERMISSION_DENIED; + } + } + + final int uid = UserHandle.getUid(userId, pkg.getUid()); + final boolean isInstantApp = mPackageManagerInt.getInstantAppPackageName(uid) != null; + + synchronized (mLock) { + final UidPermissionState uidState = getUidStateLocked(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); + return PackageManager.PERMISSION_DENIED; + } + + if (checkSinglePermissionInternalLocked(uidState, permissionName, isInstantApp)) { + return PackageManager.PERMISSION_GRANTED; + } + + final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); + if (fullerPermissionName != null && checkSinglePermissionInternalLocked(uidState, + fullerPermissionName, isInstantApp)) { + return PackageManager.PERMISSION_GRANTED; + } + } + + return PackageManager.PERMISSION_DENIED; + } + + @GuardedBy("mLock") + private boolean checkSinglePermissionInternalLocked(@NonNull UidPermissionState uidState, + @NonNull String permissionName, boolean isInstantApp) { + if (!uidState.isPermissionGranted(permissionName)) { + return false; + } + + if (isInstantApp) { + final Permission permission = mRegistry.getPermission(permissionName); + return permission != null && permission.isInstant(); + } + + return true; + } + + @Override + public int checkUidPermission(int uid, String permName) { + final int userId = UserHandle.getUserId(uid); + if (!mUserManagerInt.exists(userId)) { + return PackageManager.PERMISSION_DENIED; + } + + final AndroidPackage pkg = mPackageManagerInt.getPackage(uid); + return checkUidPermissionInternal(pkg, uid, permName); + } + + /** + * Checks whether or not the given package has been granted the specified + * permission. If the given package is {@code null}, we instead check the + * system permissions for the given UID. + * + * @see SystemConfig#getSystemPermissions() + */ + private int checkUidPermissionInternal(@Nullable AndroidPackage pkg, int uid, + @NonNull String permissionName) { + if (pkg != null) { + final int userId = UserHandle.getUserId(uid); + return checkPermissionInternal(pkg, false, permissionName, userId); + } + + synchronized (mLock) { + if (checkSingleUidPermissionInternalLocked(uid, permissionName)) { + return PackageManager.PERMISSION_GRANTED; + } + + final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); + if (fullerPermissionName != null + && checkSingleUidPermissionInternalLocked(uid, fullerPermissionName)) { + return PackageManager.PERMISSION_GRANTED; + } + } + + return PackageManager.PERMISSION_DENIED; + } + + @GuardedBy("mLock") + private boolean checkSingleUidPermissionInternalLocked(int uid, + @NonNull String permissionName) { + ArraySet permissions = mSystemPermissions.get(uid); + return permissions != null && permissions.contains(permissionName); + } + + @Override + public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, + "addOnPermissionsChangeListener"); + + mOnPermissionChangeListeners.addListener(listener); + } + + @Override + public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) { + if (mPackageManagerInt.getInstantAppPackageName(Binder.getCallingUid()) != null) { + throw new SecurityException("Instant applications don't have access to this method"); + } + mOnPermissionChangeListeners.removeListener(listener); + } + + @Nullable + @Override + public List getAllowlistedRestrictedPermissions(@NonNull String packageName, + @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId) { + Objects.requireNonNull(packageName); + Preconditions.checkFlagsArgument(flags, + PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE + | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM + | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); + Preconditions.checkArgumentNonNegative(userId, null); + + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, + "getAllowlistedRestrictedPermissions for user " + userId); + } + + final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + if (pkg == null) { + return null; + } + + final int callingUid = Binder.getCallingUid(); + if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) { + return null; + } + final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) + == PackageManager.PERMISSION_GRANTED; + final boolean isCallerInstallerOnRecord = + mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); + + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 + && !isCallerPrivileged) { + throw new SecurityException("Querying system allowlist requires " + + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); + } + + if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE + | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) { + if (!isCallerPrivileged && !isCallerInstallerOnRecord) { + throw new SecurityException("Querying upgrade or installer allowlist" + + " requires being installer on record or " + + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); + } + } + + final long identity = Binder.clearCallingIdentity(); + try { + return getAllowlistedRestrictedPermissionsInternal(pkg, flags, userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Nullable + private List getAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg, + @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId) { + synchronized (mLock) { + final UidPermissionState uidState = getUidStateLocked(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); + return null; + } + + int queryFlags = 0; + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0) { + queryFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; + } + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) { + queryFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; + } + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) { + queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; + } + + ArrayList allowlistedPermissions = null; + + final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions()); + for (int i = 0; i < permissionCount; i++) { + final String permissionName = pkg.getRequestedPermissions().get(i); + final int currentFlags = + uidState.getPermissionFlags(permissionName); + if ((currentFlags & queryFlags) != 0) { + if (allowlistedPermissions == null) { + allowlistedPermissions = new ArrayList<>(); + } + allowlistedPermissions.add(permissionName); + } + } + + return allowlistedPermissions; + } + } + + @Override + public boolean addAllowlistedRestrictedPermission(@NonNull String packageName, + @NonNull String permName, @PackageManager.PermissionWhitelistFlags int flags, + @UserIdInt int userId) { + // Other argument checks are done in get/setAllowlistedRestrictedPermissions + Objects.requireNonNull(permName); + + if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) { + return false; + } + + List permissions = + getAllowlistedRestrictedPermissions(packageName, flags, userId); + if (permissions == null) { + permissions = new ArrayList<>(1); + } + if (permissions.indexOf(permName) < 0) { + permissions.add(permName); + return setAllowlistedRestrictedPermissions(packageName, permissions, + flags, userId); + } + return false; + } + + private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission( + @NonNull String permName) { + final String permissionPackageName; + final boolean isImmutablyRestrictedPermission; + synchronized (mLock) { + final Permission bp = mRegistry.getPermission(permName); + if (bp == null) { + Slog.w(TAG, "No such permissions: " + permName); + return false; + } + permissionPackageName = bp.getPackageName(); + isImmutablyRestrictedPermission = bp.isHardOrSoftRestricted() + && bp.isImmutablyRestricted(); + } + + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + if (mPackageManagerInt.filterAppAccess(permissionPackageName, callingUid, callingUserId)) { + EventLog.writeEvent(0x534e4554, "186404356", callingUid, permName); + return false; + } + + if (isImmutablyRestrictedPermission && mContext.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Cannot modify allowlisting of an immutably " + + "restricted permission: " + permName); + } + + return true; + } + + @Override + public boolean removeAllowlistedRestrictedPermission(@NonNull String packageName, + @NonNull String permName, @PackageManager.PermissionWhitelistFlags int flags, + @UserIdInt int userId) { + // Other argument checks are done in get/setAllowlistedRestrictedPermissions + Objects.requireNonNull(permName); + + if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) { + return false; + } + + final List permissions = + getAllowlistedRestrictedPermissions(packageName, flags, userId); + if (permissions != null && permissions.remove(permName)) { + return setAllowlistedRestrictedPermissions(packageName, permissions, + flags, userId); + } + return false; + } + + private boolean setAllowlistedRestrictedPermissions(@NonNull String packageName, + @Nullable List permissions, @PackageManager.PermissionWhitelistFlags int flags, + @UserIdInt int userId) { + Objects.requireNonNull(packageName); + Preconditions.checkFlagsArgument(flags, + PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE + | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM + | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); + Preconditions.checkArgument(Integer.bitCount(flags) == 1); + Preconditions.checkArgumentNonNegative(userId, null); + + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS, + "setAllowlistedRestrictedPermissions for user " + userId); + } + + final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + if (pkg == null) { + return false; + } + + final int callingUid = Binder.getCallingUid(); + if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) { + return false; + } + + final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) + == PackageManager.PERMISSION_GRANTED; + final boolean isCallerInstallerOnRecord = + mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); + + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 && !isCallerPrivileged) { + throw new SecurityException("Modifying system allowlist requires " + + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); + } + + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) { + if (!isCallerPrivileged && !isCallerInstallerOnRecord) { + throw new SecurityException("Modifying upgrade allowlist requires" + + " being installer on record or " + + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); + } + final List allowlistedPermissions = + getAllowlistedRestrictedPermissions(pkg.getPackageName(), flags, userId); + if (permissions == null || permissions.isEmpty()) { + if (allowlistedPermissions == null || allowlistedPermissions.isEmpty()) { + return true; + } + } else { + // Only the system can add and remove while the installer can only remove. + final int permissionCount = permissions.size(); + for (int i = 0; i < permissionCount; i++) { + if ((allowlistedPermissions == null + || !allowlistedPermissions.contains(permissions.get(i))) + && !isCallerPrivileged) { + throw new SecurityException("Adding to upgrade allowlist requires" + + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); + } + } + } + + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) { + if (!isCallerPrivileged && !isCallerInstallerOnRecord) { + throw new SecurityException("Modifying installer allowlist requires" + + " being installer on record or " + + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); + } + } + } + + final long identity = Binder.clearCallingIdentity(); + try { + setAllowlistedRestrictedPermissionsInternal(pkg, permissions, flags, userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + + return true; + } + + @Override + public void grantRuntimePermission(String packageName, String permName, final int userId) { + final int callingUid = Binder.getCallingUid(); + final boolean overridePolicy = + checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) + == PackageManager.PERMISSION_GRANTED; + + grantRuntimePermissionInternal(packageName, permName, overridePolicy, + callingUid, userId, mDefaultPermissionCallback); + } + + private void grantRuntimePermissionInternal(String packageName, String permName, + boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) { + if (PermissionManager.DEBUG_TRACE_GRANTS + && PermissionManager.shouldTraceGrant(packageName, permName, userId)) { + Log.i(PermissionManager.LOG_TAG_TRACE_GRANTS, "System is granting " + packageName + " " + + permName + " for user " + userId + " on behalf of uid " + callingUid + + " " + mPackageManagerInt.getNameForUid(callingUid), + new RuntimeException()); + } + if (!mUserManagerInt.exists(userId)) { + Log.e(TAG, "No such user:" + userId); + return; + } + + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "grantRuntimePermission"); + + enforceCrossUserPermission(callingUid, userId, + true, // requireFullPermission + true, // checkShell + "grantRuntimePermission"); + + final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(packageName); + if (pkg == null || ps == null) { + Log.e(TAG, "Unknown package: " + packageName); + return; + } + if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + + final boolean isRolePermission; + final boolean isSoftRestrictedPermission; + synchronized (mLock) { + final Permission permission = mRegistry.getPermission(permName); + if (permission == null) { + throw new IllegalArgumentException("Unknown permission: " + permName); + } + isRolePermission = permission.isRole(); + isSoftRestrictedPermission = permission.isSoftRestricted(); + } + final boolean mayGrantRolePermission = isRolePermission + && mayManageRolePermission(callingUid); + final boolean mayGrantSoftRestrictedPermission = isSoftRestrictedPermission + && SoftRestrictedPermissionPolicy.forPermission(mContext, + AndroidPackageUtils.generateAppInfoWithoutState(pkg), pkg, + UserHandle.of(userId), permName) + .mayGrantPermission(); + + final boolean isRuntimePermission; + final boolean permissionHasGids; + synchronized (mLock) { + final Permission bp = mRegistry.getPermission(permName); + if (bp == null) { + throw new IllegalArgumentException("Unknown permission: " + permName); + } + + isRuntimePermission = bp.isRuntime(); + permissionHasGids = bp.hasGids(); + if (isRuntimePermission || bp.isDevelopment()) { + // Good. + } else if (bp.isRole()) { + if (!mayGrantRolePermission) { + throw new SecurityException("Permission " + permName + " is managed by role"); + } + } else { + throw new SecurityException("Permission " + permName + " requested by " + + pkg.getPackageName() + " is not a changeable permission type"); + } + + final UidPermissionState uidState = getUidStateLocked(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); + return; + } + + if (!(uidState.hasPermissionState(permName) + || pkg.getRequestedPermissions().contains(permName))) { + throw new SecurityException("Package " + pkg.getPackageName() + + " has not requested permission " + permName); + } + + // If a permission review is required for legacy apps we represent + // their permissions as always granted runtime ones since we need + // to keep the review required permission flag per user while an + // install permission's state is shared across all users. + if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) { + return; + } + + final int flags = uidState.getPermissionFlags(permName); + if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) { + Log.e(TAG, "Cannot grant system fixed permission " + + permName + " for package " + packageName); + return; + } + if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { + Log.e(TAG, "Cannot grant policy fixed permission " + + permName + " for package " + packageName); + return; + } + + if (bp.isHardRestricted() + && (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { + Log.e(TAG, "Cannot grant hard restricted non-exempt permission " + + permName + " for package " + packageName); + return; + } + + if (bp.isSoftRestricted() && !mayGrantSoftRestrictedPermission) { + Log.e(TAG, "Cannot grant soft restricted permission " + permName + " for package " + + packageName); + return; + } + + if (bp.isDevelopment() || bp.isRole()) { + // Development permissions must be handled specially, since they are not + // normal runtime permissions. For now they apply to all users. + // TODO(zhanghai): We are breaking the behavior above by making all permission state + // per-user. It isn't documented behavior and relatively rarely used anyway. + if (!uidState.grantPermission(bp)) { + return; + } + } else { + if (ps.getUserStateOrDefault(userId).isInstantApp() && !bp.isInstant()) { + throw new SecurityException("Cannot grant non-ephemeral permission" + permName + + " for package " + packageName); + } + + if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { + Slog.w(TAG, "Cannot grant runtime permission to a legacy app"); + return; + } + + if (!uidState.grantPermission(bp)) { + return; + } + } + } + + if (isRuntimePermission) { + logPermission(MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED, + permName, packageName); + } + + final int uid = UserHandle.getUid(userId, pkg.getUid()); + if (callback != null) { + if (isRuntimePermission) { + callback.onPermissionGranted(uid, userId); + } else { + callback.onInstallPermissionGranted(); + } + if (permissionHasGids) { + callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId); + } + } + + if (isRuntimePermission) { + notifyRuntimePermissionStateChanged(packageName, userId); + } + } + + @Override + public void revokeRuntimePermission(String packageName, String permName, int userId, + String reason) { + final int callingUid = Binder.getCallingUid(); + final boolean overridePolicy = + checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) + == PackageManager.PERMISSION_GRANTED; + + revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId, + reason, mDefaultPermissionCallback); + } + + private void revokeRuntimePermissionInternal(String packageName, String permName, + boolean overridePolicy, int callingUid, final int userId, String reason, + PermissionCallback callback) { + if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES + && PermissionManager.shouldTraceGrant(packageName, permName, userId)) { + Log.i(TAG, "System is revoking " + packageName + " " + + permName + " for user " + userId + " on behalf of uid " + callingUid + + " " + mPackageManagerInt.getNameForUid(callingUid), + new RuntimeException()); + } + if (!mUserManagerInt.exists(userId)) { + Log.e(TAG, "No such user:" + userId); + return; + } + + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, + "revokeRuntimePermission"); + + enforceCrossUserPermission(callingUid, userId, + true, // requireFullPermission + true, // checkShell + "revokeRuntimePermission"); + + final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + if (pkg == null) { + Log.e(TAG, "Unknown package: " + packageName); + return; + } + if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + + final boolean isRolePermission; + synchronized (mLock) { + final Permission permission = mRegistry.getPermission(permName); + if (permission == null) { + throw new IllegalArgumentException("Unknown permission: " + permName); + } + isRolePermission = permission.isRole(); + } + final boolean mayRevokeRolePermission = isRolePermission + // Allow ourselves to revoke role permissions due to definition changes. + && (callingUid == Process.myUid() || mayManageRolePermission(callingUid)); + + final boolean isRuntimePermission; + synchronized (mLock) { + final Permission bp = mRegistry.getPermission(permName); + if (bp == null) { + throw new IllegalArgumentException("Unknown permission: " + permName); + } + + isRuntimePermission = bp.isRuntime(); + if (isRuntimePermission || bp.isDevelopment()) { + // Good. + } else if (bp.isRole()) { + if (!mayRevokeRolePermission) { + throw new SecurityException("Permission " + permName + " is managed by role"); + } + } else { + throw new SecurityException("Permission " + permName + " requested by " + + pkg.getPackageName() + " is not a changeable permission type"); + } + + final UidPermissionState uidState = getUidStateLocked(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); + return; + } + + if (!(uidState.hasPermissionState(permName) + || pkg.getRequestedPermissions().contains(permName))) { + throw new SecurityException("Package " + pkg.getPackageName() + + " has not requested permission " + permName); + } + + // If a permission review is required for legacy apps we represent + // their permissions as always granted runtime ones since we need + // to keep the review required permission flag per user while an + // install permission's state is shared across all users. + if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) { + return; + } + + final int flags = uidState.getPermissionFlags(permName); + // Only the system may revoke SYSTEM_FIXED permissions. + if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0 + && UserHandle.getCallingAppId() != Process.SYSTEM_UID) { + throw new SecurityException("Non-System UID cannot revoke system fixed permission " + + permName + " for package " + packageName); + } + if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { + throw new SecurityException("Cannot revoke policy fixed permission " + + permName + " for package " + packageName); + } + + // Development permissions must be handled specially, since they are not + // normal runtime permissions. For now they apply to all users. + // TODO(zhanghai): We are breaking the behavior above by making all permission state + // per-user. It isn't documented behavior and relatively rarely used anyway. + if (!uidState.revokePermission(bp)) { + return; + } + } + + if (isRuntimePermission) { + logPermission(MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED, + permName, packageName); + } + + if (callback != null) { + if (isRuntimePermission) { + callback.onPermissionRevoked(UserHandle.getUid(userId, pkg.getUid()), userId, + reason); + } else { + mDefaultPermissionCallback.onInstallPermissionRevoked(); + } + } + + if (isRuntimePermission) { + notifyRuntimePermissionStateChanged(packageName, userId); + } + } + + private boolean mayManageRolePermission(int uid) { + final PackageManager packageManager = mContext.getPackageManager(); + final String[] packageNames = packageManager.getPackagesForUid(uid); + if (packageNames == null) { + return false; + } + final String permissionControllerPackageName = + packageManager.getPermissionControllerPackageName(); + return Arrays.asList(packageNames).contains(permissionControllerPackageName); + } + + /** + * Reverts user permission state changes (permissions and flags). + * + * @param pkg The package for which to reset. + * @param userId The device user for which to do a reset. + */ + private void resetRuntimePermissionsInternal(@NonNull AndroidPackage pkg, + @UserIdInt int userId) { + final String packageName = pkg.getPackageName(); + + // These are flags that can change base on user actions. + final int userSettableMask = FLAG_PERMISSION_USER_SET + | FLAG_PERMISSION_USER_FIXED + | FLAG_PERMISSION_REVOKED_COMPAT + | FLAG_PERMISSION_REVIEW_REQUIRED + | FLAG_PERMISSION_ONE_TIME + | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; + + final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_POLICY_FIXED; + + // Delay and combine non-async permission callbacks + final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions()); + final boolean[] permissionRemoved = new boolean[1]; + final ArraySet revokedPermissions = new ArraySet<>(); + final IntArray syncUpdatedUsers = new IntArray(permissionCount); + final IntArray asyncUpdatedUsers = new IntArray(permissionCount); + + PermissionCallback delayingPermCallback = new PermissionCallback() { + public void onGidsChanged(int appId, int userId) { + mDefaultPermissionCallback.onGidsChanged(appId, userId); + } + + public void onPermissionChanged() { + mDefaultPermissionCallback.onPermissionChanged(); + } + + public void onPermissionGranted(int uid, int userId) { + mDefaultPermissionCallback.onPermissionGranted(uid, userId); + } + + public void onInstallPermissionGranted() { + mDefaultPermissionCallback.onInstallPermissionGranted(); + } + + public void onPermissionRevoked(int uid, int userId, String reason) { + revokedPermissions.add(IntPair.of(uid, userId)); + + syncUpdatedUsers.add(userId); + } + + public void onInstallPermissionRevoked() { + mDefaultPermissionCallback.onInstallPermissionRevoked(); + } + + public void onPermissionUpdated(int[] updatedUserIds, boolean sync) { + for (int userId : updatedUserIds) { + if (sync) { + syncUpdatedUsers.add(userId); + asyncUpdatedUsers.remove(userId); + } else { + // Don't override sync=true by sync=false + if (syncUpdatedUsers.indexOf(userId) == -1) { + asyncUpdatedUsers.add(userId); + } + } + } + } + + public void onPermissionRemoved() { + permissionRemoved[0] = true; + } + + public void onInstallPermissionUpdated() { + mDefaultPermissionCallback.onInstallPermissionUpdated(); + } + + public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, + boolean sync, int uid) { + onPermissionUpdated(updatedUserIds, sync); + mOnPermissionChangeListeners.onPermissionsChanged(uid); + } + + public void onInstallPermissionUpdatedNotifyListener(int uid) { + mDefaultPermissionCallback.onInstallPermissionUpdatedNotifyListener(uid); + } + }; + + for (int i = 0; i < permissionCount; i++) { + final String permName = pkg.getRequestedPermissions().get(i); + + final boolean isRuntimePermission; + synchronized (mLock) { + final Permission permission = mRegistry.getPermission(permName); + if (permission == null) { + continue; + } + + if (permission.isRemoved()) { + continue; + } + isRuntimePermission = permission.isRuntime(); + } + + // If shared user we just reset the state to which only this app contributed. + final String[] pkgNames = mPackageManagerInt.getSharedUserPackagesForPackage( + pkg.getPackageName(), userId); + if (pkgNames.length > 0) { + boolean used = false; + for (String sharedPkgName : pkgNames) { + final AndroidPackage sharedPkg = + mPackageManagerInt.getPackage(sharedPkgName); + if (sharedPkg != null && !sharedPkg.getPackageName().equals(packageName) + && sharedPkg.getRequestedPermissions().contains(permName)) { + used = true; + break; + } + } + if (used) { + continue; + } + } + + final int oldFlags = + getPermissionFlagsInternal(packageName, permName, Process.SYSTEM_UID, userId); + + // Always clear the user settable flags. + // If permission review is enabled and this is a legacy app, mark the + // permission as requiring a review as this is the initial state. + final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId); + final int targetSdk = mPackageManagerInt.getUidTargetSdkVersion(uid); + final int flags = (targetSdk < Build.VERSION_CODES.M && isRuntimePermission) + ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKED_COMPAT + : 0; + + updatePermissionFlagsInternal( + packageName, permName, userSettableMask, flags, Process.SYSTEM_UID, userId, + false, delayingPermCallback); + + // Below is only runtime permission handling. + if (!isRuntimePermission) { + continue; + } + + // Never clobber system or policy. + if ((oldFlags & policyOrSystemFlags) != 0) { + continue; + } + + // If this permission was granted by default or role, make sure it is. + if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0 + || (oldFlags & FLAG_PERMISSION_GRANTED_BY_ROLE) != 0) { + // PermissionPolicyService will handle the app op for runtime permissions later. + grantRuntimePermissionInternal(packageName, permName, false, + Process.SYSTEM_UID, userId, delayingPermCallback); + // In certain cases we should leave the state unchanged: + // -- If permission review is enabled the permissions for a legacy apps + // are represented as constantly granted runtime ones + // -- If the permission was split from a non-runtime permission + } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0 + && !isPermissionSplitFromNonRuntime(permName, targetSdk)) { + // Otherwise, reset the permission. + revokeRuntimePermissionInternal(packageName, permName, false, Process.SYSTEM_UID, + userId, null, delayingPermCallback); + } + } + + // Execute delayed callbacks + if (permissionRemoved[0]) { + mDefaultPermissionCallback.onPermissionRemoved(); + } + + // Slight variation on the code in mPermissionCallback.onPermissionRevoked() as we cannot + // kill uid while holding mPackages-lock + if (!revokedPermissions.isEmpty()) { + int numRevokedPermissions = revokedPermissions.size(); + for (int i = 0; i < numRevokedPermissions; i++) { + int revocationUID = IntPair.first(revokedPermissions.valueAt(i)); + int revocationUserId = IntPair.second(revokedPermissions.valueAt(i)); + + mOnPermissionChangeListeners.onPermissionsChanged(revocationUID); + + // Kill app later as we are holding mPackages + mHandler.post(() -> killUid(UserHandle.getAppId(revocationUID), revocationUserId, + KILL_APP_REASON_PERMISSIONS_REVOKED)); + } + } + + mPackageManagerInt.writePermissionSettings(syncUpdatedUsers.toArray(), false); + mPackageManagerInt.writePermissionSettings(asyncUpdatedUsers.toArray(), true); + } + + /** + * Determine if the given permission should be treated as split from a + * non-runtime permission for an application targeting the given SDK level. + */ + private boolean isPermissionSplitFromNonRuntime(String permName, int targetSdk) { + final List splitPerms = getSplitPermissionInfos(); + final int size = splitPerms.size(); + for (int i = 0; i < size; i++) { + final PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(i); + if (targetSdk < splitPerm.getTargetSdk() + && splitPerm.getNewPermissions().contains(permName)) { + synchronized (mLock) { + final Permission perm = + mRegistry.getPermission(splitPerm.getSplitPermission()); + return perm != null && !perm.isRuntime(); + } + } + } + return false; + } + + /** + * This change makes it so that apps are told to show rationale for asking for background + * location access every time they request. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L; + + @Override + public boolean shouldShowRequestPermissionRationale(String packageName, String permName, + @UserIdInt int userId) { + final int callingUid = Binder.getCallingUid(); + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "canShowRequestPermissionRationale for user " + userId); + } + + final int uid = + mPackageManagerInt.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId); + if (UserHandle.getAppId(callingUid) != UserHandle.getAppId(uid)) { + return false; + } + + if (checkPermission(packageName, permName, userId) == PackageManager.PERMISSION_GRANTED) { + return false; + } + + final int flags; + + final long identity = Binder.clearCallingIdentity(); + try { + flags = getPermissionFlagsInternal(packageName, permName, callingUid, userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + + final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED + | PackageManager.FLAG_PERMISSION_POLICY_FIXED + | PackageManager.FLAG_PERMISSION_USER_FIXED; + + if ((flags & fixedFlags) != 0) { + return false; + } + + synchronized (mLock) { + final Permission permission = mRegistry.getPermission(permName); + if (permission == null) { + return false; + } + if (permission.isHardRestricted() + && (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { + return false; + } + } + + final long token = Binder.clearCallingIdentity(); + try { + if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION) + && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID, + packageName, userId)) { + return true; + } + } catch (RemoteException e) { + Log.e(TAG, "Unable to check if compatibility change is enabled.", e); + } finally { + Binder.restoreCallingIdentity(token); + } + + return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0; + } + + @Override + public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "isPermissionRevokedByPolicy for user " + userId); + } + + if (checkPermission(packageName, permName, userId) == PackageManager.PERMISSION_GRANTED) { + return false; + } + + final int callingUid = Binder.getCallingUid(); + if (mPackageManagerInt.filterAppAccess(packageName, callingUid, userId)) { + return false; + } + + final long identity = Binder.clearCallingIdentity(); + try { + final int flags = getPermissionFlagsInternal(packageName, permName, callingUid, userId); + return (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Get the state of the runtime permissions as xml file. + * + *

Can not be called on main thread. + * + * @param userId The user ID the data should be extracted for + * + * @return The state as a xml file + */ + @Nullable + @Override + public byte[] backupRuntimePermissions(@UserIdInt int userId) { + Preconditions.checkArgumentNonNegative(userId, "userId"); + CompletableFuture backup = new CompletableFuture<>(); + mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId), + mContext.getMainExecutor(), backup::complete); + + try { + return backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Slog.e(TAG, "Cannot create permission backup for user " + userId, e); + return null; + } + } + + /** + * Restore a permission state previously backed up via {@link #backupRuntimePermissions}. + * + *

If not all state can be restored, the un-appliable state will be delayed and can be + * applied via {@link #restoreDelayedRuntimePermissions}. + * + * @param backup The state as an xml file + * @param userId The user ID the data should be restored for + */ + @Override + public void restoreRuntimePermissions(@NonNull byte[] backup, @UserIdInt int userId) { + Objects.requireNonNull(backup, "backup"); + Preconditions.checkArgumentNonNegative(userId, "userId"); + synchronized (mLock) { + mHasNoDelayedPermBackup.delete(userId); + } + mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(backup, + UserHandle.of(userId)); + } + + /** + * Try to apply permission backup that was previously not applied. + * + *

Can not be called on main thread. + * + * @param packageName The package that is newly installed + * @param userId The user ID the package is installed for + * + * @see #restoreRuntimePermissions + */ + @Override + public void restoreDelayedRuntimePermissions(@NonNull String packageName, + @UserIdInt int userId) { + Objects.requireNonNull(packageName, "packageName"); + Preconditions.checkArgumentNonNegative(userId, "userId"); + synchronized (mLock) { + if (mHasNoDelayedPermBackup.get(userId, false)) { + return; + } + } + mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName, + UserHandle.of(userId), mContext.getMainExecutor(), (hasMoreBackup) -> { + if (hasMoreBackup) { + return; + } + synchronized (mLock) { + mHasNoDelayedPermBackup.put(userId, true); + } + }); + } + + @Override + public void addOnRuntimePermissionStateChangedListener( + PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener listener) { + synchronized (mLock) { + mRuntimePermissionStateChangedListeners.add(listener); + } + } + + @Override + public void removeOnRuntimePermissionStateChangedListener( + PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener listener) { + synchronized (mLock) { + mRuntimePermissionStateChangedListeners.remove(listener); + } + } + + private void notifyRuntimePermissionStateChanged(@NonNull String packageName, + @UserIdInt int userId) { + FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( + PermissionManagerServiceImpl::doNotifyRuntimePermissionStateChanged, + PermissionManagerServiceImpl.this, packageName, userId)); + } + + private void doNotifyRuntimePermissionStateChanged(@NonNull String packageName, + @UserIdInt int userId) { + final ArrayList + listeners; + synchronized (mLock) { + if (mRuntimePermissionStateChangedListeners.isEmpty()) { + return; + } + listeners = new ArrayList<>(mRuntimePermissionStateChangedListeners); + } + final int listenerCount = listeners.size(); + for (int i = 0; i < listenerCount; i++) { + listeners.get(i).onRuntimePermissionStateChanged(packageName, userId); + } + } + + /** + * If the app is updated, and has scoped storage permissions, then it is possible that the + * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. + * @param newPackage The new package that was installed + * @param oldPackage The old package that was updated + */ + private void revokeStoragePermissionsIfScopeExpandedInternal( + @NonNull AndroidPackage newPackage, + @NonNull AndroidPackage oldPackage) { + boolean downgradedSdk = oldPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q + && newPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q; + boolean upgradedSdk = oldPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q + && newPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q; + boolean newlyRequestsLegacy = !upgradedSdk && !oldPackage.isRequestLegacyExternalStorage() + && newPackage.isRequestLegacyExternalStorage(); + + if (!newlyRequestsLegacy && !downgradedSdk) { + return; + } + + final int callingUid = Binder.getCallingUid(); + for (int userId: getAllUserIds()) { + int numRequestedPermissions = newPackage.getRequestedPermissions().size(); + for (int i = 0; i < numRequestedPermissions; i++) { + PermissionInfo permInfo = getPermissionInfo( + newPackage.getRequestedPermissions().get(i), + newPackage.getPackageName(), 0); + if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) { + continue; + } + + EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(), + "Revoking permission " + permInfo.name + " from package " + + newPackage.getPackageName() + " as either the sdk downgraded " + + downgradedSdk + " or newly requested legacy full storage " + + newlyRequestsLegacy); + + try { + revokeRuntimePermissionInternal(newPackage.getPackageName(), permInfo.name, + false, callingUid, userId, null, mDefaultPermissionCallback); + } catch (IllegalStateException | SecurityException e) { + Log.e(TAG, "unable to revoke " + permInfo.name + " for " + + newPackage.getPackageName() + " user " + userId, e); + } + } + } + + } + + /** + * We might auto-grant permissions if any permission of the group is already granted. Hence if + * the group of a granted permission changes we need to revoke it to avoid having permissions of + * the new group auto-granted. + * + * @param newPackage The new package that was installed + * @param oldPackage The old package that was updated + */ + private void revokeRuntimePermissionsIfGroupChangedInternal(@NonNull AndroidPackage newPackage, + @NonNull AndroidPackage oldPackage) { + final int numOldPackagePermissions = ArrayUtils.size(oldPackage.getPermissions()); + final ArrayMap oldPermissionNameToGroupName = + new ArrayMap<>(numOldPackagePermissions); + + for (int i = 0; i < numOldPackagePermissions; i++) { + final ParsedPermission permission = oldPackage.getPermissions().get(i); + + if (permission.getParsedPermissionGroup() != null) { + oldPermissionNameToGroupName.put(permission.getName(), + permission.getParsedPermissionGroup().getName()); + } + } + + final int callingUid = Binder.getCallingUid(); + final int numNewPackagePermissions = ArrayUtils.size(newPackage.getPermissions()); + for (int newPermissionNum = 0; newPermissionNum < numNewPackagePermissions; + newPermissionNum++) { + final ParsedPermission newPermission = + newPackage.getPermissions().get(newPermissionNum); + final int newProtection = ParsedPermissionUtils.getProtection(newPermission); + + if ((newProtection & PermissionInfo.PROTECTION_DANGEROUS) != 0) { + final String permissionName = newPermission.getName(); + final String newPermissionGroupName = + newPermission.getParsedPermissionGroup() == null + ? null : newPermission.getParsedPermissionGroup().getName(); + final String oldPermissionGroupName = oldPermissionNameToGroupName.get( + permissionName); + + if (newPermissionGroupName != null + && !newPermissionGroupName.equals(oldPermissionGroupName)) { + final int[] userIds = mUserManagerInt.getUserIds(); + mPackageManagerInt.forEachPackage(pkg -> { + final String packageName = pkg.getPackageName(); + for (final int userId : userIds) { + final int permissionState = + checkPermission(packageName, permissionName, userId); + if (permissionState == PackageManager.PERMISSION_GRANTED) { + EventLog.writeEvent(0x534e4554, "72710897", + newPackage.getUid(), + "Revoking permission " + permissionName + + " from package " + packageName + + " as the group changed from " + oldPermissionGroupName + + " to " + newPermissionGroupName); + + try { + revokeRuntimePermissionInternal(packageName, permissionName, + false, callingUid, userId, null, + mDefaultPermissionCallback); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Could not revoke " + permissionName + " from " + + packageName, e); + } + } + } + }); + } + } + } + } + + /** + * If permissions are upgraded to runtime, or their owner changes to the system, then any + * granted permissions must be revoked. + * + * @param permissionsToRevoke A list of permission names to revoke + */ + private void revokeRuntimePermissionsIfPermissionDefinitionChangedInternal( + @NonNull List permissionsToRevoke) { + final int[] userIds = mUserManagerInt.getUserIds(); + final int numPermissions = permissionsToRevoke.size(); + final int callingUid = Binder.getCallingUid(); + + for (int permNum = 0; permNum < numPermissions; permNum++) { + final String permName = permissionsToRevoke.get(permNum); + final boolean isInternalPermission; + synchronized (mLock) { + final Permission bp = mRegistry.getPermission(permName); + if (bp == null || !(bp.isInternal() || bp.isRuntime())) { + continue; + } + isInternalPermission = bp.isInternal(); + } + mPackageManagerInt.forEachPackage(pkg -> { + final String packageName = pkg.getPackageName(); + final int appId = pkg.getUid(); + if (appId < Process.FIRST_APPLICATION_UID) { + // do not revoke from system apps + return; + } + for (final int userId : userIds) { + final int permissionState = checkPermission(packageName, permName, + userId); + final int flags = getPermissionFlags(packageName, permName, userId); + final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_POLICY_FIXED + | FLAG_PERMISSION_GRANTED_BY_DEFAULT + | FLAG_PERMISSION_GRANTED_BY_ROLE; + if (permissionState == PackageManager.PERMISSION_GRANTED + && (flags & flagMask) == 0) { + final int uid = UserHandle.getUid(userId, appId); + if (isInternalPermission) { + EventLog.writeEvent(0x534e4554, "195338390", uid, + "Revoking permission " + permName + " from package " + + packageName + " due to definition change"); + } else { + EventLog.writeEvent(0x534e4554, "154505240", uid, + "Revoking permission " + permName + " from package " + + packageName + " due to definition change"); + EventLog.writeEvent(0x534e4554, "168319670", uid, + "Revoking permission " + permName + " from package " + + packageName + " due to definition change"); + } + Slog.e(TAG, "Revoking permission " + permName + " from package " + + packageName + " due to definition change"); + try { + revokeRuntimePermissionInternal(packageName, permName, + false, callingUid, userId, null, mDefaultPermissionCallback); + } catch (Exception e) { + Slog.e(TAG, "Could not revoke " + permName + " from " + + packageName, e); + } + } + } + }); + } + } + + private List addAllPermissionsInternal(@NonNull AndroidPackage pkg) { + final int N = ArrayUtils.size(pkg.getPermissions()); + ArrayList definitionChangedPermissions = new ArrayList<>(); + for (int i=0; i Build.VERSION_CODES.LOLLIPOP_MR1) { + ComponentMutateUtils.setParsedPermissionGroup(p, + mRegistry.getPermissionGroup(p.getGroup())); + // Warn for a permission in an unknown group. + if (DEBUG_PERMISSIONS + && p.getGroup() != null && p.getParsedPermissionGroup() == null) { + Slog.i(TAG, "Permission " + p.getName() + " from package " + + p.getPackageName() + " in an unknown group " + p.getGroup()); + } + } + + permissionInfo = PackageInfoUtils.generatePermissionInfo(p, + PackageManager.GET_META_DATA); + oldPermission = p.isTree() ? mRegistry.getPermissionTree(p.getName()) + : mRegistry.getPermission(p.getName()); + } + // TODO(zhanghai): Maybe we should store whether a permission is owned by system inside + // itself. + final boolean isOverridingSystemPermission = Permission.isOverridingSystemPermission( + oldPermission, permissionInfo, mPackageManagerInt); + synchronized (mLock) { + final Permission permission = Permission.createOrUpdate(oldPermission, + permissionInfo, pkg, mRegistry.getPermissionTrees(), + isOverridingSystemPermission); + if (p.isTree()) { + mRegistry.addPermissionTree(permission); + } else { + mRegistry.addPermission(permission); + } + if (permission.isInstalled()) { + ComponentMutateUtils.setExactFlags(p, + p.getFlags() | PermissionInfo.FLAG_INSTALLED); + } + if (permission.isDefinitionChanged()) { + definitionChangedPermissions.add(p.getName()); + permission.setDefinitionChanged(false); + } + } + } + return definitionChangedPermissions; + } + + private void addAllPermissionGroupsInternal(@NonNull AndroidPackage pkg) { + synchronized (mLock) { + final int N = ArrayUtils.size(pkg.getPermissionGroups()); + StringBuilder r = null; + for (int i = 0; i < N; i++) { + final ParsedPermissionGroup pg = pkg.getPermissionGroups().get(i); + final ParsedPermissionGroup cur = mRegistry.getPermissionGroup(pg.getName()); + final String curPackageName = (cur == null) ? null : cur.getPackageName(); + final boolean isPackageUpdate = pg.getPackageName().equals(curPackageName); + if (cur == null || isPackageUpdate) { + mRegistry.addPermissionGroup(pg); + if (DEBUG_PACKAGE_SCANNING) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + if (isPackageUpdate) { + r.append("UPD:"); + } + r.append(pg.getName()); + } + } else { + Slog.w(TAG, "Permission group " + pg.getName() + " from package " + + pg.getPackageName() + " ignored: original from " + + cur.getPackageName()); + if (DEBUG_PACKAGE_SCANNING) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + r.append("DUP:"); + r.append(pg.getName()); + } + } + } + if (r != null && DEBUG_PACKAGE_SCANNING) { + Log.d(TAG, " Permission Groups: " + r); + } + } + } + + private void removeAllPermissionsInternal(@NonNull AndroidPackage pkg) { + synchronized (mLock) { + int n = ArrayUtils.size(pkg.getPermissions()); + StringBuilder r = null; + for (int i = 0; i < n; i++) { + ParsedPermission p = pkg.getPermissions().get(i); + Permission bp = mRegistry.getPermission(p.getName()); + if (bp == null) { + bp = mRegistry.getPermissionTree(p.getName()); + } + if (bp != null && bp.isPermission(p)) { + bp.setPermissionInfo(null); + if (DEBUG_REMOVE) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + r.append(p.getName()); + } + } + if (ParsedPermissionUtils.isAppOp(p)) { + // TODO(zhanghai): Should we just remove the entry for this permission directly? + mRegistry.removeAppOpPermissionPackage(p.getName(), pkg.getPackageName()); + } + } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); + } + + n = pkg.getRequestedPermissions().size(); + r = null; + for (int i = 0; i < n; i++) { + final String permissionName = pkg.getRequestedPermissions().get(i); + final Permission permission = mRegistry.getPermission(permissionName); + if (permission != null && permission.isAppOp()) { + mRegistry.removeAppOpPermissionPackage(permissionName, + pkg.getPackageName()); + } + } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); + } + } + } + + @Override + public void onUserRemoved(@UserIdInt int userId) { + Preconditions.checkArgumentNonNegative(userId, "userId"); + synchronized (mLock) { + mState.removeUserState(userId); + } + } + + @NonNull + private Set getGrantedPermissionsInternal(@NonNull String packageName, + @UserIdInt int userId) { + final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(packageName); + if (ps == null) { + return Collections.emptySet(); + } + + synchronized (mLock) { + final UidPermissionState uidState = getUidStateLocked(ps, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); + return Collections.emptySet(); + } + if (!ps.getUserStateOrDefault(userId).isInstantApp()) { + return uidState.getGrantedPermissions(); + } else { + // Install permission state is shared among all users, but instant app state is + // per-user, so we can only filter it here unless we make install permission state + // per-user as well. + final Set instantPermissions = + new ArraySet<>(uidState.getGrantedPermissions()); + instantPermissions.removeIf(permissionName -> { + Permission permission = mRegistry.getPermission(permissionName); + if (permission == null) { + return true; + } + if (!permission.isInstant()) { + EventLog.writeEvent(0x534e4554, "140256621", UserHandle.getUid(userId, + ps.getAppId()), permissionName); + return true; + } + return false; + }); + return instantPermissions; + } + } + } + + @NonNull + private int[] getPermissionGidsInternal(@NonNull String permissionName, @UserIdInt int userId) { + synchronized (mLock) { + Permission permission = mRegistry.getPermission(permissionName); + if (permission == null) { + return EmptyArray.INT; + } + return permission.computeGids(userId); + } + } + + /** + * Restore the permission state for a package. + * + *

    + *
  • During boot the state gets restored from the disk
  • + *
  • During app update the state gets restored from the last version of the app
  • + *
+ * + * @param pkg the package the permissions belong to + * @param replace if the package is getting replaced (this might change the requested + * permissions of this package) + * @param packageOfInterest If this is the name of {@code pkg} add extra logging + * @param callback Result call back + * @param filterUserId If not {@link UserHandle.USER_ALL}, only restore the permission state for + * this particular user + */ + private void restorePermissionState(@NonNull AndroidPackage pkg, boolean replace, + @Nullable String packageOfInterest, @Nullable PermissionCallback callback, + @UserIdInt int filterUserId) { + // IMPORTANT: There are two types of permissions: install and runtime. + // Install time permissions are granted when the app is installed to + // all device users and users added in the future. Runtime permissions + // are granted at runtime explicitly to specific users. Normal and signature + // protected permissions are install time permissions. Dangerous permissions + // are install permissions if the app's target SDK is Lollipop MR1 or older, + // otherwise they are runtime permissions. This function does not manage + // runtime permissions except for the case an app targeting Lollipop MR1 + // being upgraded to target a newer SDK, in which case dangerous permissions + // are transformed from install time to runtime ones. + + final PackageStateInternal ps = + mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); + if (ps == null) { + return; + } + + final int[] userIds = filterUserId == UserHandle.USER_ALL ? getAllUserIds() + : new int[] { filterUserId }; + + boolean runtimePermissionsRevoked = false; + int[] updatedUserIds = EMPTY_INT_ARRAY; + + ArraySet isPrivilegedPermissionAllowlisted = null; + ArraySet shouldGrantSignaturePermission = null; + ArraySet shouldGrantInternalPermission = null; + ArraySet shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>(); + final List requestedPermissions = pkg.getRequestedPermissions(); + final int requestedPermissionsSize = requestedPermissions.size(); + for (int i = 0; i < requestedPermissionsSize; i++) { + final String permissionName = pkg.getRequestedPermissions().get(i); + + final Permission permission; + synchronized (mLock) { + permission = mRegistry.getPermission(permissionName); + } + if (permission == null) { + continue; + } + if (permission.isPrivileged() + && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) { + if (isPrivilegedPermissionAllowlisted == null) { + isPrivilegedPermissionAllowlisted = new ArraySet<>(); + } + isPrivilegedPermissionAllowlisted.add(permissionName); + } + if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission) + || shouldGrantPermissionByProtectionFlags(pkg, ps, permission, + shouldGrantPrivilegedPermissionIfWasGranted))) { + if (shouldGrantSignaturePermission == null) { + shouldGrantSignaturePermission = new ArraySet<>(); + } + shouldGrantSignaturePermission.add(permissionName); + } + if (permission.isInternal() + && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, + shouldGrantPrivilegedPermissionIfWasGranted)) { + if (shouldGrantInternalPermission == null) { + shouldGrantInternalPermission = new ArraySet<>(); + } + shouldGrantInternalPermission.add(permissionName); + } + } + + final SparseBooleanArray isPermissionPolicyInitialized = new SparseBooleanArray(); + if (mPermissionPolicyInternal != null) { + for (final int userId : userIds) { + if (mPermissionPolicyInternal.isInitialized(userId)) { + isPermissionPolicyInitialized.put(userId, true); + } + } + } + + synchronized (mLock) { + for (final int userId : userIds) { + final UserPermissionState userState = mState.getOrCreateUserState(userId); + final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId()); + + if (uidState.isMissing()) { + Collection uidRequestedPermissions; + int targetSdkVersion; + if (ps.getSharedUser() == null) { + uidRequestedPermissions = pkg.getRequestedPermissions(); + targetSdkVersion = pkg.getTargetSdkVersion(); + } else { + uidRequestedPermissions = new ArraySet<>(); + targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; + List packages = ps.getSharedUser().getPackages(); + int packagesSize = packages.size(); + for (int i = 0; i < packagesSize; i++) { + AndroidPackage sharedUserPackage = packages.get(i); + uidRequestedPermissions.addAll( + sharedUserPackage.getRequestedPermissions()); + targetSdkVersion = Math.min(targetSdkVersion, + sharedUserPackage.getTargetSdkVersion()); + } + } + + for (String permissionName : uidRequestedPermissions) { + Permission permission = mRegistry.getPermission(permissionName); + if (permission == null) { + continue; + } + if (Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME) + && permission.isRuntime() && !permission.isRemoved()) { + if (permission.isHardOrSoftRestricted() + || permission.isImmutablyRestricted()) { + uidState.updatePermissionFlags(permission, + FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, + FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT); + } + if (targetSdkVersion < Build.VERSION_CODES.M) { + uidState.updatePermissionFlags(permission, + PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED + | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, + PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED + | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT); + uidState.grantPermission(permission); + } + } + } + + uidState.setMissing(false); + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + } + + UidPermissionState origState = uidState; + + boolean changedInstallPermission = false; + + if (replace) { + userState.setInstallPermissionsFixed(ps.getPackageName(), false); + if (ps.getSharedUser() == null) { + origState = new UidPermissionState(uidState); + uidState.reset(); + } else { + // We need to know only about runtime permission changes since the + // calling code always writes the install permissions state but + // the runtime ones are written only if changed. The only cases of + // changed runtime permissions here are promotion of an install to + // runtime and revocation of a runtime from a shared user. + if (revokeUnusedSharedUserPermissionsLocked( + ps.getSharedUser().getPackages(), uidState)) { + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + runtimePermissionsRevoked = true; + } + } + } + + ArraySet newImplicitPermissions = new ArraySet<>(); + final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")"; + + for (int i = 0; i < requestedPermissionsSize; i++) { + final String permName = requestedPermissions.get(i); + + final Permission bp = mRegistry.getPermission(permName); + final boolean appSupportsRuntimePermissions = + pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M; + String legacyActivityRecognitionPermission = null; + + if (DEBUG_INSTALL && bp != null) { + Log.i(TAG, "Package " + friendlyName + + " checking " + permName + ": " + bp); + } + + // TODO(zhanghai): I don't think we need to check source package setting if + // permission is present, because otherwise the permission should have been + // removed. + if (bp == null /*|| getSourcePackageSetting(bp) == null*/) { + if (packageOfInterest == null || packageOfInterest.equals( + pkg.getPackageName())) { + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Unknown permission " + permName + + " in package " + friendlyName); + } + } + continue; + } + + // Cache newImplicitPermissions before modifing permissionsState as for the + // shared uids the original and new state are the same object + if (!origState.hasPermissionState(permName) + && (pkg.getImplicitPermissions().contains(permName) + || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) { + if (pkg.getImplicitPermissions().contains(permName)) { + // If permName is an implicit permission, try to auto-grant + newImplicitPermissions.add(permName); + + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, permName + " is newly added for " + friendlyName); + } + } else { + // Special case for Activity Recognition permission. Even if AR + // permission is not an implicit permission we want to add it to the + // list (try to auto-grant it) if the app was installed on a device + // before AR permission was split, regardless of if the app now requests + // the new AR permission or has updated its target SDK and AR is no + // longer implicit to it. This is a compatibility workaround for apps + // when AR permission was split in Q. + // TODO(zhanghai): This calls into SystemConfig, which generally + // shouldn't cause deadlock, but maybe we should keep a cache of the + // split permission list and just eliminate the possibility. + final List permissionList = + getSplitPermissionInfos(); + int numSplitPerms = permissionList.size(); + for (int splitPermNum = 0; splitPermNum < numSplitPerms; + splitPermNum++) { + PermissionManager.SplitPermissionInfo sp = permissionList.get( + splitPermNum); + String splitPermName = sp.getSplitPermission(); + if (sp.getNewPermissions().contains(permName) + && origState.isPermissionGranted(splitPermName)) { + legacyActivityRecognitionPermission = splitPermName; + newImplicitPermissions.add(permName); + + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, permName + " is newly added for " + + friendlyName); + } + break; + } + } + } + } + + // TODO(b/140256621): The package instant app method has been removed + // as part of work in b/135203078, so this has been commented out in the + // meantime + // Limit ephemeral apps to ephemeral allowed permissions. + // if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) { + // if (DEBUG_PERMISSIONS) { + // Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + // + " for package " + pkg.getPackageName()); + // } + // continue; + // } + + if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) { + if (DEBUG_PERMISSIONS) { + Log.i(TAG, "Denying runtime-only permission " + bp.getName() + + " for package " + friendlyName); + } + continue; + } + + final String perm = bp.getName(); + + // Keep track of app op permissions. + if (bp.isAppOp()) { + mRegistry.addAppOpPermissionPackage(perm, pkg.getPackageName()); + } + + boolean shouldGrantNormalPermission = true; + if (bp.isNormal() && !origState.isPermissionGranted(perm)) { + // If this is an existing, non-system package, then + // we can't add any new permissions to it. Runtime + // permissions can be added any time - they are dynamic. + if (!ps.isSystem() && userState.areInstallPermissionsFixed( + ps.getPackageName())) { + // Except... if this is a permission that was added + // to the platform (note: need to only do this when + // updating the platform). + if (!isCompatPlatformPermissionForPackage(perm, pkg)) { + shouldGrantNormalPermission = false; + } + } + } + + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Considering granting permission " + perm + " to package " + + pkg.getPackageName()); + } + + if ((bp.isNormal() && shouldGrantNormalPermission) + || (bp.isSignature() + && (!bp.isPrivileged() || CollectionUtils.contains( + isPrivilegedPermissionAllowlisted, permName)) + && (CollectionUtils.contains(shouldGrantSignaturePermission, + permName) + || (((bp.isPrivileged() && CollectionUtils.contains( + shouldGrantPrivilegedPermissionIfWasGranted, + permName)) || bp.isDevelopment() || bp.isRole()) + && origState.isPermissionGranted(permName)))) + || (bp.isInternal() + && (!bp.isPrivileged() || CollectionUtils.contains( + isPrivilegedPermissionAllowlisted, permName)) + && (CollectionUtils.contains(shouldGrantInternalPermission, + permName) + || (((bp.isPrivileged() && CollectionUtils.contains( + shouldGrantPrivilegedPermissionIfWasGranted, + permName)) || bp.isDevelopment() || bp.isRole()) + && origState.isPermissionGranted(permName))))) { + // Grant an install permission. + if (uidState.grantPermission(bp)) { + changedInstallPermission = true; + } + } else if (bp.isRuntime()) { + boolean hardRestricted = bp.isHardRestricted(); + boolean softRestricted = bp.isSoftRestricted(); + + // If permission policy is not ready we don't deal with restricted + // permissions as the policy may allowlist some permissions. Once + // the policy is initialized we would re-evaluate permissions. + final boolean permissionPolicyInitialized = + isPermissionPolicyInitialized.get(userId); + + PermissionState origPermState = origState.getPermissionState(perm); + int flags = origPermState != null ? origPermState.getFlags() : 0; + + boolean wasChanged = false; + + boolean restrictionExempt = + (origState.getPermissionFlags(bp.getName()) + & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; + boolean restrictionApplied = (origState.getPermissionFlags( + bp.getName()) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; + + if (appSupportsRuntimePermissions) { + // If hard restricted we don't allow holding it + if (permissionPolicyInitialized && hardRestricted) { + if (!restrictionExempt) { + if (origPermState != null && origPermState.isGranted() + && uidState.revokePermission(bp)) { + wasChanged = true; + } + if (!restrictionApplied) { + flags |= FLAG_PERMISSION_APPLY_RESTRICTION; + wasChanged = true; + } + } + // If soft restricted we allow holding in a restricted form + } else if (permissionPolicyInitialized && softRestricted) { + // Regardless if granted set the restriction flag as it + // may affect app treatment based on this permission. + if (!restrictionExempt && !restrictionApplied) { + flags |= FLAG_PERMISSION_APPLY_RESTRICTION; + wasChanged = true; + } + } + + // Remove review flag as it is not necessary anymore + if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { + flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED; + wasChanged = true; + } + + if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0 + && !isPermissionSplitFromNonRuntime(permName, + pkg.getTargetSdkVersion())) { + flags &= ~FLAG_PERMISSION_REVOKED_COMPAT; + wasChanged = true; + // Hard restricted permissions cannot be held. + } else if (!permissionPolicyInitialized + || (!hardRestricted || restrictionExempt)) { + if ((origPermState != null && origPermState.isGranted()) + || legacyActivityRecognitionPermission != null) { + if (!uidState.grantPermission(bp)) { + wasChanged = true; + } + } + } + } else { + if (origPermState == null) { + // New permission + if (PLATFORM_PACKAGE_NAME.equals( + bp.getPackageName())) { + if (!bp.isRemoved()) { + flags |= FLAG_PERMISSION_REVIEW_REQUIRED + | FLAG_PERMISSION_REVOKED_COMPAT; + wasChanged = true; + } + } + } + + if (!uidState.isPermissionGranted(bp.getName()) + && uidState.grantPermission(bp)) { + wasChanged = true; + } + + // If legacy app always grant the permission but if restricted + // and not exempt take a note a restriction should be applied. + if (permissionPolicyInitialized + && (hardRestricted || softRestricted) + && !restrictionExempt && !restrictionApplied) { + flags |= FLAG_PERMISSION_APPLY_RESTRICTION; + wasChanged = true; + } + } + + // If unrestricted or restriction exempt, don't apply restriction. + if (permissionPolicyInitialized) { + if (!(hardRestricted || softRestricted) || restrictionExempt) { + if (restrictionApplied) { + flags &= ~FLAG_PERMISSION_APPLY_RESTRICTION; + // Dropping restriction on a legacy app implies a review + if (!appSupportsRuntimePermissions) { + flags |= FLAG_PERMISSION_REVIEW_REQUIRED; + } + wasChanged = true; + } + } + } + + if (wasChanged) { + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + } + + uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, + flags); + } else { + if (DEBUG_PERMISSIONS) { + boolean wasGranted = uidState.isPermissionGranted(bp.getName()); + if (wasGranted || bp.isAppOp()) { + Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting") + + " permission " + perm + + " from package " + friendlyName + + " (protectionLevel=" + bp.getProtectionLevel() + + " flags=0x" + + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, + ps)) + + ")"); + } + } + if (uidState.removePermissionState(bp.getName())) { + changedInstallPermission = true; + } + } + } + + if ((changedInstallPermission || replace) + && !userState.areInstallPermissionsFixed(ps.getPackageName()) + && !ps.isSystem() || ps.getTransientState().isUpdatedSystemApp()) { + // This is the first that we have heard about this package, so the + // permissions we have now selected are fixed until explicitly + // changed. + userState.setInstallPermissionsFixed(ps.getPackageName(), true); + } + + updatedUserIds = revokePermissionsNoLongerImplicitLocked(uidState, pkg, + userId, updatedUserIds); + updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origState, + uidState, pkg, newImplicitPermissions, userId, updatedUserIds); + } + } + + updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds, + updatedUserIds); + + // TODO: Kill UIDs whose GIDs or runtime permissions changed. This might be more important + // for shared users. + // Persist the runtime permissions state for users with changes. If permissions + // were revoked because no app in the shared user declares them we have to + // write synchronously to avoid losing runtime permissions state. + if (callback != null) { + callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked); + } + + for (int userId : updatedUserIds) { + notifyRuntimePermissionStateChanged(pkg.getPackageName(), userId); + } + } + + /** + * Returns all relevant user ids. This list include the current set of created user ids as well + * as pre-created user ids. + * @return user ids for created users and pre-created users + */ + private int[] getAllUserIds() { + return UserManagerService.getInstance().getUserIdsIncludingPreCreated(); + } + + /** + * Revoke permissions that are not implicit anymore and that have + * {@link PackageManager#FLAG_PERMISSION_REVOKE_WHEN_REQUESTED} set. + * + * @param ps The state of the permissions of the package + * @param pkg The package that is currently looked at + * @param userIds All user IDs in the system, must be passed in because this method is locked + * @param updatedUserIds a list of user ids that needs to be amended if the permission state + * for a user is changed. + * + * @return The updated value of the {@code updatedUserIds} parameter + */ + @NonNull + @GuardedBy("mLock") + private int[] revokePermissionsNoLongerImplicitLocked(@NonNull UidPermissionState ps, + @NonNull AndroidPackage pkg, int userId, @NonNull int[] updatedUserIds) { + String pkgName = pkg.getPackageName(); + boolean supportsRuntimePermissions = pkg.getTargetSdkVersion() + >= Build.VERSION_CODES.M; + + for (String permission : ps.getGrantedPermissions()) { + if (pkg.getRequestedPermissions().contains(permission) + && !pkg.getImplicitPermissions().contains(permission)) { + Permission bp = mRegistry.getPermission(permission); + if (bp != null && bp.isRuntime()) { + int flags = ps.getPermissionFlags(permission); + if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { + int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; + + // We're willing to preserve an implicit "Nearby devices" + // permission grant if this app was already able to interact + // with nearby devices via background location access + boolean preserveGrant = false; + if (ArrayUtils.contains(NEARBY_DEVICES_PERMISSIONS, permission) + && ps.isPermissionGranted( + android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) + && (ps.getPermissionFlags( + android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) + & (FLAG_PERMISSION_REVOKE_WHEN_REQUESTED + | FLAG_PERMISSION_REVOKED_COMPAT)) == 0) { + preserveGrant = true; + } + + if ((flags & BLOCKING_PERMISSION_FLAGS) == 0 + && supportsRuntimePermissions + && !preserveGrant) { + if (ps.revokePermission(bp)) { + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Revoking runtime permission " + + permission + " for " + pkgName + + " as it is now requested"); + } + } + + flagsToRemove |= USER_PERMISSION_FLAGS; + } + + ps.updatePermissionFlags(bp, flagsToRemove, 0); + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + } + } + } + } + + return updatedUserIds; + } + + /** + * {@code newPerm} is newly added; Inherit the state from {@code sourcePerms}. + * + *

A single new permission can be split off from several source permissions. In this case + * the most leniant state is inherited. + * + *

Warning: This does not handle foreground / background permissions + * + * @param sourcePerms The permissions to inherit from + * @param newPerm The permission to inherit to + * @param ps The permission state of the package + * @param pkg The package requesting the permissions + */ + @GuardedBy("mLock") + private void inheritPermissionStateToNewImplicitPermissionLocked( + @NonNull ArraySet sourcePerms, @NonNull String newPerm, + @NonNull UidPermissionState ps, @NonNull AndroidPackage pkg) { + String pkgName = pkg.getPackageName(); + boolean isGranted = false; + int flags = 0; + + int numSourcePerm = sourcePerms.size(); + for (int i = 0; i < numSourcePerm; i++) { + String sourcePerm = sourcePerms.valueAt(i); + if (ps.isPermissionGranted(sourcePerm)) { + if (!isGranted) { + flags = 0; + } + + isGranted = true; + flags |= ps.getPermissionFlags(sourcePerm); + } else { + if (!isGranted) { + flags |= ps.getPermissionFlags(sourcePerm); + } + } + } + + if (isGranted) { + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, newPerm + " inherits runtime perm grant from " + sourcePerms + + " for " + pkgName); + } + + ps.grantPermission(mRegistry.getPermission(newPerm)); + } + + // Add permission flags + ps.updatePermissionFlags(mRegistry.getPermission(newPerm), flags, flags); + } + + /** + * When the app has requested legacy storage we might need to update + * {@link android.app.AppOpsManager#OP_LEGACY_STORAGE}. Hence force an update in + * {@link com.android.server.policy.PermissionPolicyService#synchronizePackagePermissionsAndAppOpsForUser(Context, String, int)} + * + * @param pkg The package for which the permissions are updated + * @param replace If the app is being replaced + * @param userIds All user IDs in the system, must be passed in because this method is locked + * @param updatedUserIds The ids of the users that already changed. + * + * @return The ids of the users that are changed + */ + private @NonNull int[] checkIfLegacyStorageOpsNeedToBeUpdated(@NonNull AndroidPackage pkg, + boolean replace, @NonNull int[] userIds, @NonNull int[] updatedUserIds) { + if (replace && pkg.isRequestLegacyExternalStorage() && ( + pkg.getRequestedPermissions().contains(READ_EXTERNAL_STORAGE) + || pkg.getRequestedPermissions().contains(WRITE_EXTERNAL_STORAGE))) { + return userIds.clone(); + } + + return updatedUserIds; + } + + /** + * Set the state of a implicit permission that is seen for the first time. + * + * @param origPs The permission state of the package before the split + * @param ps The new permission state + * @param pkg The package the permission belongs to + * @param userId The user ID + * @param updatedUserIds List of users for which the permission state has already been changed + * + * @return List of users for which the permission state has been changed + */ + @NonNull + @GuardedBy("mLock") + private int[] setInitialGrantForNewImplicitPermissionsLocked( + @NonNull UidPermissionState origPs, @NonNull UidPermissionState ps, + @NonNull AndroidPackage pkg, @NonNull ArraySet newImplicitPermissions, + @UserIdInt int userId, @NonNull int[] updatedUserIds) { + String pkgName = pkg.getPackageName(); + ArrayMap> newToSplitPerms = new ArrayMap<>(); + + final List permissionList = + getSplitPermissionInfos(); + int numSplitPerms = permissionList.size(); + for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { + PermissionManager.SplitPermissionInfo spi = permissionList.get(splitPermNum); + + List newPerms = spi.getNewPermissions(); + int numNewPerms = newPerms.size(); + for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) { + String newPerm = newPerms.get(newPermNum); + + ArraySet splitPerms = newToSplitPerms.get(newPerm); + if (splitPerms == null) { + splitPerms = new ArraySet<>(); + newToSplitPerms.put(newPerm, splitPerms); + } + + splitPerms.add(spi.getSplitPermission()); + } + } + + int numNewImplicitPerms = newImplicitPermissions.size(); + for (int newImplicitPermNum = 0; newImplicitPermNum < numNewImplicitPerms; + newImplicitPermNum++) { + String newPerm = newImplicitPermissions.valueAt(newImplicitPermNum); + ArraySet sourcePerms = newToSplitPerms.get(newPerm); + + if (sourcePerms != null) { + Permission bp = mRegistry.getPermission(newPerm); + if (bp == null) { + throw new IllegalStateException("Unknown new permission in split permission: " + + newPerm); + } + if (bp.isRuntime()) { + + if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) { + ps.updatePermissionFlags(bp, + FLAG_PERMISSION_REVOKE_WHEN_REQUESTED, + FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); + } + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + + if (!origPs.hasPermissionState(sourcePerms)) { + boolean inheritsFromInstallPerm = false; + for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size(); + sourcePermNum++) { + final String sourcePerm = sourcePerms.valueAt(sourcePermNum); + Permission sourceBp = mRegistry.getPermission(sourcePerm); + if (sourceBp == null) { + throw new IllegalStateException("Unknown source permission in split" + + " permission: " + sourcePerm); + } + if (!sourceBp.isRuntime()) { + inheritsFromInstallPerm = true; + break; + } + } + + if (!inheritsFromInstallPerm) { + // Both permissions are new so nothing to inherit. + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms + + " for " + pkgName + " as split permission is also new"); + } + continue; + } + } + + // Inherit from new install or existing runtime permissions + inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms, newPerm, ps, + pkg); + } + } else if (IMPLICIT_GRANTED_PERMISSIONS.contains(newPerm) + && !origPs.hasPermissionState(newPerm)) { + Permission bp = mRegistry.getPermission(newPerm); + if (bp == null) { + throw new IllegalStateException("Unknown new permission " + newPerm); + } + if ((ps.getPermissionState(newPerm).getFlags() + & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { + // No need to grant if review is required + continue; + } + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + ps.updatePermissionFlags(bp, + FLAG_PERMISSION_REVOKE_WHEN_REQUESTED, + FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); + ps.grantPermission(bp); + } + } + + return updatedUserIds; + } + + @NonNull + @Override + public List getSplitPermissions() { + return PermissionManager.splitPermissionInfoListToParcelableList(getSplitPermissionInfos()); + } + + @NonNull + private List getSplitPermissionInfos() { + return SystemConfig.getInstance().getSplitPermissions(); + } + + private static boolean isCompatPlatformPermissionForPackage(String perm, AndroidPackage pkg) { + boolean allowed = false; + for (int i = 0, size = CompatibilityPermissionInfo.COMPAT_PERMS.length; i < size; i++) { + final CompatibilityPermissionInfo info = CompatibilityPermissionInfo.COMPAT_PERMS[i]; + if (info.getName().equals(perm) + && pkg.getTargetSdkVersion() < info.getSdkVersion()) { + allowed = true; + Log.i(TAG, "Auto-granting " + perm + " to old pkg " + + pkg.getPackageName()); + break; + } + } + return allowed; + } + + private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg, + @NonNull PackageStateInternal packageSetting, @NonNull Permission permission) { + if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) { + return true; + } + final String packageName = pkg.getPackageName(); + if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) { + return true; + } + if (!pkg.isPrivileged()) { + return true; + } + if (!mPrivilegedPermissionAllowlistSourcePackageNames + .contains(permission.getPackageName())) { + return true; + } + final String permissionName = permission.getName(); + final ApexManager apexManager = ApexManager.getInstance(); + final String containingApexPackageName = + apexManager.getActiveApexPackageNameContainingPackage(packageName); + if (isInSystemConfigPrivAppPermissions(pkg, permissionName, + containingApexPackageName)) { + return true; + } + if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName, + containingApexPackageName)) { + return false; + } + // Updated system apps do not need to be allowlisted + if (packageSetting.getTransientState().isUpdatedSystemApp()) { + // Let shouldGrantPermissionByProtectionFlags() decide whether the privileged permission + // can be granted, because an updated system app may be in a shared UID, and in case a + // new privileged permission is requested by the updated system app but not the factory + // app, although this app and permission combination isn't in the allowlist and can't + // get the permission this way, other apps in the shared UID may still get it. A proper + // fix for this would be to perform the reconciliation by UID, but for now let's keep + // the old workaround working, which is to keep granted privileged permissions still + // granted. + return true; + } + // Only enforce the allowlist on boot + if (!mSystemReady) { + final boolean isInUpdatedApex = containingApexPackageName != null + && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName, + MATCH_ACTIVE_PACKAGE)); + // Apps that are in updated apexs' do not need to be allowlisted + if (!isInUpdatedApex) { + Slog.w(TAG, "Privileged permission " + permissionName + " for package " + + packageName + " (" + pkg.getPath() + + ") not in privapp-permissions allowlist"); + if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { + synchronized (mLock) { + if (mPrivappPermissionsViolations == null) { + mPrivappPermissionsViolations = new ArraySet<>(); + } + mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): " + + permissionName); + } + } + } + } + return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE; + } + + private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg, + @NonNull String permission, String containingApexPackageName) { + final SystemConfig systemConfig = SystemConfig.getInstance(); + final Set permissions; + if (pkg.isVendor()) { + permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName()); + } else if (pkg.isProduct()) { + permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName()); + } else if (pkg.isSystemExt()) { + permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName()); + } else if (containingApexPackageName != null) { + final Set privAppPermissions = systemConfig.getPrivAppPermissions( + pkg.getPackageName()); + final Set apexPermissions = systemConfig.getApexPrivAppPermissions( + containingApexPackageName, pkg.getPackageName()); + if (privAppPermissions != null) { + // TODO(andreionea): Remove check as soon as all apk-in-apex + // permission allowlists are migrated. + Slog.w(TAG, "Package " + pkg.getPackageName() + " is an APK in APEX," + + " but has permission allowlist on the system image. Please bundle the" + + " allowlist in the " + containingApexPackageName + " APEX instead."); + if (apexPermissions != null) { + permissions = new ArraySet<>(privAppPermissions); + permissions.addAll(apexPermissions); + } else { + permissions = privAppPermissions; + } + } else { + permissions = apexPermissions; + } + } else { + permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName()); + } + return CollectionUtils.contains(permissions, permission); + } + + private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg, + @NonNull String permission, String containingApexPackageName) { + final SystemConfig systemConfig = SystemConfig.getInstance(); + final Set permissions; + if (pkg.isVendor()) { + permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName()); + } else if (pkg.isProduct()) { + permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName()); + } else if (pkg.isSystemExt()) { + permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName()); + } else if (containingApexPackageName != null) { + permissions = systemConfig.getApexPrivAppDenyPermissions(containingApexPackageName, + pkg.getPackageName()); + } else { + permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName()); + } + return CollectionUtils.contains(permissions, permission); + } + + private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg, + @NonNull Permission bp) { + // expect single system package + String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM)); + final AndroidPackage systemPackage = + mPackageManagerInt.getPackage(systemPackageName); + // check if the package is allow to use this signature permission. A package is allowed to + // use a signature permission if: + // - it has the same set of signing certificates as the source package + // - or its signing certificate was rotated from the source package's certificate + // - or its signing certificate is a previous signing certificate of the defining + // package, and the defining package still trusts the old certificate for permissions + // - or it shares a common signing certificate in its lineage with the defining package, + // and the defining package still trusts the old certificate for permissions + // - or it shares the above relationships with the system package + final SigningDetails sourceSigningDetails = + getSourcePackageSigningDetails(bp); + return sourceSigningDetails.hasCommonSignerWithCapability( + pkg.getSigningDetails(), + SigningDetails.CertCapabilities.PERMISSION) + || pkg.getSigningDetails().hasAncestorOrSelf(systemPackage.getSigningDetails()) + || systemPackage.getSigningDetails().checkCapability( + pkg.getSigningDetails(), + SigningDetails.CertCapabilities.PERMISSION); + } + + private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg, + @NonNull PackageStateInternal pkgSetting, @NonNull Permission bp, + @NonNull ArraySet shouldGrantPrivilegedPermissionIfWasGranted) { + boolean allowed = false; + final boolean isPrivilegedPermission = bp.isPrivileged(); + final boolean isOemPermission = bp.isOem(); + if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) { + final String permissionName = bp.getName(); + // For updated system applications, a privileged/oem permission + // is granted only if it had been defined by the original application. + if (pkgSetting.getTransientState().isUpdatedSystemApp()) { + final PackageSetting disabledPs = mPackageManagerInt + .getDisabledSystemPackage(pkg.getPackageName()); + final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.getPkg(); + if (disabledPkg != null + && ((isPrivilegedPermission && disabledPkg.isPrivileged()) + || (isOemPermission && canGrantOemPermission(disabledPkg, + permissionName)))) { + if (disabledPkg.getRequestedPermissions().contains(permissionName)) { + allowed = true; + } else { + // If the original was granted this permission, we take + // that grant decision as read and propagate it to the + // update. + shouldGrantPrivilegedPermissionIfWasGranted.add(permissionName); + } + } + } else { + allowed = (isPrivilegedPermission && pkg.isPrivileged()) + || (isOemPermission && canGrantOemPermission(pkg, permissionName)); + } + // In any case, don't grant a privileged permission to privileged vendor apps, if + // the permission's protectionLevel does not have the extra 'vendorPrivileged' + // flag. + if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) { + Slog.w(TAG, "Permission " + permissionName + + " cannot be granted to privileged vendor apk " + pkg.getPackageName() + + " because it isn't a 'vendorPrivileged' permission."); + allowed = false; + } + } + if (!allowed && bp.isPre23() && pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { + // If this was a previously normal/dangerous permission that got moved + // to a system permission as part of the runtime permission redesign, then + // we still want to blindly grant it to old apps. + allowed = true; + } + // TODO (moltmann): The installer now shares the platforms signature. Hence it does not + // need a separate flag anymore. Hence we need to check which + // permissions are needed by the permission controller + if (!allowed && bp.isInstaller() + && (ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM), + pkg.getPackageName()) || ArrayUtils.contains( + mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER, + UserHandle.USER_SYSTEM), pkg.getPackageName()))) { + // If this permission is to be granted to the system installer and + // this app is an installer, then it gets the permission. + allowed = true; + } + if (!allowed && bp.isVerifier() + && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM), + pkg.getPackageName())) { + // If this permission is to be granted to the system verifier and + // this app is a verifier, then it gets the permission. + allowed = true; + } + if (!allowed && bp.isPreInstalled() + && pkg.isSystem()) { + // Any pre-installed system app is allowed to get this permission. + allowed = true; + } + if (!allowed && bp.isKnownSigner()) { + // If the permission is to be granted to a known signer then check if any of this + // app's signing certificates are in the trusted certificate digest Set. + allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts()); + } + // Deferred to be checked under permission data lock inside restorePermissionState(). + //if (!allowed && bp.isDevelopment()) { + // // For development permissions, a development permission + // // is granted only if it was already granted. + // allowed = origPermissions.isPermissionGranted(permissionName); + //} + if (!allowed && bp.isSetup() + && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM), + pkg.getPackageName())) { + // If this permission is to be granted to the system setup wizard and + // this app is a setup wizard, then it gets the permission. + allowed = true; + } + if (!allowed && bp.isSystemTextClassifier() + && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER, + UserHandle.USER_SYSTEM), pkg.getPackageName())) { + // Special permissions for the system default text classifier. + allowed = true; + } + if (!allowed && bp.isConfigurator() + && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_CONFIGURATOR, + UserHandle.USER_SYSTEM), pkg.getPackageName())) { + // Special permissions for the device configurator. + allowed = true; + } + if (!allowed && bp.isIncidentReportApprover() + && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER, + UserHandle.USER_SYSTEM), pkg.getPackageName())) { + // If this permission is to be granted to the incident report approver and + // this app is the incident report approver, then it gets the permission. + allowed = true; + } + if (!allowed && bp.isAppPredictor() + && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM), + pkg.getPackageName())) { + // Special permissions for the system app predictor. + allowed = true; + } + if (!allowed && bp.isCompanion() + && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_COMPANION, UserHandle.USER_SYSTEM), + pkg.getPackageName())) { + // Special permissions for the system companion device manager. + allowed = true; + } + if (!allowed && bp.isRetailDemo() + && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM), + pkg.getPackageName()) && isProfileOwner(pkg.getUid())) { + // Special permission granted only to the OEM specified retail demo app + allowed = true; + } + if (!allowed && bp.isRecents() + && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames( + PackageManagerInternal.PACKAGE_RECENTS, UserHandle.USER_SYSTEM), + pkg.getPackageName())) { + // Special permission for the recents app. + allowed = true; + } + return allowed; + } + + @NonNull + private SigningDetails getSourcePackageSigningDetails( + @NonNull Permission bp) { + final PackageStateInternal ps = getSourcePackageSetting(bp); + if (ps == null) { + return SigningDetails.UNKNOWN; + } + return ps.getSigningDetails(); + } + + @Nullable + private PackageStateInternal getSourcePackageSetting(@NonNull Permission bp) { + final String sourcePackageName = bp.getPackageName(); + return mPackageManagerInt.getPackageStateInternal(sourcePackageName); + } + + private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) { + if (!pkg.isOem()) { + return false; + } + // all oem permissions must explicitly be granted or denied + final Boolean granted = + SystemConfig.getInstance().getOemPermissions(pkg.getPackageName()).get(permission); + if (granted == null) { + throw new IllegalStateException("OEM permission" + permission + " requested by package " + + pkg.getPackageName() + " must be explicitly declared granted or not"); + } + return Boolean.TRUE == granted; + } + + private static boolean isProfileOwner(int uid) { + DevicePolicyManagerInternal dpmInternal = + LocalServices.getService(DevicePolicyManagerInternal.class); + //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode. + if (dpmInternal != null) { + return dpmInternal.isActiveProfileOwner(uid) || dpmInternal.isActiveDeviceOwner(uid); + } + return false; + } + + private boolean isPermissionsReviewRequiredInternal(@NonNull String packageName, + @UserIdInt int userId) { + final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + if (pkg == null) { + return false; + } + + // Permission review applies only to apps not supporting the new permission model. + if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) { + return false; + } + + // Legacy apps have the permission and get user consent on launch. + synchronized (mLock) { + final UidPermissionState uidState = getUidStateLocked(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); + return false; + } + return uidState.isPermissionsReviewRequired(); + } + } + + private void grantRequestedRuntimePermissionsInternal(@NonNull AndroidPackage pkg, + @Nullable List permissions, int userId) { + final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED + | PackageManager.FLAG_PERMISSION_POLICY_FIXED; + + final int compatFlags = PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED + | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; + + final boolean supportsRuntimePermissions = pkg.getTargetSdkVersion() + >= Build.VERSION_CODES.M; + + final boolean instantApp = mPackageManagerInt.isInstantApp(pkg.getPackageName(), userId); + + final int myUid = Process.myUid(); + + for (String permission : pkg.getRequestedPermissions()) { + final boolean shouldGrantPermission; + synchronized (mLock) { + final Permission bp = mRegistry.getPermission(permission); + shouldGrantPermission = bp != null && (bp.isRuntime() || bp.isDevelopment()) + && (!instantApp || bp.isInstant()) + && (supportsRuntimePermissions || !bp.isRuntimeOnly()) + && (permissions == null || permissions.contains(permission)); + } + if (shouldGrantPermission) { + final int flags = getPermissionFlagsInternal(pkg.getPackageName(), permission, + myUid, userId); + if (supportsRuntimePermissions) { + // Installer cannot change immutable permissions. + if ((flags & immutableFlags) == 0) { + grantRuntimePermissionInternal(pkg.getPackageName(), permission, false, + myUid, userId, mDefaultPermissionCallback); + } + } else { + // In permission review mode we clear the review flag and the revoked compat + // flag when we are asked to install the app with all permissions granted. + if ((flags & compatFlags) != 0) { + updatePermissionFlagsInternal(pkg.getPackageName(), permission, compatFlags, + 0, myUid, userId, false, mDefaultPermissionCallback); + } + } + } + } + } + + private void setAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg, + @Nullable List permissions, + @PackageManager.PermissionWhitelistFlags int allowlistFlags, + @UserIdInt int userId) { + ArraySet oldGrantedRestrictedPermissions = null; + boolean updatePermissions = false; + final int permissionCount = pkg.getRequestedPermissions().size(); + final int myUid = Process.myUid(); + + for (int j = 0; j < permissionCount; j++) { + final String permissionName = pkg.getRequestedPermissions().get(j); + + final boolean isGranted; + synchronized (mLock) { + final Permission bp = mRegistry.getPermission(permissionName); + if (bp == null || !bp.isHardOrSoftRestricted()) { + continue; + } + + final UidPermissionState uidState = getUidStateLocked(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + + " and user " + userId); + continue; + } + isGranted = uidState.isPermissionGranted(permissionName); + } + + if (isGranted) { + if (oldGrantedRestrictedPermissions == null) { + oldGrantedRestrictedPermissions = new ArraySet<>(); + } + oldGrantedRestrictedPermissions.add(permissionName); + } + + final int oldFlags = getPermissionFlagsInternal(pkg.getPackageName(), permissionName, + myUid, userId); + + int newFlags = oldFlags; + int mask = 0; + int allowlistFlagsCopy = allowlistFlags; + while (allowlistFlagsCopy != 0) { + final int flag = 1 << Integer.numberOfTrailingZeros(allowlistFlagsCopy); + allowlistFlagsCopy &= ~flag; + switch (flag) { + case FLAG_PERMISSION_WHITELIST_SYSTEM: { + mask |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; + if (permissions != null && permissions.contains(permissionName)) { + newFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; + } else { + newFlags &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; + } + } + break; + case FLAG_PERMISSION_WHITELIST_UPGRADE: { + mask |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; + if (permissions != null && permissions.contains(permissionName)) { + newFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; + } else { + newFlags &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; + } + } + break; + case FLAG_PERMISSION_WHITELIST_INSTALLER: { + mask |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; + if (permissions != null && permissions.contains(permissionName)) { + newFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; + } else { + newFlags &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; + } + } + break; + } + } + + if (oldFlags == newFlags) { + continue; + } + + updatePermissions = true; + + final boolean wasAllowlisted = (oldFlags + & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0; + final boolean isAllowlisted = (newFlags + & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0; + + // If the permission is policy fixed as granted but it is no longer + // on any of the allowlists we need to clear the policy fixed flag + // as allowlisting trumps policy i.e. policy cannot grant a non + // grantable permission. + if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { + if (!isAllowlisted && isGranted) { + mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED; + newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED; + } + } + + // If we are allowlisting an app that does not support runtime permissions + // we need to make sure it goes through the permission review UI at launch. + if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M + && !wasAllowlisted && isAllowlisted) { + mask |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; + newFlags |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; + } + + updatePermissionFlagsInternal(pkg.getPackageName(), permissionName, mask, newFlags, + myUid, userId, false, null /*callback*/); + } + + if (updatePermissions) { + // Update permission of this app to take into account the new allowlist state. + restorePermissionState(pkg, false, pkg.getPackageName(), mDefaultPermissionCallback, + userId); + + // If this resulted in losing a permission we need to kill the app. + if (oldGrantedRestrictedPermissions == null) { + return; + } + + final int oldGrantedCount = oldGrantedRestrictedPermissions.size(); + for (int j = 0; j < oldGrantedCount; j++) { + final String permissionName = oldGrantedRestrictedPermissions.valueAt(j); + // Sometimes we create a new permission state instance during update. + final boolean isGranted; + synchronized (mLock) { + final UidPermissionState uidState = getUidStateLocked(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + + " and user " + userId); + continue; + } + isGranted = uidState.isPermissionGranted(permissionName); + } + if (!isGranted) { + mDefaultPermissionCallback.onPermissionRevoked(pkg.getUid(), userId, null); + break; + } + } + } + } + + private void revokeSharedUserPermissionsForLeavingPackageInternal( + @Nullable AndroidPackage pkg, int appId, @NonNull List sharedUserPkgs, + @UserIdInt int userId) { + if (pkg == null) { + Slog.i(TAG, "Trying to update info for null package. Just ignoring"); + return; + } + + // No shared user packages + if (sharedUserPkgs.isEmpty()) { + return; + } + + PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage( + pkg.getPackageName()); + boolean isShadowingSystemPkg = disabledPs != null && disabledPs.getAppId() == pkg.getUid(); + + boolean shouldKillUid = false; + // Update permissions + for (String eachPerm : pkg.getRequestedPermissions()) { + // Check if another package in the shared user needs the permission. + boolean used = false; + for (AndroidPackage sharedUserpkg : sharedUserPkgs) { + if (sharedUserpkg != null + && !sharedUserpkg.getPackageName().equals(pkg.getPackageName()) + && sharedUserpkg.getRequestedPermissions().contains(eachPerm)) { + used = true; + break; + } + } + if (used) { + continue; + } + + // If the package is shadowing a disabled system package, + // do not drop permissions that the shadowed package requests. + if (isShadowingSystemPkg + && disabledPs.getPkg().getRequestedPermissions().contains(eachPerm)) { + continue; + } + + synchronized (mLock) { + UidPermissionState uidState = getUidStateLocked(appId, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + + " and user " + userId); + continue; + } + + Permission bp = mRegistry.getPermission(eachPerm); + if (bp == null) { + continue; + } + + // TODO(zhanghai): Why are we only killing the UID when GIDs changed, instead of any + // permission change? + if (uidState.removePermissionState(bp.getName()) && bp.hasGids()) { + shouldKillUid = true; + } + } + } + + // If gids changed, kill all affected packages. + if (shouldKillUid) { + mHandler.post(() -> { + // This has to happen with no lock held. + killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED); + }); + } + } + + @GuardedBy("mLock") + private boolean revokeUnusedSharedUserPermissionsLocked( + List pkgList, UidPermissionState uidState) { + // Collect all used permissions in the UID + final ArraySet usedPermissions = new ArraySet<>(); + if (pkgList == null || pkgList.size() == 0) { + return false; + } + for (AndroidPackage pkg : pkgList) { + if (pkg.getRequestedPermissions().isEmpty()) { + continue; + } + final int requestedPermCount = pkg.getRequestedPermissions().size(); + for (int j = 0; j < requestedPermCount; j++) { + String permission = pkg.getRequestedPermissions().get(j); + Permission bp = mRegistry.getPermission(permission); + if (bp != null) { + usedPermissions.add(permission); + } + } + } + + boolean runtimePermissionChanged = false; + + // Prune permissions + final List permissionStates = uidState.getPermissionStates(); + final int permissionStatesSize = permissionStates.size(); + for (int i = permissionStatesSize - 1; i >= 0; i--) { + PermissionState permissionState = permissionStates.get(i); + if (!usedPermissions.contains(permissionState.getName())) { + Permission bp = mRegistry.getPermission(permissionState.getName()); + if (bp != null) { + if (uidState.removePermissionState(bp.getName()) && bp.isRuntime()) { + runtimePermissionChanged = true; + } + } + } + } + + return runtimePermissionChanged; + } + + /** + * Update permissions when a package changed. + * + *

    + *
  1. Reconsider the ownership of permission
  2. + *
  3. Update the state (grant, flags) of the permissions
  4. + *
+ * + * @param packageName The package that is updated + * @param pkg The package that is updated, or {@code null} if package is deleted + */ + private void updatePermissions(@NonNull String packageName, @Nullable AndroidPackage pkg) { + // If the package is being deleted, update the permissions of all the apps + final int flags = + (pkg == null ? UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG + : UPDATE_PERMISSIONS_REPLACE_PKG); + updatePermissions( + packageName, pkg, getVolumeUuidForPackage(pkg), flags, mDefaultPermissionCallback); + } + + /** + * Update all permissions for all apps. + * + *

    + *
  1. Reconsider the ownership of permission
  2. + *
  3. Update the state (grant, flags) of the permissions
  4. + *
+ * + * @param volumeUuid The volume UUID of the packages to be updated + * @param fingerprintChanged whether the current build fingerprint is different from what it was + * when this volume was last mounted + */ + private void updateAllPermissions(@NonNull String volumeUuid, boolean fingerprintChanged) { + PackageManager.corkPackageInfoCache(); // Prevent invalidation storm + try { + final int flags = UPDATE_PERMISSIONS_ALL | + (fingerprintChanged + ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL + : 0); + updatePermissions(null, null, volumeUuid, flags, mDefaultPermissionCallback); + } finally { + PackageManager.uncorkPackageInfoCache(); + } + } + + /** + * Update all packages on the volume, beside the changing package. If the changing + * package is set too, all packages are updated. + */ + private static final int UPDATE_PERMISSIONS_ALL = 1 << 0; + /** The changing package is replaced. Requires the changing package to be set */ + private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1 << 1; + /** + * Schedule all packages beside the changing package for replacement. Requires + * UPDATE_PERMISSIONS_ALL to be set + */ + private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1 << 2; + + @IntDef(flag = true, prefix = { "UPDATE_PERMISSIONS_" }, value = { + UPDATE_PERMISSIONS_ALL, UPDATE_PERMISSIONS_REPLACE_PKG, + UPDATE_PERMISSIONS_REPLACE_ALL }) + @Retention(RetentionPolicy.SOURCE) + private @interface UpdatePermissionFlags {} + + /** + * Update permissions when packages changed. + * + *

    + *
  1. Reconsider the ownership of permission
  2. + *
  3. Update the state (grant, flags) of the permissions
  4. + *
+ * + *

Meaning of combination of package parameters: + * + * + * + * + *
changingPkgName != nullchangingPkgName == null
changingPkg != nullpackage is updatedinvalid
changingPkg == nullpackage is deletedall packages are + * updated
+ * + * @param changingPkgName The package that is updated, or {@code null} if all packages should be + * updated + * @param changingPkg The package that is updated, or {@code null} if all packages should be + * updated or package is deleted + * @param replaceVolumeUuid The volume of the packages to be updated are on, {@code null} for + * all volumes + * @param flags Control permission for which apps should be updated + * @param callback Callback to call after permission changes + */ + private void updatePermissions(final @Nullable String changingPkgName, + final @Nullable AndroidPackage changingPkg, + final @Nullable String replaceVolumeUuid, + @UpdatePermissionFlags int flags, + final @Nullable PermissionCallback callback) { + // TODO: Most of the methods exposing BasePermission internals [source package name, + // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't + // have package settings, we should make note of it elsewhere [map between + // source package name and BasePermission] and cycle through that here. Then we + // define a single method on BasePermission that takes a PackageSetting, changing + // package name and a package. + // NOTE: With this approach, we also don't need to tree trees differently than + // normal permissions. Today, we need two separate loops because these BasePermission + // objects are stored separately. + // Make sure there are no dangling permission trees. + boolean permissionTreesSourcePackageChanged = updatePermissionTreeSourcePackage( + changingPkgName, changingPkg); + // Make sure all dynamic permissions have been assigned to a package, + // and make sure there are no dangling permissions. + boolean permissionSourcePackageChanged = updatePermissionSourcePackage(changingPkgName, + callback); + + if (permissionTreesSourcePackageChanged | permissionSourcePackageChanged) { + // Permission ownership has changed. This e.g. changes which packages can get signature + // permissions + Slog.i(TAG, "Permission ownership changed. Updating all permissions."); + flags |= UPDATE_PERMISSIONS_ALL; + } + + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "restorePermissionState"); + // Now update the permissions for all packages. + if ((flags & UPDATE_PERMISSIONS_ALL) != 0) { + final boolean replaceAll = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0); + mPackageManagerInt.forEachPackage((AndroidPackage pkg) -> { + if (pkg == changingPkg) { + return; + } + // Only replace for packages on requested volume + final String volumeUuid = getVolumeUuidForPackage(pkg); + final boolean replace = replaceAll && Objects.equals(replaceVolumeUuid, volumeUuid); + restorePermissionState(pkg, replace, changingPkgName, callback, + UserHandle.USER_ALL); + }); + } + + if (changingPkg != null) { + // Only replace for packages on requested volume + final String volumeUuid = getVolumeUuidForPackage(changingPkg); + final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0) + && Objects.equals(replaceVolumeUuid, volumeUuid); + restorePermissionState(changingPkg, replace, changingPkgName, callback, + UserHandle.USER_ALL); + } + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + + /** + * Update which app declares a permission. + * + * @param packageName The package that is updated, or {@code null} if all packages should be + * updated + * + * @return {@code true} if a permission source package might have changed + */ + private boolean updatePermissionSourcePackage(@Nullable String packageName, + final @Nullable PermissionCallback callback) { + // Always need update if packageName is null + if (packageName == null) { + return true; + } + + boolean changed = false; + Set needsUpdate = null; + synchronized (mLock) { + for (final Permission bp : mRegistry.getPermissions()) { + if (bp.isDynamic()) { + bp.updateDynamicPermission(mRegistry.getPermissionTrees()); + } + if (!packageName.equals(bp.getPackageName())) { + // Not checking sourcePackageSetting because it can be null when + // the permission source package is the target package and the target package is + // being uninstalled, + continue; + } + // The target package is the source of the current permission + // Set to changed for either install or uninstall + changed = true; + if (needsUpdate == null) { + needsUpdate = new ArraySet<>(); + } + needsUpdate.add(bp); + } + } + if (needsUpdate != null) { + final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + for (final Permission bp : needsUpdate) { + // If the target package is being uninstalled, we need to revoke this permission + // From all other packages + if (pkg == null || !hasPermission(pkg, bp.getName())) { + if (!isPermissionDeclaredByDisabledSystemPkg(bp)) { + Slog.i(TAG, "Removing permission " + bp.getName() + + " that used to be declared by " + bp.getPackageName()); + if (bp.isRuntime()) { + final int[] userIds = mUserManagerInt.getUserIds(); + final int numUserIds = userIds.length; + for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) { + final int userId = userIds[userIdNum]; + mPackageManagerInt.forEachPackage((AndroidPackage p) -> + revokePermissionFromPackageForUser(p.getPackageName(), + bp.getName(), true, userId, callback)); + } + } else { + mPackageManagerInt.forEachPackage(p -> { + final int[] userIds = mUserManagerInt.getUserIds(); + synchronized (mLock) { + for (final int userId : userIds) { + final UidPermissionState uidState = getUidStateLocked(p, + userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + + p.getPackageName() + " and user " + userId); + continue; + } + uidState.removePermissionState(bp.getName()); + } + } + }); + } + } + synchronized (mLock) { + mRegistry.removePermission(bp.getName()); + } + continue; + } + final AndroidPackage sourcePkg = + mPackageManagerInt.getPackage(bp.getPackageName()); + final PackageStateInternal sourcePs = + mPackageManagerInt.getPackageStateInternal(bp.getPackageName()); + synchronized (mLock) { + if (sourcePkg != null && sourcePs != null) { + continue; + } + Slog.w(TAG, "Removing dangling permission: " + bp.getName() + + " from package " + bp.getPackageName()); + mRegistry.removePermission(bp.getName()); + } + } + } + return changed; + } + + private boolean isPermissionDeclaredByDisabledSystemPkg(@NonNull Permission permission) { + final PackageSetting disabledSourcePs = mPackageManagerInt.getDisabledSystemPackage( + permission.getPackageName()); + if (disabledSourcePs != null && disabledSourcePs.getPkg() != null) { + final String permissionName = permission.getName(); + final List sourcePerms = disabledSourcePs.getPkg().getPermissions(); + for (ParsedPermission sourcePerm : sourcePerms) { + if (TextUtils.equals(permissionName, sourcePerm.getName()) + && permission.getProtectionLevel() == sourcePerm.getProtectionLevel()) { + return true; + } + } + } + return false; + } + + /** + * Revoke a runtime permission from a package for a given user ID. + */ + private void revokePermissionFromPackageForUser(@NonNull String pName, + @NonNull String permissionName, boolean overridePolicy, int userId, + @Nullable PermissionCallback callback) { + final ApplicationInfo appInfo = + mPackageManagerInt.getApplicationInfo(pName, 0, + Process.SYSTEM_UID, UserHandle.USER_SYSTEM); + if (appInfo != null + && appInfo.targetSdkVersion < Build.VERSION_CODES.M) { + return; + } + + if (checkPermission(pName, permissionName, userId) + == PackageManager.PERMISSION_GRANTED) { + try { + revokeRuntimePermissionInternal( + pName, permissionName, + overridePolicy, + Process.SYSTEM_UID, + userId, + null, callback); + } catch (IllegalArgumentException e) { + Slog.e(TAG, + "Failed to revoke " + + permissionName + + " from " + + pName, + e); + } + } + } + + /** + * Update which app owns a permission trees. + * + *

Possible parameter combinations + * + * + * + * + *
packageName != nullpackageName == null
pkg != nullpackage is updatedinvalid
pkg == nullpackage is deletedall packages are updated
+ * + * @param packageName The package that is updated, or {@code null} if all packages should be + * updated + * @param pkg The package that is updated, or {@code null} if all packages should be updated or + * package is deleted + * + * @return {@code true} if a permission tree ownership might have changed + */ + private boolean updatePermissionTreeSourcePackage(@Nullable String packageName, + @Nullable AndroidPackage pkg) { + // Always need update if packageName is null + if (packageName == null) { + return true; + } + boolean changed = false; + + Set needsUpdate = null; + synchronized (mLock) { + final Iterator it = mRegistry.getPermissionTrees().iterator(); + while (it.hasNext()) { + final Permission bp = it.next(); + if (!packageName.equals(bp.getPackageName())) { + // Not checking sourcePackageSetting because it can be null when + // the permission source package is the target package and the target package is + // being uninstalled, + continue; + } + // The target package is the source of the current permission tree + // Set to changed for either install or uninstall + changed = true; + if (pkg == null || !hasPermission(pkg, bp.getName())) { + Slog.i(TAG, "Removing permission tree " + bp.getName() + + " that used to be declared by " + bp.getPackageName()); + it.remove(); + } + if (needsUpdate == null) { + needsUpdate = new ArraySet<>(); + } + needsUpdate.add(bp); + } + } + if (needsUpdate != null) { + for (final Permission bp : needsUpdate) { + final AndroidPackage sourcePkg = + mPackageManagerInt.getPackage(bp.getPackageName()); + final PackageStateInternal sourcePs = + mPackageManagerInt.getPackageStateInternal(bp.getPackageName()); + synchronized (mLock) { + if (sourcePkg != null && sourcePs != null) { + continue; + } + Slog.w(TAG, "Removing dangling permission tree: " + bp.getName() + + " from package " + bp.getPackageName()); + mRegistry.removePermission(bp.getName()); + } + } + } + return changed; + } + + private void enforceGrantRevokeRuntimePermissionPermissions(String message) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS) + != PackageManager.PERMISSION_GRANTED + && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException(message + " requires " + + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or " + + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); + } + } + + private void enforceGrantRevokeGetRuntimePermissionPermissions(@NonNull String message) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) + != PackageManager.PERMISSION_GRANTED + && mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS) + != PackageManager.PERMISSION_GRANTED + && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException(message + " requires " + + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or " + + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS + " or " + + Manifest.permission.GET_RUNTIME_PERMISSIONS); + } + } + + /** + * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS + * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userId} is not for the caller. + * + * @param checkShell whether to prevent shell from access if there's a debugging restriction + * @param message the message to log on security exception + */ + private void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, + boolean requireFullPermission, boolean checkShell, @Nullable String message) { + if (userId < 0) { + throw new IllegalArgumentException("Invalid userId " + userId); + } + if (checkShell) { + enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); + } + final int callingUserId = UserHandle.getUserId(callingUid); + if (checkCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission)) { + return; + } + String errorMessage = buildInvalidCrossUserPermissionMessage( + callingUid, userId, message, requireFullPermission); + Slog.w(TAG, errorMessage); + throw new SecurityException(errorMessage); + } + + /** + * Enforces that if the caller is shell, it does not have the provided user restriction. + */ + private void enforceShellRestriction(@NonNull String restriction, int callingUid, + @UserIdInt int userId) { + if (callingUid == Process.SHELL_UID) { + if (userId >= 0 && mUserManagerInt.hasUserRestriction(restriction, userId)) { + throw new SecurityException("Shell does not have permission to access user " + + userId); + } else if (userId < 0) { + Slog.e(LOG_TAG, "Unable to check shell permission for user " + + userId + "\n\t" + Debug.getCallers(3)); + } + } + } + + private boolean checkCrossUserPermission(int callingUid, @UserIdInt int callingUserId, + @UserIdInt int userId, boolean requireFullPermission) { + if (userId == callingUserId) { + return true; + } + if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) { + return true; + } + if (requireFullPermission) { + return checkCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + } + return checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + || checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS); + } + + private boolean checkCallingOrSelfPermission(String permission) { + return mContext.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; + } + + @NonNull + private static String buildInvalidCrossUserPermissionMessage(int callingUid, + @UserIdInt int userId, @Nullable String message, boolean requireFullPermission) { + StringBuilder builder = new StringBuilder(); + if (message != null) { + builder.append(message); + builder.append(": "); + } + builder.append("UID "); + builder.append(callingUid); + builder.append(" requires "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + if (!requireFullPermission) { + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS); + } + builder.append(" to access user "); + builder.append(userId); + builder.append("."); + return builder.toString(); + } + + @GuardedBy("mLock") + private int calculateCurrentPermissionFootprintLocked(@NonNull Permission permissionTree) { + int size = 0; + for (final Permission permission : mRegistry.getPermissions()) { + size += permissionTree.calculateFootprint(permission); + } + return size; + } + + @GuardedBy("mLock") + private void enforcePermissionCapLocked(PermissionInfo info, Permission tree) { + // We calculate the max size of permissions defined by this uid and throw + // if that plus the size of 'info' would exceed our stated maximum. + if (tree.getUid() != Process.SYSTEM_UID) { + final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree); + if (curTreeSize + info.calculateFootprint() > MAX_PERMISSION_TREE_FOOTPRINT) { + throw new SecurityException("Permission tree size cap exceeded"); + } + } + } + + @Override + public void onSystemReady() { + // Now that we've scanned all packages, and granted any default + // permissions, ensure permissions are updated. Beware of dragons if you + // try optimizing this. + updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false); + + final PermissionPolicyInternal permissionPolicyInternal = LocalServices.getService( + PermissionPolicyInternal.class); + permissionPolicyInternal.setOnInitializedCallback(userId -> + // The SDK updated case is already handled when we run during the ctor. + updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false) + ); + + mSystemReady = true; + + synchronized (mLock) { + if (mPrivappPermissionsViolations != null) { + throw new IllegalStateException("Signature|privileged permissions not in " + + "privapp-permissions allowlist: " + mPrivappPermissionsViolations); + } + } + + mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class); + mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class); + } + + private static String getVolumeUuidForPackage(AndroidPackage pkg) { + if (pkg == null) { + return StorageManager.UUID_PRIVATE_INTERNAL; + } + if (pkg.isExternalStorage()) { + if (TextUtils.isEmpty(pkg.getVolumeUuid())) { + return StorageManager.UUID_PRIMARY_PHYSICAL; + } else { + return pkg.getVolumeUuid(); + } + } else { + return StorageManager.UUID_PRIVATE_INTERNAL; + } + } + + private static boolean hasPermission(AndroidPackage pkg, String permName) { + if (pkg.getPermissions().isEmpty()) { + return false; + } + + for (int i = pkg.getPermissions().size() - 1; i >= 0; i--) { + if (pkg.getPermissions().get(i).getName().equals(permName)) { + return true; + } + } + return false; + } + + /** + * Log that a permission request was granted/revoked. + * + * @param action the action performed + * @param name name of the permission + * @param packageName package permission is for + */ + private void logPermission(int action, @NonNull String name, @NonNull String packageName) { + final LogMaker log = new LogMaker(action); + log.setPackageName(packageName); + log.addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, name); + + mMetricsLogger.write(log); + } + + @GuardedBy("mLock") + @Nullable + private UidPermissionState getUidStateLocked(@NonNull PackageStateInternal ps, + @UserIdInt int userId) { + return getUidStateLocked(ps.getAppId(), userId); + } + + @GuardedBy("mLock") + @Nullable + private UidPermissionState getUidStateLocked(@NonNull AndroidPackage pkg, + @UserIdInt int userId) { + return getUidStateLocked(pkg.getUid(), userId); + } + + @GuardedBy("mLock") + @Nullable + private UidPermissionState getUidStateLocked(@AppIdInt int appId, @UserIdInt int userId) { + final UserPermissionState userState = mState.getUserState(userId); + if (userState == null) { + return null; + } + return userState.getUidState(appId); + } + + private void removeUidStateAndResetPackageInstallPermissionsFixed(@AppIdInt int appId, + @NonNull String packageName, @UserIdInt int userId) { + synchronized (mLock) { + final UserPermissionState userState = mState.getUserState(userId); + if (userState == null) { + return; + } + userState.removeUidState(appId); + userState.setInstallPermissionsFixed(packageName, false); + } + } + + @Override + public void readLegacyPermissionStateTEMP() { + final int[] userIds = getAllUserIds(); + mPackageManagerInt.forEachPackageSetting(ps -> { + final int appId = ps.getAppId(); + final LegacyPermissionState legacyState = ps.getLegacyPermissionState(); + + synchronized (mLock) { + for (final int userId : userIds) { + final UserPermissionState userState = mState.getOrCreateUserState(userId); + + userState.setInstallPermissionsFixed(ps.getPackageName(), + ps.isInstallPermissionsFixed()); + final UidPermissionState uidState = userState.getOrCreateUidState(appId); + uidState.reset(); + uidState.setMissing(legacyState.isMissing(userId)); + readLegacyPermissionStatesLocked(uidState, + legacyState.getPermissionStates(userId)); + } + } + }); + } + + @GuardedBy("mLock") + private void readLegacyPermissionStatesLocked(@NonNull UidPermissionState uidState, + @NonNull Collection permissionStates) { + for (final LegacyPermissionState.PermissionState permissionState : permissionStates) { + final String permissionName = permissionState.getName(); + final Permission permission = mRegistry.getPermission(permissionName); + if (permission == null) { + Slog.w(TAG, "Unknown permission: " + permissionName); + continue; + } + uidState.putPermissionState(permission, permissionState.isGranted(), + permissionState.getFlags()); + } + } + + @Override + public void writeLegacyPermissionStateTEMP() { + final int[] userIds; + synchronized (mLock) { + userIds = mState.getUserIds(); + } + mPackageManagerInt.forEachPackageSetting(ps -> { + ps.setInstallPermissionsFixed(false); + final LegacyPermissionState legacyState = ps.getLegacyPermissionState(); + legacyState.reset(); + final int appId = ps.getAppId(); + + synchronized (mLock) { + for (final int userId : userIds) { + final UserPermissionState userState = mState.getUserState(userId); + if (userState == null) { + Slog.e(TAG, "Missing user state for " + userId); + continue; + } + + if (userState.areInstallPermissionsFixed(ps.getPackageName())) { + ps.setInstallPermissionsFixed(true); + } + + final UidPermissionState uidState = userState.getUidState(appId); + if (uidState == null) { + Slog.e(TAG, "Missing permission state for " + ps.getPackageName() + + " and user " + userId); + continue; + } + + legacyState.setMissing(uidState.isMissing(), userId); + final List permissionStates = uidState.getPermissionStates(); + final int permissionStatesSize = permissionStates.size(); + for (int i = 0; i < permissionStatesSize; i++) { + final PermissionState permissionState = permissionStates.get(i); + + final LegacyPermissionState.PermissionState legacyPermissionState = + new LegacyPermissionState.PermissionState(permissionState.getName(), + permissionState.getPermission().isRuntime(), + permissionState.isGranted(), permissionState.getFlags()); + legacyState.putPermissionState(legacyPermissionState, userId); + } + } + } + }); + } + + @Override + public void readLegacyPermissionsTEMP( + @NonNull LegacyPermissionSettings legacyPermissionSettings) { + for (int readPermissionOrPermissionTree = 0; readPermissionOrPermissionTree < 2; + readPermissionOrPermissionTree++) { + final List legacyPermissions = readPermissionOrPermissionTree == 0 + ? legacyPermissionSettings.getPermissions() + : legacyPermissionSettings.getPermissionTrees(); + synchronized (mLock) { + final int legacyPermissionsSize = legacyPermissions.size(); + for (int i = 0; i < legacyPermissionsSize; i++) { + final LegacyPermission legacyPermission = legacyPermissions.get(i); + final Permission permission = new Permission( + legacyPermission.getPermissionInfo(), legacyPermission.getType()); + if (readPermissionOrPermissionTree == 0) { + // Config permissions are currently read in PermissionManagerService + // constructor. The old behavior was to add other attributes to the config + // permission in LegacyPermission.read(), so equivalently we can add the + // GIDs to the new permissions here, since config permissions created in + // PermissionManagerService constructor get only their names and GIDs there. + final Permission configPermission = mRegistry.getPermission( + permission.getName()); + if (configPermission != null + && configPermission.getType() == Permission.TYPE_CONFIG) { + permission.setGids(configPermission.getRawGids(), + configPermission.areGidsPerUser()); + } + mRegistry.addPermission(permission); + } else { + mRegistry.addPermissionTree(permission); + } + } + } + } + } + + @Override + public void writeLegacyPermissionsTEMP( + @NonNull LegacyPermissionSettings legacyPermissionSettings) { + for (int writePermissionOrPermissionTree = 0; writePermissionOrPermissionTree < 2; + writePermissionOrPermissionTree++) { + final List legacyPermissions = new ArrayList<>(); + synchronized (mLock) { + final Collection permissions = writePermissionOrPermissionTree == 0 + ? mRegistry.getPermissions() : mRegistry.getPermissionTrees(); + for (final Permission permission : permissions) { + // We don't need to provide UID and GIDs, which are only retrieved when dumping. + final LegacyPermission legacyPermission = new LegacyPermission( + permission.getPermissionInfo(), permission.getType(), 0, + EmptyArray.INT); + legacyPermissions.add(legacyPermission); + } + } + if (writePermissionOrPermissionTree == 0) { + legacyPermissionSettings.replacePermissions(legacyPermissions); + } else { + legacyPermissionSettings.replacePermissionTrees(legacyPermissions); + } + } + } + + private void onPackageAddedInternal(@NonNull AndroidPackage pkg, boolean isInstantApp, + @Nullable AndroidPackage oldPkg) { + if (!pkg.getAdoptPermissions().isEmpty()) { + // This package wants to adopt ownership of permissions from + // another package. + for (int i = pkg.getAdoptPermissions().size() - 1; i >= 0; i--) { + final String origName = pkg.getAdoptPermissions().get(i); + if (canAdoptPermissionsInternal(origName, pkg)) { + Slog.i(TAG, "Adopting permissions from " + origName + " to " + + pkg.getPackageName()); + synchronized (mLock) { + mRegistry.transferPermissions(origName, pkg.getPackageName()); + } + } + } + } + + // Don't allow ephemeral applications to define new permissions groups. + if (isInstantApp) { + Slog.w(TAG, "Permission groups from package " + pkg.getPackageName() + + " ignored: instant apps cannot define new permission groups."); + } else { + addAllPermissionGroupsInternal(pkg); + } + + // If a permission has had its defining app changed, or it has had its protection + // upgraded, we need to revoke apps that hold it + final List permissionsWithChangedDefinition; + // Don't allow ephemeral applications to define new permissions. + if (isInstantApp) { + permissionsWithChangedDefinition = null; + Slog.w(TAG, "Permissions from package " + pkg.getPackageName() + + " ignored: instant apps cannot define new permissions."); + } else { + permissionsWithChangedDefinition = addAllPermissionsInternal(pkg); + } + + boolean hasOldPkg = oldPkg != null; + boolean hasPermissionDefinitionChanges = + !CollectionUtils.isEmpty(permissionsWithChangedDefinition); + if (hasOldPkg || hasPermissionDefinitionChanges) { + // We need to call revokeRuntimePermissionsIfGroupChanged async as permission + // revoke callbacks from this method might need to kill apps which need the + // mPackages lock on a different thread. This would dead lock. + AsyncTask.execute(() -> { + if (hasOldPkg) { + revokeRuntimePermissionsIfGroupChangedInternal(pkg, oldPkg); + revokeStoragePermissionsIfScopeExpandedInternal(pkg, oldPkg); + } + if (hasPermissionDefinitionChanges) { + revokeRuntimePermissionsIfPermissionDefinitionChangedInternal( + permissionsWithChangedDefinition); + } + }); + } + } + + private boolean canAdoptPermissionsInternal(@NonNull String oldPackageName, + @NonNull AndroidPackage newPkg) { + final PackageStateInternal oldPs = + mPackageManagerInt.getPackageStateInternal(oldPackageName); + if (oldPs == null) { + return false; + } + if (!oldPs.isSystem()) { + Slog.w(TAG, "Unable to update from " + oldPs.getPackageName() + + " to " + newPkg.getPackageName() + + ": old package not in system partition"); + return false; + } + if (mPackageManagerInt.getPackage(oldPs.getPackageName()) != null) { + Slog.w(TAG, "Unable to update from " + oldPs.getPackageName() + + " to " + newPkg.getPackageName() + + ": old package still exists"); + return false; + } + 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()); + 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); + if (userState == null) { + continue; + } + final UidPermissionState prevUidState = userState.getUidState(previousAppId); + if (prevUidState == null) { + continue; + } + + // Insert new uid state by cloning the original one + userState.createUidStateWithExisting(ps.getAppId(), prevUidState); + + // Remove original app ID from original shared user group + // Should match the implementation of onPackageUninstalledInternal(...) + if (origSharedUserPackages.isEmpty()) { + removeUidStateAndResetPackageInstallPermissionsFixed( + previousAppId, pkg.getPackageName(), userId); + } else { + revokeSharedUserPermissionsForLeavingPackageInternal(pkg, previousAppId, + origSharedUserPackages, userId); + } + } + } + } + updatePermissions(pkg.getPackageName(), pkg); + for (final int userId : userIds) { + addAllowlistedRestrictedPermissionsInternal(pkg, + params.getAllowlistedRestrictedPermissions(), + FLAG_PERMISSION_WHITELIST_INSTALLER, userId); + final int autoRevokePermissionsMode = params.getAutoRevokePermissionsMode(); + if (autoRevokePermissionsMode == AppOpsManager.MODE_ALLOWED + || autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED) { + // TODO: theianchen Bug: 182523293 + // We should move this portion of code that's calling + // setAutoRevokeExemptedInternal() into the old PMS + setAutoRevokeExemptedInternal(pkg, + autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED, userId); + } + grantRequestedRuntimePermissionsInternal(pkg, params.getGrantedPermissions(), userId); + } + } + + private void addAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg, + @NonNull List allowlistedRestrictedPermissions, + @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId) { + List permissions = getAllowlistedRestrictedPermissionsInternal(pkg, flags, userId); + if (permissions != null) { + ArraySet permissionSet = new ArraySet<>(permissions); + permissionSet.addAll(allowlistedRestrictedPermissions); + permissions = new ArrayList<>(permissionSet); + } else { + permissions = allowlistedRestrictedPermissions; + } + setAllowlistedRestrictedPermissionsInternal(pkg, permissions, flags, userId); + } + + private void onPackageRemovedInternal(@NonNull AndroidPackage pkg) { + removeAllPermissionsInternal(pkg); + } + + private void onPackageUninstalledInternal(@NonNull String packageName, int appId, + @Nullable AndroidPackage pkg, @NonNull List sharedUserPkgs, + @UserIdInt int[] userIds) { + // TODO: Handle the case when a system app upgrade is uninstalled and need to rejoin + // a shared UID permission state. + + // TODO: Move these checks to check PackageState to be more reliable. + // System packages should always have an available APK. + if (pkg != null && pkg.isSystem() + // We may be fully removing invalid system packages during boot, and in that case we + // do want to remove their permission state. So make sure that the package is only + // being marked as uninstalled instead of fully removed. + && mPackageManagerInt.getPackage(packageName) != null) { + // If we are only marking a system package as uninstalled, we need to keep its + // pregranted permission state so that it still works once it gets reinstalled, thus + // only reset the user modifications to its permission state. + for (final int userId : userIds) { + resetRuntimePermissionsInternal(pkg, userId); + } + return; + } + updatePermissions(packageName, null); + for (final int userId : userIds) { + if (sharedUserPkgs.isEmpty()) { + removeUidStateAndResetPackageInstallPermissionsFixed(appId, packageName, userId); + } else { + // Remove permissions associated with package. Since runtime + // permissions are per user we have to kill the removed package + // or packages running under the shared user of the removed + // package if revoking the permissions requested only by the removed + // package is successful and this causes a change in gids. + revokeSharedUserPermissionsForLeavingPackageInternal(pkg, appId, + sharedUserPkgs, userId); + } + } + } + + @NonNull + @Override + public List getLegacyPermissions() { + synchronized (mLock) { + final List legacyPermissions = new ArrayList<>(); + for (final Permission permission : mRegistry.getPermissions()) { + final LegacyPermission legacyPermission = new LegacyPermission( + permission.getPermissionInfo(), permission.getType(), permission.getUid(), + permission.getRawGids()); + legacyPermissions.add(legacyPermission); + } + return legacyPermissions; + } + } + + @Override + public Map> getAllAppOpPermissionPackages() { + synchronized (mLock) { + final ArrayMap> appOpPermissionPackages = + mRegistry.getAllAppOpPermissionPackages(); + final Map> deepClone = new ArrayMap<>(); + final int appOpPermissionPackagesSize = appOpPermissionPackages.size(); + for (int i = 0; i < appOpPermissionPackagesSize; i++) { + final String appOpPermission = appOpPermissionPackages.keyAt(i); + final ArraySet packageNames = appOpPermissionPackages.valueAt(i); + deepClone.put(appOpPermission, new ArraySet<>(packageNames)); + } + return deepClone; + } + } + + @NonNull + @Override + public LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) { + final LegacyPermissionState legacyState = new LegacyPermissionState(); + synchronized (mLock) { + final int[] userIds = mState.getUserIds(); + for (final int userId : userIds) { + final UidPermissionState uidState = getUidStateLocked(appId, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " + + userId); + continue; + } + + final List permissionStates = uidState.getPermissionStates(); + final int permissionStatesSize = permissionStates.size(); + for (int i = 0; i < permissionStatesSize; i++) { + final PermissionState permissionState = permissionStates.get(i); + + final LegacyPermissionState.PermissionState legacyPermissionState = + new LegacyPermissionState.PermissionState(permissionState.getName(), + permissionState.getPermission().isRuntime(), + permissionState.isGranted(), permissionState.getFlags()); + legacyState.putPermissionState(legacyPermissionState, userId); + } + } + } + return legacyState; + } + + @NonNull + @Override + public int[] getGidsForUid(int uid) { + final int appId = UserHandle.getAppId(uid); + final int userId = UserHandle.getUserId(uid); + synchronized (mLock) { + final UidPermissionState uidState = getUidStateLocked(appId, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " + + userId); + return EMPTY_INT_ARRAY; + } + return uidState.computeGids(mGlobalGids, userId); + } + } + + @Override + public boolean isPermissionsReviewRequired(@NonNull String packageName, + @UserIdInt int userId) { + Objects.requireNonNull(packageName, "packageName"); + // TODO(b/173235285): Some caller may pass USER_ALL as userId. + //Preconditions.checkArgumentNonnegative(userId, "userId"); + return isPermissionsReviewRequiredInternal(packageName, userId); + } + + @NonNull + @Override + public Set getGrantedPermissions(@NonNull String packageName, + @UserIdInt int userId) { + Objects.requireNonNull(packageName, "packageName"); + Preconditions.checkArgumentNonNegative(userId, "userId"); + return getGrantedPermissionsInternal(packageName, userId); + } + + @NonNull + @Override + public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) { + Objects.requireNonNull(permissionName, "permissionName"); + Preconditions.checkArgumentNonNegative(userId, "userId"); + return getPermissionGidsInternal(permissionName, userId); + } + + @NonNull + @Override + public String[] getAppOpPermissionPackages(@NonNull String permissionName) { + Objects.requireNonNull(permissionName, "permissionName"); + return PermissionManagerServiceImpl.this.getAppOpPermissionPackagesInternal(permissionName); + } + + @Override + public void onStorageVolumeMounted(@Nullable String volumeUuid, boolean fingerprintChanged) { + updateAllPermissions(volumeUuid, fingerprintChanged); + } + + @Override + public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) { + Objects.requireNonNull(pkg, "pkg"); + Preconditions.checkArgumentNonNegative(userId, "userId"); + resetRuntimePermissionsInternal(pkg, userId); + } + + @Override + public Permission getPermissionTEMP(String permName) { + synchronized (mLock) { + return mRegistry.getPermission(permName); + } + } + + @NonNull + @Override + public ArrayList getAllPermissionsWithProtection( + @PermissionInfo.Protection int protection) { + ArrayList matchingPermissions = new ArrayList<>(); + + synchronized (mLock) { + for (final Permission permission : mRegistry.getPermissions()) { + if (permission.getProtection() == protection) { + matchingPermissions.add(permission.generatePermissionInfo(0)); + } + } + } + + return matchingPermissions; + } + + @NonNull + @Override + public ArrayList getAllPermissionsWithProtectionFlags( + @PermissionInfo.ProtectionFlags int protectionFlags) { + ArrayList matchingPermissions = new ArrayList<>(); + + synchronized (mLock) { + for (final Permission permission : mRegistry.getPermissions()) { + if ((permission.getProtectionFlags() & protectionFlags) == protectionFlags) { + matchingPermissions.add(permission.generatePermissionInfo(0)); + } + } + } + + return matchingPermissions; + } + + @Override + public void onUserCreated(@UserIdInt int userId) { + Preconditions.checkArgumentNonNegative(userId, "userId"); + // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG + updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, true); + } + + @Override + public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp, + @Nullable AndroidPackage oldPkg) { + Objects.requireNonNull(pkg); + onPackageAddedInternal(pkg, isInstantApp, oldPkg); + } + + @Override + public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId, + @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, + @UserIdInt int userId) { + Objects.requireNonNull(pkg, "pkg"); + Objects.requireNonNull(params, "params"); + Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM + || userId == UserHandle.USER_ALL, "userId"); + final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds() + : new int[] { userId }; + onPackageInstalledInternal(pkg, previousAppId, params, userIds); + } + + @Override + public void onPackageRemoved(@NonNull AndroidPackage pkg) { + Objects.requireNonNull(pkg); + onPackageRemovedInternal(pkg); + } + + @Override + public void onPackageUninstalled(@NonNull String packageName, int appId, + @Nullable AndroidPackage pkg, @NonNull List sharedUserPkgs, + @UserIdInt int userId) { + Objects.requireNonNull(packageName, "packageName"); + Objects.requireNonNull(sharedUserPkgs, "sharedUserPkgs"); + Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM + || userId == UserHandle.USER_ALL, "userId"); + final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds() + : new int[] { userId }; + onPackageUninstalledInternal(packageName, appId, pkg, sharedUserPkgs, userIds); + } + + private boolean setAutoRevokeExemptedInternal(@NonNull AndroidPackage pkg, boolean exempted, + @UserIdInt int userId) { + final int packageUid = UserHandle.getUid(userId, pkg.getUid()); + if (mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, + packageUid, pkg.getPackageName()) != MODE_ALLOWED) { + // Allowlist user set - don't override + return false; + } + + final long identity = Binder.clearCallingIdentity(); + try { + mAppOpsManager.setMode(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageUid, + pkg.getPackageName(), exempted ? MODE_IGNORED : MODE_ALLOWED); + } finally { + Binder.restoreCallingIdentity(identity); + } + return true; + } + + /** + * Callbacks invoked when interesting actions have been taken on a permission. + *

+ * NOTE: The current arguments are merely to support the existing use cases. This + * needs to be properly thought out with appropriate arguments for each of the + * callback methods. + */ + private static class PermissionCallback { + public void onGidsChanged(@AppIdInt int appId, @UserIdInt int userId) {} + public void onPermissionChanged() {} + public void onPermissionGranted(int uid, @UserIdInt int userId) {} + public void onInstallPermissionGranted() {} + public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason) {} + public void onInstallPermissionRevoked() {} + public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {} + public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync, + int uid) { + onPermissionUpdated(updatedUserIds, sync); + } + public void onPermissionRemoved() {} + public void onInstallPermissionUpdated() {} + public void onInstallPermissionUpdatedNotifyListener(int uid) { + onInstallPermissionUpdated(); + } + } + + private static final class OnPermissionChangeListeners extends Handler { + private static final int MSG_ON_PERMISSIONS_CHANGED = 1; + + private final RemoteCallbackList mPermissionListeners = + new RemoteCallbackList<>(); + + OnPermissionChangeListeners(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ON_PERMISSIONS_CHANGED: { + final int uid = msg.arg1; + handleOnPermissionsChanged(uid); + } break; + } + } + + public void addListener(IOnPermissionsChangeListener listener) { + mPermissionListeners.register(listener); + } + + public void removeListener(IOnPermissionsChangeListener listener) { + mPermissionListeners.unregister(listener); + } + + public void onPermissionsChanged(int uid) { + if (mPermissionListeners.getRegisteredCallbackCount() > 0) { + obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget(); + } + } + + private void handleOnPermissionsChanged(int uid) { + final int count = mPermissionListeners.beginBroadcast(); + try { + for (int i = 0; i < count; i++) { + IOnPermissionsChangeListener callback = mPermissionListeners + .getBroadcastItem(i); + try { + callback.onPermissionsChanged(uid); + } catch (RemoteException e) { + Log.e(TAG, "Permission listener is dead", e); + } + } + } finally { + mPermissionListeners.finishBroadcast(); + } + } + } +} diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java new file mode 100644 index 0000000000000..d2018f26d04ad --- /dev/null +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.permission; + +import android.annotation.AppIdInt; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.content.pm.permission.SplitPermissionInfoParcelable; +import android.permission.IOnPermissionsChangeListener; +import android.permission.PermissionManagerInternal; + +import com.android.server.pm.parsing.pkg.AndroidPackage; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Interface for managing all permissions and handling permissions related tasks. + */ +public interface PermissionManagerServiceInterface extends PermissionManagerInternal { + /** + * Dump. + */ + void dump(FileDescriptor fd, PrintWriter pw, String[] args); + + /** + * Retrieve all of the known permission groups in the system. + * + * @param flags additional option flags to modify the data returned + * @return a list of {@link PermissionGroupInfo} containing information about all of the known + * permission groups + */ + List getAllPermissionGroups( + @PackageManager.PermissionGroupInfoFlags int flags); + + /** + * Retrieve all of the information we know about a particular group of permissions. + * + * @param groupName the fully qualified name (e.g. com.android.permission_group.APPS) of the + * permission you are interested in + * @param flags additional option flags to modify the data returned + * @return a {@link PermissionGroupInfo} containing information about the permission, or + * {@code null} if not found + */ + PermissionGroupInfo getPermissionGroupInfo(String groupName, + @PackageManager.PermissionGroupInfoFlags int flags); + + /** + * Retrieve all of the information we know about a particular permission. + * + * @param permName the fully qualified name (e.g. com.android.permission.LOGIN) of the + * permission you are interested in + * @param flags additional option flags to modify the data returned + * @return a {@link PermissionInfo} containing information about the permission, or {@code null} + * if not found + */ + PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName, + @PackageManager.PermissionInfoFlags int flags); + + /** + * Query for all of the permissions associated with a particular group. + * + * @param groupName the fully qualified name (e.g. com.android.permission.LOGIN) of the + * permission group you are interested in. Use {@code null} to find all of the + * permissions not associated with a group + * @param flags additional option flags to modify the data returned + * @return a list of {@link PermissionInfo} containing information about all of the permissions + * in the given group, or {@code null} if the group is not found + */ + List queryPermissionsByGroup(String groupName, + @PackageManager.PermissionInfoFlags int flags); + + /** + * Add a new dynamic permission to the system. For this to work, your package must have defined + * a permission tree through the + * {@link android.R.styleable#AndroidManifestPermissionTree <permission-tree>} tag in its + * manifest. A package can only add permissions to trees that were defined by either its own + * package or another with the same user id; a permission is in a tree if it matches the name of + * the permission tree + ".": for example, "com.foo.bar" is a member of the permission tree + * "com.foo". + *

+ * It is good to make your permission tree name descriptive, because you are taking possession + * of that entire set of permission names. Thus, it must be under a domain you control, with a + * suffix that will not match any normal permissions that may be declared in any applications + * that are part of that domain. + *

+ * New permissions must be added before any .apks are installed that use those permissions. + * Permissions you add through this method are remembered across reboots of the device. If the + * given permission already exists, the info you supply here will be used to update it. + * + * @param info description of the permission to be added + * @param async whether the persistence of the permission should be asynchronous, allowing it to + * return quicker and batch a series of adds, at the expense of no guarantee the + * added permission will be retained if the device is rebooted before it is + * written. + * @return {@code true} if a new permission was created, {@code false} if an existing one was + * updated + * @throws SecurityException if you are not allowed to add the given permission name + * + * @see #removePermission(String) + */ + boolean addPermission(PermissionInfo info, boolean async); + + /** + * Removes a permission that was previously added with + * {@link #addPermission(PermissionInfo, boolean)}. The same ownership rules apply -- you are + * only allowed to remove permissions that you are allowed to add. + * + * @param permName the name of the permission to remove + * @throws SecurityException if you are not allowed to remove the given permission name + * + * @see #addPermission(PermissionInfo, boolean) + */ + void removePermission(String permName); + + /** + * Gets the state flags associated with a permission. + * + * @param packageName the package name for which to get the flags + * @param permName the permission for which to get the flags + * @param userId the user for which to get permission flags + * @return the permission flags + */ + int getPermissionFlags(String packageName, String permName, int userId); + + /** + * Updates the flags associated with a permission by replacing the flags in the specified mask + * with the provided flag values. + * + * @param packageName The package name for which to update the flags + * @param permName The permission for which to update the flags + * @param flagMask The flags which to replace + * @param flagValues The flags with which to replace + * @param userId The user for which to update the permission flags + */ + void updatePermissionFlags(String packageName, String permName, int flagMask, + int flagValues, boolean checkAdjustPolicyFlagPermission, int userId); + + /** + * Update the permission flags for all packages and runtime permissions of a user in order + * to allow device or profile owner to remove POLICY_FIXED. + */ + void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId); + + /** + * TODO: theianchen We should get rid of the IBinder interface which is an implementation detail + * + * Add a listener for permission changes for installed packages. + * @param listener the listener to add + */ + void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener); + + /** + * Remove a listener for permission changes for installed packages. + * @param listener the listener to remove + */ + void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener); + + /** + * addAllowlistedRestrictedPermission. TODO: theianchen add doc + */ + boolean addAllowlistedRestrictedPermission(@NonNull String packageName, + @NonNull String permName, @PackageManager.PermissionWhitelistFlags int flags, + @UserIdInt int userId); + + /** + * Gets the restricted permissions that have been allowlisted and the app is allowed to have + * them granted in their full form. + *

+ * Permissions can be hard restricted which means that the app cannot hold them or soft + * restricted where the app can hold the permission but in a weaker form. Whether a permission + * is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard restricted} or + * {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted} depends on the permission + * declaration. Allowlisting a hard restricted permission allows for the to hold that permission + * and allowlisting a soft restricted permission allows the app to hold the permission in its + * full, unrestricted form. + *

+ * There are four allowlists: + *

    + *
  1. + * One for cases where the system permission policy allowlists a permission. This list + * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM} flag. Can only be + * accessed by pre-installed holders of a dedicated permission. + *
  2. + * One for cases where the system allowlists the permission when upgrading from an OS version in + * which the permission was not restricted to an OS version in which the permission is + * restricted. This list corresponds to the + * {@link PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be accessed by + * pre-installed holders of a dedicated permission or the installer on record. + *
  3. + * One for cases where the installer of the package allowlists a permission. This list + * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be + * accessed by pre-installed holders of a dedicated permission or the installer on record. + *
+ * + * @param packageName the app for which to get allowlisted permissions + * @param flags the flag to determine which allowlist to query. Only one flag can be + * passed. + * @return the allowlisted permissions that are on any of the allowlists you query for + * @throws SecurityException if you try to access a allowlist that you have no access to + * + * @see #addAllowlistedRestrictedPermission(String, String, int) + * @see #removeAllowlistedRestrictedPermission(String, String, int) + * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM + * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE + * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER + */ + List getAllowlistedRestrictedPermissions(@NonNull String packageName, + @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId); + + /** + * Removes a allowlisted restricted permission for an app. + *

+ * Permissions can be hard restricted which means that the app cannot hold them or soft + * restricted where the app can hold the permission but in a weaker form. Whether a permission + * is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard restricted} or + * {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted} depends on the permission + * declaration. Allowlisting a hard restricted permission allows for the to hold that permission + * and allowlisting a soft restricted permission allows the app to hold the permission in its + * full, unrestricted form. + *

There are four allowlists: + *

    + *
  1. + * One for cases where the system permission policy allowlists a permission. This list + * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM} flag. Can only be + * accessed by pre-installed holders of a dedicated permission. + *
  2. + * One for cases where the system allowlists the permission when upgrading from an OS version in + * which the permission was not restricted to an OS version in which the permission is + * restricted. This list corresponds to the + * {@link PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be accessed by + * pre-installed holders of a dedicated permission or the installer on record. + *
  3. + * One for cases where the installer of the package allowlists a permission. This list + * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be + * accessed by pre-installed holders of a dedicated permission or the installer on record. + *
+ *

+ * You need to specify the allowlists for which to set the allowlisted permissions which will + * clear the previous allowlisted permissions and replace them with the provided ones. + * + * @param packageName the app for which to get allowlisted permissions + * @param permName the allowlisted permission to remove + * @param flags the allowlists from which to remove. Passing multiple flags updates all + * specified allowlists. + * @return whether the permission was removed from the allowlist + * @throws SecurityException if you try to modify a allowlist that you have no access to. + * + * @see #getAllowlistedRestrictedPermissions(String, int) + * @see #addAllowlistedRestrictedPermission(String, String, int) + * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM + * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE + * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER + */ + boolean removeAllowlistedRestrictedPermission(@NonNull String packageName, + @NonNull String permName, @PackageManager.PermissionWhitelistFlags int flags, + @UserIdInt int userId); + + /** + * Grant a runtime permission to an application which the application does not already have. The + * permission must have been requested by the application. If the application is not allowed to + * hold the permission, a {@link java.lang.SecurityException} is thrown. If the package or + * permission is invalid, a {@link java.lang.IllegalArgumentException} is thrown. + *

+ * Note: Using this API requires holding + * {@code android.permission.GRANT_RUNTIME_PERMISSIONS} and if the user ID is not the current + * user {@code android.permission.INTERACT_ACROSS_USERS_FULL}. + * + * @param packageName the package to which to grant the permission + * @param permName the permission name to grant + * @param userId the user for which to grant the permission + * + * @see #revokeRuntimePermission(String, String, android.os.UserHandle, String) + */ + void grantRuntimePermission(String packageName, String permName, int userId); + + /** + * Revoke a runtime permission that was previously granted by + * {@link #grantRuntimePermission(String, String, android.os.UserHandle)}. The permission must + * have been requested by and granted to the application. If the application is not allowed to + * hold the permission, a {@link java.lang.SecurityException} is thrown. If the package or + * permission is invalid, a {@link java.lang.IllegalArgumentException} is thrown. + *

+ * Note: Using this API requires holding + * {@code android.permission.REVOKE_RUNTIME_PERMISSIONS} and if the user ID is not the current + * user {@code android.permission.INTERACT_ACROSS_USERS_FULL}. + * + * @param packageName the package from which to revoke the permission + * @param permName the permission name to revoke + * @param userId the user for which to revoke the permission + * @param reason the reason for the revoke, or {@code null} for unspecified + * + * @see #grantRuntimePermission(String, String, android.os.UserHandle) + */ + void revokeRuntimePermission(String packageName, String permName, int userId, + String reason); + + /** + * Get whether you should show UI with rationale for requesting a permission. You should do this + * only if you do not have the permission and the context in which the permission is requested + * does not clearly communicate to the user what would be the benefit from grating this + * permission. + * + * @param permName a permission your app wants to request + * @return whether you can show permission rationale UI + */ + boolean shouldShowRequestPermissionRationale(String packageName, String permName, + @UserIdInt int userId); + + /** + * Checks whether a particular permissions has been revoked for a package by policy. Typically + * the device owner or the profile owner may apply such a policy. The user cannot grant policy + * revoked permissions, hence the only way for an app to get such a permission is by a policy + * change. + * + * @param packageName the name of the package you are checking against + * @param permName the name of the permission you are checking for + * + * @return whether the permission is restricted by policy + */ + boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId); + + /** + * Get set of permissions that have been split into more granular or dependent permissions. + * + *

E.g. before {@link android.os.Build.VERSION_CODES#Q} an app that was granted + * {@link Manifest.permission#ACCESS_COARSE_LOCATION} could access the location while it was in + * foreground and background. On platforms after {@link android.os.Build.VERSION_CODES#Q} + * the location permission only grants location access while the app is in foreground. This + * would break apps that target before {@link android.os.Build.VERSION_CODES#Q}. Hence whenever + * such an old app asks for a location permission (i.e. the + * {@link PermissionManager.SplitPermissionInfo#getSplitPermission()}), then the + * {@link Manifest.permission#ACCESS_BACKGROUND_LOCATION} permission (inside + * {@link PermissionManager.SplitPermissionInfo#getNewPermissions}) is added. + * + *

Note: Regular apps do not have to worry about this. The platform and permission controller + * automatically add the new permissions where needed. + * + * @return All permissions that are split. + */ + List getSplitPermissions(); + + /** + * TODO:theianchen add doc describing this is the old checkPermissionImpl + */ + int checkPermission(String pkgName, String permName, int userId); + + /** + * TODO:theianchen add doc describing this is the old checkUidPermissionImpl + */ + int checkUidPermission(int uid, String permName); + + /** + * Adds a listener for runtime permission state (permissions or flags) changes. + * + * @param listener The listener. + */ + void addOnRuntimePermissionStateChangedListener( + @NonNull PermissionManagerServiceInternal + .OnRuntimePermissionStateChangedListener listener); + + /** + * Removes a listener for runtime permission state (permissions or flags) changes. + * + * @param listener The listener. + */ + void removeOnRuntimePermissionStateChangedListener( + @NonNull PermissionManagerServiceInternal + .OnRuntimePermissionStateChangedListener listener); + + /** + * Get all the package names requesting app op permissions. + * + * @return a map of app op permission names to package names requesting them + */ + Map> getAllAppOpPermissionPackages(); + + /** + * Get whether permission review is required for a package. + * + * @param packageName the name of the package + * @param userId the user ID + * @return whether permission review is required + */ + boolean isPermissionsReviewRequired(@NonNull String packageName, + @UserIdInt int userId); + + /** + * Reset the runtime permission state changes for a package. + * + * TODO(zhanghai): Turn this into package change callback? + * + * @param pkg the package + * @param userId the user ID + */ + void resetRuntimePermissions(@NonNull AndroidPackage pkg, + @UserIdInt int userId); + + /** + * Read legacy permission state from package settings. + * + * TODO(zhanghai): This is a temporary method because we should not expose + * {@code PackageSetting} which is a implementation detail that permission should not know. + * Instead, it should retrieve the legacy state via a defined API. + */ + void readLegacyPermissionStateTEMP(); + + /** + * Write legacy permission state to package settings. + * + * TODO(zhanghai): This is a temporary method and should be removed once we migrated persistence + * for permission. + */ + void writeLegacyPermissionStateTEMP(); + + /** + * Get all the permissions granted to a package. + * + * @param packageName the name of the package + * @param userId the user ID + * @return the names of the granted permissions + */ + @NonNull + Set getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId); + + /** + * Get the GIDs of a permission. + * + * @param permissionName the name of the permission + * @param userId the user ID + * @return the GIDs of the permission + */ + @NonNull + int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId); + + /** + * Get the packages that have requested an app op permission. + * + * @param permissionName the name of the app op permission + * @return the names of the packages that have requested the app op permission + */ + @NonNull + String[] getAppOpPermissionPackages(@NonNull String permissionName); + + /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */ + @Nullable + Permission getPermissionTEMP(@NonNull String permName); + + /** Get all permissions that have a certain protection */ + @NonNull + ArrayList getAllPermissionsWithProtection( + @PermissionInfo.Protection int protection); + + /** Get all permissions that have certain protection flags */ + @NonNull ArrayList getAllPermissionsWithProtectionFlags( + @PermissionInfo.ProtectionFlags int protectionFlags); + + /** + * Get all the legacy permissions currently registered in the system. + * + * @return the legacy permissions + */ + @NonNull + List getLegacyPermissions(); + + /** + * Get the legacy permission state of an app ID, either a package or a shared user. + * + * @param appId the app ID + * @return the legacy permission state + */ + @NonNull + LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId); + + /** + * Read legacy permissions from legacy permission settings. + * + * TODO(zhanghai): This is a temporary method because we should not expose + * {@code LegacyPermissionSettings} which is a implementation detail that permission should not + * know. Instead, it should retrieve the legacy permissions via a defined API. + */ + void readLegacyPermissionsTEMP(@NonNull LegacyPermissionSettings legacyPermissionSettings); + + /** + * Write legacy permissions to legacy permission settings. + * + * TODO(zhanghai): This is a temporary method and should be removed once we migrated persistence + * for permission. + */ + void writeLegacyPermissionsTEMP(@NonNull LegacyPermissionSettings legacyPermissionSettings); + + /** + * Callback when the system is ready. + */ + void onSystemReady(); + + /** + * Callback when a storage volume is mounted, so that all packages on it become available. + * + * @param volumeUuid the UUID of the storage volume + * @param fingerprintChanged whether the current build fingerprint is different from what it was + * when this volume was last mounted + */ + void onStorageVolumeMounted(@NonNull String volumeUuid, boolean fingerprintChanged); + + /** + * Get the GIDs computed from the permission state of a UID, either a package or a shared user. + * + * @param uid the UID + * @return the GIDs for the UID + */ + @NonNull + int[] getGidsForUid(int uid); + + /** + * Callback when a user has been created. + * + * @param userId the created user ID + */ + void onUserCreated(@UserIdInt int userId); + + /** + * Callback when a user has been removed. + * + * @param userId the removed user ID + */ + void onUserRemoved(@UserIdInt int userId); + + /** + * Callback when a package has been added. + * + * @param pkg the added package + * @param isInstantApp whether the added package is an instant app + * @param oldPkg the old package, or {@code null} if none + */ + void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp, + @Nullable AndroidPackage oldPkg); + + /** + * Callback when a package has been installed for a user. + * + * @param pkg the installed package + * @param previousAppId the previous app ID if the package is leaving a shared UID, + * or Process.INVALID_UID + * @param params the parameters passed in for package installation + * @param userId the user ID this package is installed for + */ + void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId, + @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, + @UserIdInt int userId); + + /** + * Callback when a package has been removed. + * + * @param pkg the removed package + */ + void onPackageRemoved(@NonNull AndroidPackage pkg); + + /** + * Callback when a package has been uninstalled. + *

+ * The package may have been fully removed from the system, or only marked as uninstalled for + * this user but still instlaled for other users. + * + * TODO: Pass PackageState instead. + * + * @param packageName the name of the uninstalled package + * @param appId the app ID of the uninstalled package + * @param pkg the uninstalled package, or {@code null} if unavailable + * @param sharedUserPkgs the packages that are in the same shared user + * @param userId the user ID the package is uninstalled for + */ + void onPackageUninstalled(@NonNull String packageName, int appId, @Nullable AndroidPackage pkg, + @NonNull List sharedUserPkgs, @UserIdInt int userId); +}