Loading src/com/android/packageinstaller/permission/service/PermissionControllerServiceImpl.java +226 −1 Original line number Diff line number Diff line Loading @@ -17,29 +17,35 @@ package com.android.packageinstaller.permission.service; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.permission.PermissionControllerManager.REASON_INSTALLER_POLICY_VIOLATION; import static android.permission.PermissionControllerManager.REASON_MALWARE; import static com.android.packageinstaller.permission.utils.Utils.getLauncherPackages; import static com.android.packageinstaller.permission.utils.Utils.isSystem; import static com.android.packageinstaller.permission.utils.Utils.shouldShowPermission; import android.Manifest; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.permission.PermissionControllerService; import android.permission.PermissionManager; import android.permission.RuntimePermissionPresentationInfo; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.packageinstaller.permission.model.AppPermissionGroup; import com.android.packageinstaller.permission.model.AppPermissions; import com.android.packageinstaller.permission.model.Permission; import com.android.packageinstaller.permission.utils.Utils; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * Calls from the system into the permission controller Loading @@ -47,6 +53,225 @@ import java.util.List; public final class PermissionControllerServiceImpl extends PermissionControllerService { private static final String LOG_TAG = PermissionControllerServiceImpl.class.getSimpleName(); /** * Expand {@code perms} by split permissions for an app with the given targetSDK. * * @param perms The permissions that should be expanded * @param targetSDK The target SDK to expand for * * @return The expanded permissions */ private @NonNull ArrayList<String> addSplitPermissions(@NonNull List<String> perms, int targetSDK) { List<PermissionManager.SplitPermissionInfo> splitPerms = getSystemService(PermissionManager.class).getSplitPermissions(); // Add split permissions to the request ArrayList<String> expandedPerms = new ArrayList<>(perms); int numReqPerms = perms.size(); for (int reqPermNum = 0; reqPermNum < numReqPerms; reqPermNum++) { String reqPerm = perms.get(reqPermNum); int numSplitPerms = splitPerms.size(); for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(splitPermNum); if (targetSDK < splitPerm.getTargetSdk() && splitPerm.getSplitPermission().equals(reqPerm)) { expandedPerms.addAll(splitPerm.getNewPermissions()); } } } return expandedPerms; } /** * Get the package info for a package. * * @param pkg The package name * * @return the package info or {@code null} if the package could not be found */ private @Nullable PackageInfo getPkgInfo(@NonNull String pkg) { try { return getPackageManager().getPackageInfo(pkg, GET_PERMISSIONS); } catch (PackageManager.NameNotFoundException e) { Log.w(LOG_TAG, pkg + " not found", e); return null; } } /** * Given a set of permissions, find all permission groups of an app that can be revoked and that * contain any of the permissions. * * @param permissions The permissions to revoke * @param appPerms The {@link AppPermissions} for the app that is currently investigated * * @return The groups to revoke */ private @NonNull ArrayList<AppPermissionGroup> getRevocableGroupsForPermissions( @NonNull ArrayList<String> permissions, @NonNull AppPermissions appPerms) { ArrayList<AppPermissionGroup> groupsToRevoke = new ArrayList<>(); int numGroups = appPerms.getPermissionGroups().size(); for (int groupNum = 0; groupNum < numGroups; groupNum++) { AppPermissionGroup group = appPerms.getPermissionGroups().get(groupNum); // Do not override fixed permissions if (group.isPolicyFixed() || group.isSystemFixed()) { continue; } int numPerms = permissions.size(); for (int permNum = 0; permNum < numPerms; permNum++) { String reqPerm = permissions.get(permNum); if (group.hasPermission(reqPerm)) { groupsToRevoke.add(group); // If fg permissions get revoked also revoke bg permissions as bg // permissions require fg permissions. AppPermissionGroup bgPerms = group.getBackgroundPermissions(); if (bgPerms != null) { groupsToRevoke.add(bgPerms); } } else { AppPermissionGroup bgPerms = group.getBackgroundPermissions(); if (bgPerms != null && bgPerms.hasPermission(reqPerm)) { groupsToRevoke.add(bgPerms); } } } } return groupsToRevoke; } /** * Revoke all permissions of some groups. * * @param groupsToRevoke The groups * * @return The permissions that were revoked */ private @NonNull ArrayList<String> revokePermissionGroups( @NonNull ArrayList<AppPermissionGroup> groupsToRevoke) { ArrayList<String> revokedPerms = new ArrayList<>(); int numGroupsToRevoke = groupsToRevoke.size(); for (int groupsToRevokeNum = 0; groupsToRevokeNum < numGroupsToRevoke; groupsToRevokeNum++) { AppPermissionGroup group = groupsToRevoke.get(groupsToRevokeNum); ArrayList<Permission> perms = group.getPermissions(); // Mark the permissions as reviewed as we don't want to use to accidentally grant // the permission during review group.resetReviewRequired(); int numPerms = perms.size(); for (int permNum = 0; permNum < numPerms; permNum++) { Permission perm = perms.get(permNum); // Only count individual permissions that are actually revoked if (perm.isGrantedIncludingAppOp()) { revokedPerms.add(perm.getName()); } } group.revokeRuntimePermissions(false); } return revokedPerms; } @Override public @NonNull Map<String, List<String>> onRevokeRuntimePermissions( @NonNull Map<String, List<String>> request, boolean doDryRun, int reason, @NonNull String callerPackageName) { // The reason parameter is not checked by platform code as this might need to be updated // async to platform releases. if (reason != REASON_MALWARE && reason != REASON_INSTALLER_POLICY_VIOLATION) { Log.e(LOG_TAG, "Invalid reason " + reason); return Collections.emptyMap(); } PackageManager pm = getPackageManager(); PackageInfo callerPkgInfo = getPkgInfo(callerPackageName); if (callerPkgInfo == null) { return Collections.emptyMap(); } int callerTargetSdk = callerPkgInfo.applicationInfo.targetSdkVersion; Map<String, List<String>> actuallyRevokedPerms = new ArrayMap<>(); ArrayList<AppPermissions> appsWithRevokedPerms = new ArrayList<>(); for (Map.Entry<String, List<String>> appRequest : request.entrySet()) { PackageInfo requestedPkgInfo = getPkgInfo(appRequest.getKey()); if (requestedPkgInfo == null) { continue; } // Permissions are per UID. Hence permissions will be removed from all apps sharing an // UID. String[] pkgNames = pm.getPackagesForUid(requestedPkgInfo.applicationInfo.uid); if (pkgNames == null) { continue; } int numPkgNames = pkgNames.length; for (int pkgNum = 0; pkgNum < numPkgNames; pkgNum++) { String pkgName = pkgNames[pkgNum]; PackageInfo pkgInfo = getPkgInfo(pkgName); if (pkgInfo == null) { continue; } // If the revocation is because of a market policy violation only the installer can // revoke the permissions. if (reason == REASON_INSTALLER_POLICY_VIOLATION && !callerPackageName.equals(pm.getInstallerPackageName(pkgName))) { Log.i(LOG_TAG, "Ignoring " + pkgName + " as it is not installed by " + callerPackageName); continue; } // In rare cases the caller does not know about the permissions that have been added // due to splits. Hence add them now. ArrayList<String> expandedPerms = addSplitPermissions(appRequest.getValue(), callerTargetSdk); AppPermissions appPerms = new AppPermissions(this, pkgInfo, false, true, null); // First find the groups that should be revoked and then revoke all permissions of // these groups. This is needed as soon as a single permission in the group is // granted, all other permissions get auto-granted on request. ArrayList<AppPermissionGroup> groupsToRevoke = getRevocableGroupsForPermissions( expandedPerms, appPerms); ArrayList<String> revokedPerms = revokePermissionGroups(groupsToRevoke); // In racy conditions the group might not have had granted permissions anymore if (!revokedPerms.isEmpty()) { actuallyRevokedPerms.put(pkgName, revokedPerms); appsWithRevokedPerms.add(appPerms); } } } // Persist changes after we computed everything to remove // This is necessary as we would otherwise only look at the first app of a shared UID. if (!doDryRun) { int numChangedApps = appsWithRevokedPerms.size(); for (int i = 0; i < numChangedApps; i++) { appsWithRevokedPerms.get(i).persistChanges(); } } return actuallyRevokedPerms; } @Override public @NonNull List<RuntimePermissionPresentationInfo> onGetAppPermissions( @NonNull String packageName) { Loading src/com/android/packageinstaller/permission/service/TEST_MAPPING +4 −1 Original line number Diff line number Diff line Loading @@ -5,6 +5,9 @@ "options": [ { "include-filter": "android.permission.cts.LocationAccessCheckTest" }, { "include-filter": "android.permission.cts.PermissionControllerTest" } ] } Loading Loading
src/com/android/packageinstaller/permission/service/PermissionControllerServiceImpl.java +226 −1 Original line number Diff line number Diff line Loading @@ -17,29 +17,35 @@ package com.android.packageinstaller.permission.service; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.permission.PermissionControllerManager.REASON_INSTALLER_POLICY_VIOLATION; import static android.permission.PermissionControllerManager.REASON_MALWARE; import static com.android.packageinstaller.permission.utils.Utils.getLauncherPackages; import static com.android.packageinstaller.permission.utils.Utils.isSystem; import static com.android.packageinstaller.permission.utils.Utils.shouldShowPermission; import android.Manifest; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.permission.PermissionControllerService; import android.permission.PermissionManager; import android.permission.RuntimePermissionPresentationInfo; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.packageinstaller.permission.model.AppPermissionGroup; import com.android.packageinstaller.permission.model.AppPermissions; import com.android.packageinstaller.permission.model.Permission; import com.android.packageinstaller.permission.utils.Utils; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * Calls from the system into the permission controller Loading @@ -47,6 +53,225 @@ import java.util.List; public final class PermissionControllerServiceImpl extends PermissionControllerService { private static final String LOG_TAG = PermissionControllerServiceImpl.class.getSimpleName(); /** * Expand {@code perms} by split permissions for an app with the given targetSDK. * * @param perms The permissions that should be expanded * @param targetSDK The target SDK to expand for * * @return The expanded permissions */ private @NonNull ArrayList<String> addSplitPermissions(@NonNull List<String> perms, int targetSDK) { List<PermissionManager.SplitPermissionInfo> splitPerms = getSystemService(PermissionManager.class).getSplitPermissions(); // Add split permissions to the request ArrayList<String> expandedPerms = new ArrayList<>(perms); int numReqPerms = perms.size(); for (int reqPermNum = 0; reqPermNum < numReqPerms; reqPermNum++) { String reqPerm = perms.get(reqPermNum); int numSplitPerms = splitPerms.size(); for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(splitPermNum); if (targetSDK < splitPerm.getTargetSdk() && splitPerm.getSplitPermission().equals(reqPerm)) { expandedPerms.addAll(splitPerm.getNewPermissions()); } } } return expandedPerms; } /** * Get the package info for a package. * * @param pkg The package name * * @return the package info or {@code null} if the package could not be found */ private @Nullable PackageInfo getPkgInfo(@NonNull String pkg) { try { return getPackageManager().getPackageInfo(pkg, GET_PERMISSIONS); } catch (PackageManager.NameNotFoundException e) { Log.w(LOG_TAG, pkg + " not found", e); return null; } } /** * Given a set of permissions, find all permission groups of an app that can be revoked and that * contain any of the permissions. * * @param permissions The permissions to revoke * @param appPerms The {@link AppPermissions} for the app that is currently investigated * * @return The groups to revoke */ private @NonNull ArrayList<AppPermissionGroup> getRevocableGroupsForPermissions( @NonNull ArrayList<String> permissions, @NonNull AppPermissions appPerms) { ArrayList<AppPermissionGroup> groupsToRevoke = new ArrayList<>(); int numGroups = appPerms.getPermissionGroups().size(); for (int groupNum = 0; groupNum < numGroups; groupNum++) { AppPermissionGroup group = appPerms.getPermissionGroups().get(groupNum); // Do not override fixed permissions if (group.isPolicyFixed() || group.isSystemFixed()) { continue; } int numPerms = permissions.size(); for (int permNum = 0; permNum < numPerms; permNum++) { String reqPerm = permissions.get(permNum); if (group.hasPermission(reqPerm)) { groupsToRevoke.add(group); // If fg permissions get revoked also revoke bg permissions as bg // permissions require fg permissions. AppPermissionGroup bgPerms = group.getBackgroundPermissions(); if (bgPerms != null) { groupsToRevoke.add(bgPerms); } } else { AppPermissionGroup bgPerms = group.getBackgroundPermissions(); if (bgPerms != null && bgPerms.hasPermission(reqPerm)) { groupsToRevoke.add(bgPerms); } } } } return groupsToRevoke; } /** * Revoke all permissions of some groups. * * @param groupsToRevoke The groups * * @return The permissions that were revoked */ private @NonNull ArrayList<String> revokePermissionGroups( @NonNull ArrayList<AppPermissionGroup> groupsToRevoke) { ArrayList<String> revokedPerms = new ArrayList<>(); int numGroupsToRevoke = groupsToRevoke.size(); for (int groupsToRevokeNum = 0; groupsToRevokeNum < numGroupsToRevoke; groupsToRevokeNum++) { AppPermissionGroup group = groupsToRevoke.get(groupsToRevokeNum); ArrayList<Permission> perms = group.getPermissions(); // Mark the permissions as reviewed as we don't want to use to accidentally grant // the permission during review group.resetReviewRequired(); int numPerms = perms.size(); for (int permNum = 0; permNum < numPerms; permNum++) { Permission perm = perms.get(permNum); // Only count individual permissions that are actually revoked if (perm.isGrantedIncludingAppOp()) { revokedPerms.add(perm.getName()); } } group.revokeRuntimePermissions(false); } return revokedPerms; } @Override public @NonNull Map<String, List<String>> onRevokeRuntimePermissions( @NonNull Map<String, List<String>> request, boolean doDryRun, int reason, @NonNull String callerPackageName) { // The reason parameter is not checked by platform code as this might need to be updated // async to platform releases. if (reason != REASON_MALWARE && reason != REASON_INSTALLER_POLICY_VIOLATION) { Log.e(LOG_TAG, "Invalid reason " + reason); return Collections.emptyMap(); } PackageManager pm = getPackageManager(); PackageInfo callerPkgInfo = getPkgInfo(callerPackageName); if (callerPkgInfo == null) { return Collections.emptyMap(); } int callerTargetSdk = callerPkgInfo.applicationInfo.targetSdkVersion; Map<String, List<String>> actuallyRevokedPerms = new ArrayMap<>(); ArrayList<AppPermissions> appsWithRevokedPerms = new ArrayList<>(); for (Map.Entry<String, List<String>> appRequest : request.entrySet()) { PackageInfo requestedPkgInfo = getPkgInfo(appRequest.getKey()); if (requestedPkgInfo == null) { continue; } // Permissions are per UID. Hence permissions will be removed from all apps sharing an // UID. String[] pkgNames = pm.getPackagesForUid(requestedPkgInfo.applicationInfo.uid); if (pkgNames == null) { continue; } int numPkgNames = pkgNames.length; for (int pkgNum = 0; pkgNum < numPkgNames; pkgNum++) { String pkgName = pkgNames[pkgNum]; PackageInfo pkgInfo = getPkgInfo(pkgName); if (pkgInfo == null) { continue; } // If the revocation is because of a market policy violation only the installer can // revoke the permissions. if (reason == REASON_INSTALLER_POLICY_VIOLATION && !callerPackageName.equals(pm.getInstallerPackageName(pkgName))) { Log.i(LOG_TAG, "Ignoring " + pkgName + " as it is not installed by " + callerPackageName); continue; } // In rare cases the caller does not know about the permissions that have been added // due to splits. Hence add them now. ArrayList<String> expandedPerms = addSplitPermissions(appRequest.getValue(), callerTargetSdk); AppPermissions appPerms = new AppPermissions(this, pkgInfo, false, true, null); // First find the groups that should be revoked and then revoke all permissions of // these groups. This is needed as soon as a single permission in the group is // granted, all other permissions get auto-granted on request. ArrayList<AppPermissionGroup> groupsToRevoke = getRevocableGroupsForPermissions( expandedPerms, appPerms); ArrayList<String> revokedPerms = revokePermissionGroups(groupsToRevoke); // In racy conditions the group might not have had granted permissions anymore if (!revokedPerms.isEmpty()) { actuallyRevokedPerms.put(pkgName, revokedPerms); appsWithRevokedPerms.add(appPerms); } } } // Persist changes after we computed everything to remove // This is necessary as we would otherwise only look at the first app of a shared UID. if (!doDryRun) { int numChangedApps = appsWithRevokedPerms.size(); for (int i = 0; i < numChangedApps; i++) { appsWithRevokedPerms.get(i).persistChanges(); } } return actuallyRevokedPerms; } @Override public @NonNull List<RuntimePermissionPresentationInfo> onGetAppPermissions( @NonNull String packageName) { Loading
src/com/android/packageinstaller/permission/service/TEST_MAPPING +4 −1 Original line number Diff line number Diff line Loading @@ -5,6 +5,9 @@ "options": [ { "include-filter": "android.permission.cts.LocationAccessCheckTest" }, { "include-filter": "android.permission.cts.PermissionControllerTest" } ] } Loading