1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.packageinstaller.permission.service; 18 19 import static com.android.packageinstaller.PermissionControllerStatsLog.RUNTIME_PERMISSIONS_UPGRADE_RESULT; 20 21 import android.Manifest; 22 import android.content.Context; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PermissionInfo; 26 import android.permission.PermissionManager; 27 import android.text.TextUtils; 28 import android.util.Log; 29 30 import androidx.annotation.NonNull; 31 32 import com.android.packageinstaller.PermissionControllerStatsLog; 33 import com.android.packageinstaller.permission.model.AppPermissionGroup; 34 import com.android.packageinstaller.permission.model.Permission; 35 import com.android.packageinstaller.permission.utils.Utils; 36 37 import java.util.ArrayList; 38 import java.util.List; 39 40 /** 41 * This class handles upgrading the runtime permissions database 42 */ 43 class RuntimePermissionsUpgradeController { 44 private static final String LOG_TAG = RuntimePermissionsUpgradeController.class.getSimpleName(); 45 46 // The latest version of the runtime permissions database 47 private static final int LATEST_VERSION = 7; 48 RuntimePermissionsUpgradeController()49 private RuntimePermissionsUpgradeController() { 50 /* do nothing - hide constructor */ 51 } 52 upgradeIfNeeded(@onNull Context context)53 static void upgradeIfNeeded(@NonNull Context context) { 54 final PermissionManager permissionManager = context.getSystemService( 55 PermissionManager.class); 56 final int currentVersion = permissionManager.getRuntimePermissionsVersion(); 57 58 whitelistAllSystemAppPermissions(context); 59 60 final int upgradedVersion = onUpgradeLocked(context, currentVersion); 61 62 if (upgradedVersion != LATEST_VERSION) { 63 Log.wtf("PermissionControllerService", "warning: upgrading permission database" 64 + " to version " + LATEST_VERSION + " left it at " + currentVersion 65 + " instead; this is probably a bug. Did you update LATEST_VERSION?", 66 new Throwable()); 67 throw new RuntimeException("db upgrade error"); 68 } 69 70 if (currentVersion != upgradedVersion) { 71 permissionManager.setRuntimePermissionsVersion(LATEST_VERSION); 72 } 73 } 74 75 /** 76 * Whitelist permissions of system-apps. 77 * 78 * <p>Apps that are updated via OTAs are never installed. Hence their permission are never 79 * whitelisted. This code replaces that by always whitelisting them. 80 * 81 * @param context A context to talk to the platform 82 */ whitelistAllSystemAppPermissions(@onNull Context context)83 private static void whitelistAllSystemAppPermissions(@NonNull Context context) { 84 // Only whitelist permissions that are in the OTA. For non-OTA updates the installer should 85 // do the white-listing 86 final List<PackageInfo> apps = context.getPackageManager() 87 .getInstalledPackages(PackageManager.GET_PERMISSIONS 88 | PackageManager.MATCH_UNINSTALLED_PACKAGES 89 | PackageManager.MATCH_FACTORY_ONLY); 90 91 final int appCount = apps.size(); 92 for (int i = 0; i < appCount; i++) { 93 final PackageInfo app = apps.get(i); 94 95 if (app.requestedPermissions == null) { 96 continue; 97 } 98 99 for (String requestedPermission : app.requestedPermissions) { 100 final PermissionInfo permInfo; 101 try { 102 permInfo = context.getPackageManager().getPermissionInfo( 103 requestedPermission, 0); 104 } catch (PackageManager.NameNotFoundException e) { 105 continue; 106 } 107 108 if ((permInfo.flags & (PermissionInfo.FLAG_HARD_RESTRICTED 109 | PermissionInfo.FLAG_SOFT_RESTRICTED)) == 0) { 110 continue; 111 } 112 113 context.getPackageManager().addWhitelistedRestrictedPermission( 114 app.packageName, requestedPermission, 115 PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE); 116 } 117 } 118 } 119 120 /** 121 * You must perform all necessary mutations to bring the runtime permissions 122 * database from the old to the new version. When you add a new upgrade step 123 * you *must* update LATEST_VERSION. 124 * 125 * @param context Context to access APIs. 126 * @param currentVersion The current db version. 127 */ onUpgradeLocked(@onNull Context context, int currentVersion)128 private static int onUpgradeLocked(@NonNull Context context, int currentVersion) { 129 final List<PackageInfo> apps = context.getPackageManager() 130 .getInstalledPackages(PackageManager.MATCH_ALL 131 | PackageManager.GET_PERMISSIONS); 132 final int appCount = apps.size(); 133 134 final boolean sdkUpgradedFromP; 135 if (currentVersion <= -1) { 136 Log.i(LOG_TAG, "Upgrading from Android P"); 137 138 sdkUpgradedFromP = true; 139 140 currentVersion = 0; 141 } else { 142 sdkUpgradedFromP = false; 143 } 144 145 if (currentVersion == 0) { 146 Log.i(LOG_TAG, "Grandfathering SMS and CallLog permissions"); 147 148 final List<String> smsPermissions = Utils.getPlatformPermissionNamesOfGroup( 149 android.Manifest.permission_group.SMS); 150 final List<String> callLogPermissions = Utils.getPlatformPermissionNamesOfGroup( 151 Manifest.permission_group.CALL_LOG); 152 153 for (int i = 0; i < appCount; i++) { 154 final PackageInfo app = apps.get(i); 155 if (app.requestedPermissions == null) { 156 continue; 157 } 158 159 for (String requestedPermission : app.requestedPermissions) { 160 if (smsPermissions.contains(requestedPermission) 161 || callLogPermissions.contains(requestedPermission)) { 162 context.getPackageManager().addWhitelistedRestrictedPermission( 163 app.packageName, requestedPermission, 164 PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE); 165 } 166 } 167 } 168 currentVersion = 1; 169 } 170 171 if (currentVersion == 1) { 172 // moved to step 4->5 as it has to be after the grandfathering of loc bg perms 173 currentVersion = 2; 174 } 175 176 if (currentVersion == 2) { 177 // moved to step 5->6 to clean up broken permission state during dogfooding 178 currentVersion = 3; 179 } 180 181 if (currentVersion == 3) { 182 Log.i(LOG_TAG, "Grandfathering location background permissions"); 183 184 for (int i = 0; i < appCount; i++) { 185 final PackageInfo app = apps.get(i); 186 if (app.requestedPermissions == null) { 187 continue; 188 } 189 190 for (String requestedPermission : app.requestedPermissions) { 191 if (requestedPermission.equals( 192 Manifest.permission.ACCESS_BACKGROUND_LOCATION)) { 193 context.getPackageManager().addWhitelistedRestrictedPermission( 194 app.packageName, Manifest.permission.ACCESS_BACKGROUND_LOCATION, 195 PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE); 196 break; 197 } 198 } 199 } 200 currentVersion = 4; 201 } 202 203 if (currentVersion == 4) { 204 // moved to step 5->6 to clean up broken permission state during beta 4->5 upgrade 205 currentVersion = 5; 206 } 207 208 if (currentVersion == 5) { 209 Log.i(LOG_TAG, "Grandfathering Storage permissions"); 210 211 final List<String> storagePermissions = Utils.getPlatformPermissionNamesOfGroup( 212 Manifest.permission_group.STORAGE); 213 214 for (int i = 0; i < appCount; i++) { 215 final PackageInfo app = apps.get(i); 216 if (app.requestedPermissions == null) { 217 continue; 218 } 219 220 // We don't want to allow modification of storage post install, so put it 221 // on the internal system whitelist to prevent the installer changing it. 222 for (String requestedPermission : app.requestedPermissions) { 223 if (storagePermissions.contains(requestedPermission)) { 224 context.getPackageManager().addWhitelistedRestrictedPermission( 225 app.packageName, requestedPermission, 226 PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE); 227 } 228 } 229 } 230 currentVersion = 6; 231 } 232 233 if (currentVersion == 6) { 234 if (sdkUpgradedFromP) { 235 Log.i(LOG_TAG, "Expanding location permissions"); 236 237 for (int i = 0; i < appCount; i++) { 238 final PackageInfo app = apps.get(i); 239 if (app.requestedPermissions == null) { 240 continue; 241 } 242 243 for (String perm : app.requestedPermissions) { 244 String groupName = Utils.getGroupOfPlatformPermission(perm); 245 246 if (!TextUtils.equals(groupName, Manifest.permission_group.LOCATION)) { 247 continue; 248 } 249 250 final AppPermissionGroup group = AppPermissionGroup.create(context, app, 251 perm, false); 252 final AppPermissionGroup bgGroup = group.getBackgroundPermissions(); 253 254 if (group.areRuntimePermissionsGranted() 255 && bgGroup != null 256 && !bgGroup.isUserSet() && !bgGroup.isSystemFixed() 257 && !bgGroup.isPolicyFixed()) { 258 bgGroup.grantRuntimePermissions(group.isUserFixed()); 259 260 logRuntimePermissionUpgradeResult(bgGroup, 261 app.applicationInfo.uid, app.packageName); 262 } 263 264 break; 265 } 266 } 267 } else { 268 Log.i(LOG_TAG, "Not expanding location permissions as this is not an upgrade " 269 + "from Android P"); 270 } 271 272 currentVersion = 7; 273 } 274 275 // XXX: Add new upgrade steps above this point. 276 277 return currentVersion; 278 } 279 logRuntimePermissionUpgradeResult(AppPermissionGroup permissionGroup, int uid, String packageName)280 private static void logRuntimePermissionUpgradeResult(AppPermissionGroup permissionGroup, 281 int uid, String packageName) { 282 ArrayList<Permission> permissions = permissionGroup.getPermissions(); 283 int numPermissions = permissions.size(); 284 for (int i = 0; i < numPermissions; i++) { 285 Permission permission = permissions.get(i); 286 PermissionControllerStatsLog.write(RUNTIME_PERMISSIONS_UPGRADE_RESULT, 287 permission.getName(), uid, packageName); 288 Log.v(LOG_TAG, "Runtime permission upgrade logged for permissionName=" 289 + permission.getName() + " uid=" + uid + " packageName=" + packageName); 290 } 291 } 292 } 293