/* * Copyright (C) 2019 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.packageinstaller.permission.service; import static com.android.packageinstaller.PermissionControllerStatsLog.RUNTIME_PERMISSIONS_UPGRADE_RESULT; import android.Manifest; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; import android.permission.PermissionManager; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import com.android.packageinstaller.PermissionControllerStatsLog; import com.android.packageinstaller.permission.model.AppPermissionGroup; import com.android.packageinstaller.permission.model.Permission; import com.android.packageinstaller.permission.utils.Utils; import java.util.ArrayList; import java.util.List; /** * This class handles upgrading the runtime permissions database */ class RuntimePermissionsUpgradeController { private static final String LOG_TAG = RuntimePermissionsUpgradeController.class.getSimpleName(); // The latest version of the runtime permissions database private static final int LATEST_VERSION = 7; private RuntimePermissionsUpgradeController() { /* do nothing - hide constructor */ } static void upgradeIfNeeded(@NonNull Context context) { final PermissionManager permissionManager = context.getSystemService( PermissionManager.class); final int currentVersion = permissionManager.getRuntimePermissionsVersion(); whitelistAllSystemAppPermissions(context); final int upgradedVersion = onUpgradeLocked(context, currentVersion); if (upgradedVersion != LATEST_VERSION) { Log.wtf("PermissionControllerService", "warning: upgrading permission database" + " to version " + LATEST_VERSION + " left it at " + currentVersion + " instead; this is probably a bug. Did you update LATEST_VERSION?", new Throwable()); throw new RuntimeException("db upgrade error"); } if (currentVersion != upgradedVersion) { permissionManager.setRuntimePermissionsVersion(LATEST_VERSION); } } /** * Whitelist permissions of system-apps. * *

Apps that are updated via OTAs are never installed. Hence their permission are never * whitelisted. This code replaces that by always whitelisting them. * * @param context A context to talk to the platform */ private static void whitelistAllSystemAppPermissions(@NonNull Context context) { // Only whitelist permissions that are in the OTA. For non-OTA updates the installer should // do the white-listing final List apps = context.getPackageManager() .getInstalledPackages(PackageManager.GET_PERMISSIONS | PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_FACTORY_ONLY); final int appCount = apps.size(); for (int i = 0; i < appCount; i++) { final PackageInfo app = apps.get(i); if (app.requestedPermissions == null) { continue; } for (String requestedPermission : app.requestedPermissions) { final PermissionInfo permInfo; try { permInfo = context.getPackageManager().getPermissionInfo( requestedPermission, 0); } catch (PackageManager.NameNotFoundException e) { continue; } if ((permInfo.flags & (PermissionInfo.FLAG_HARD_RESTRICTED | PermissionInfo.FLAG_SOFT_RESTRICTED)) == 0) { continue; } context.getPackageManager().addWhitelistedRestrictedPermission( app.packageName, requestedPermission, PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE); } } } /** * You must perform all necessary mutations to bring the runtime permissions * database from the old to the new version. When you add a new upgrade step * you *must* update LATEST_VERSION. * * @param context Context to access APIs. * @param currentVersion The current db version. */ private static int onUpgradeLocked(@NonNull Context context, int currentVersion) { final List apps = context.getPackageManager() .getInstalledPackages(PackageManager.MATCH_ALL | PackageManager.GET_PERMISSIONS); final int appCount = apps.size(); final boolean sdkUpgradedFromP; if (currentVersion <= -1) { Log.i(LOG_TAG, "Upgrading from Android P"); sdkUpgradedFromP = true; currentVersion = 0; } else { sdkUpgradedFromP = false; } if (currentVersion == 0) { Log.i(LOG_TAG, "Grandfathering SMS and CallLog permissions"); final List smsPermissions = Utils.getPlatformPermissionNamesOfGroup( android.Manifest.permission_group.SMS); final List callLogPermissions = Utils.getPlatformPermissionNamesOfGroup( Manifest.permission_group.CALL_LOG); for (int i = 0; i < appCount; i++) { final PackageInfo app = apps.get(i); if (app.requestedPermissions == null) { continue; } for (String requestedPermission : app.requestedPermissions) { if (smsPermissions.contains(requestedPermission) || callLogPermissions.contains(requestedPermission)) { context.getPackageManager().addWhitelistedRestrictedPermission( app.packageName, requestedPermission, PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE); } } } currentVersion = 1; } if (currentVersion == 1) { // moved to step 4->5 as it has to be after the grandfathering of loc bg perms currentVersion = 2; } if (currentVersion == 2) { // moved to step 5->6 to clean up broken permission state during dogfooding currentVersion = 3; } if (currentVersion == 3) { Log.i(LOG_TAG, "Grandfathering location background permissions"); for (int i = 0; i < appCount; i++) { final PackageInfo app = apps.get(i); if (app.requestedPermissions == null) { continue; } for (String requestedPermission : app.requestedPermissions) { if (requestedPermission.equals( Manifest.permission.ACCESS_BACKGROUND_LOCATION)) { context.getPackageManager().addWhitelistedRestrictedPermission( app.packageName, Manifest.permission.ACCESS_BACKGROUND_LOCATION, PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE); break; } } } currentVersion = 4; } if (currentVersion == 4) { // moved to step 5->6 to clean up broken permission state during beta 4->5 upgrade currentVersion = 5; } if (currentVersion == 5) { Log.i(LOG_TAG, "Grandfathering Storage permissions"); final List storagePermissions = Utils.getPlatformPermissionNamesOfGroup( Manifest.permission_group.STORAGE); for (int i = 0; i < appCount; i++) { final PackageInfo app = apps.get(i); if (app.requestedPermissions == null) { continue; } // We don't want to allow modification of storage post install, so put it // on the internal system whitelist to prevent the installer changing it. for (String requestedPermission : app.requestedPermissions) { if (storagePermissions.contains(requestedPermission)) { context.getPackageManager().addWhitelistedRestrictedPermission( app.packageName, requestedPermission, PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE); } } } currentVersion = 6; } if (currentVersion == 6) { if (sdkUpgradedFromP) { Log.i(LOG_TAG, "Expanding location permissions"); for (int i = 0; i < appCount; i++) { final PackageInfo app = apps.get(i); if (app.requestedPermissions == null) { continue; } for (String perm : app.requestedPermissions) { String groupName = Utils.getGroupOfPlatformPermission(perm); if (!TextUtils.equals(groupName, Manifest.permission_group.LOCATION)) { continue; } final AppPermissionGroup group = AppPermissionGroup.create(context, app, perm, false); final AppPermissionGroup bgGroup = group.getBackgroundPermissions(); if (group.areRuntimePermissionsGranted() && bgGroup != null && !bgGroup.isUserSet() && !bgGroup.isSystemFixed() && !bgGroup.isPolicyFixed()) { bgGroup.grantRuntimePermissions(group.isUserFixed()); logRuntimePermissionUpgradeResult(bgGroup, app.applicationInfo.uid, app.packageName); } break; } } } else { Log.i(LOG_TAG, "Not expanding location permissions as this is not an upgrade " + "from Android P"); } currentVersion = 7; } // XXX: Add new upgrade steps above this point. return currentVersion; } private static void logRuntimePermissionUpgradeResult(AppPermissionGroup permissionGroup, int uid, String packageName) { ArrayList permissions = permissionGroup.getPermissions(); int numPermissions = permissions.size(); for (int i = 0; i < numPermissions; i++) { Permission permission = permissions.get(i); PermissionControllerStatsLog.write(RUNTIME_PERMISSIONS_UPGRADE_RESULT, permission.getName(), uid, packageName); Log.v(LOG_TAG, "Runtime permission upgrade logged for permissionName=" + permission.getName() + " uid=" + uid + " packageName=" + packageName); } } }