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.server.policy; 18 19 import static android.Manifest.permission.READ_EXTERNAL_STORAGE; 20 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; 21 import static android.Manifest.permission.WRITE_MEDIA_STORAGE; 22 import static android.app.AppOpsManager.OP_LEGACY_STORAGE; 23 import static android.app.AppOpsManager.OP_NONE; 24 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; 25 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; 26 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; 27 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; 28 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 29 30 import static java.lang.Integer.min; 31 32 import android.annotation.NonNull; 33 import android.annotation.Nullable; 34 import android.app.AppOpsManager; 35 import android.content.Context; 36 import android.content.pm.ApplicationInfo; 37 import android.content.pm.PackageManager; 38 import android.os.Build; 39 import android.os.UserHandle; 40 import android.os.storage.StorageManager; 41 import android.os.storage.StorageManagerInternal; 42 import android.provider.DeviceConfig; 43 44 import com.android.server.LocalServices; 45 import com.android.server.pm.pkg.AndroidPackage; 46 47 import java.util.Arrays; 48 import java.util.HashSet; 49 50 /** 51 * The behavior of soft restricted permissions is different for each permission. This class collects 52 * the policies in one place. 53 * 54 * This is the twin of 55 * {@link com.android.packageinstaller.permission.utils.SoftRestrictedPermissionPolicy} 56 */ 57 public abstract class SoftRestrictedPermissionPolicy { 58 private static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = 59 FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT 60 | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT 61 | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; 62 63 private static final SoftRestrictedPermissionPolicy DUMMY_POLICY = 64 new SoftRestrictedPermissionPolicy() { 65 @Override 66 public boolean mayGrantPermission() { 67 return true; 68 } 69 }; 70 71 private static final HashSet<String> sForcedScopedStorageAppWhitelist = new HashSet<>( 72 Arrays.asList(getForcedScopedStorageAppWhitelist())); 73 74 /** 75 * TargetSDK is per package. To make sure two apps int the same shared UID do not fight over 76 * what to set, always compute the combined targetSDK. 77 * 78 * @param context A context 79 * @param appInfo The app that is changed 80 * @param user The user the app belongs to 81 * 82 * @return The minimum targetSDK of all apps sharing the uid of the app 83 */ getMinimumTargetSDK(@onNull Context context, @NonNull ApplicationInfo appInfo, @NonNull UserHandle user)84 private static int getMinimumTargetSDK(@NonNull Context context, 85 @NonNull ApplicationInfo appInfo, @NonNull UserHandle user) { 86 PackageManager pm = context.getPackageManager(); 87 88 int minimumTargetSDK = appInfo.targetSdkVersion; 89 90 String[] uidPkgs = pm.getPackagesForUid(appInfo.uid); 91 if (uidPkgs != null) { 92 for (String uidPkg : uidPkgs) { 93 if (!uidPkg.equals(appInfo.packageName)) { 94 ApplicationInfo uidPkgInfo; 95 try { 96 uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user); 97 } catch (PackageManager.NameNotFoundException e) { 98 continue; 99 } 100 101 minimumTargetSDK = min(minimumTargetSDK, uidPkgInfo.targetSdkVersion); 102 } 103 } 104 } 105 106 return minimumTargetSDK; 107 } 108 109 /** 110 * Get the policy for a soft restricted permission. 111 * 112 * @param context A context to use 113 * @param appInfo The application the permission belongs to. 114 * @param user The user the app belongs to. 115 * @param permission The name of the permission 116 * 117 * @return The policy for this permission 118 */ forPermission(@onNull Context context, @Nullable ApplicationInfo appInfo, @Nullable AndroidPackage pkg, @Nullable UserHandle user, @NonNull String permission)119 public static @NonNull SoftRestrictedPermissionPolicy forPermission(@NonNull Context context, 120 @Nullable ApplicationInfo appInfo, @Nullable AndroidPackage pkg, 121 @Nullable UserHandle user, @NonNull String permission) { 122 switch (permission) { 123 // Storage uses a special app op to decide the mount state and supports soft restriction 124 // where the restricted state allows the permission but only for accessing the medial 125 // collections. 126 case READ_EXTERNAL_STORAGE: { 127 final boolean isWhiteListed; 128 boolean shouldApplyRestriction; 129 final int targetSDK; 130 final boolean hasLegacyExternalStorage; 131 final boolean hasRequestedLegacyExternalStorage; 132 final boolean hasRequestedPreserveLegacyExternalStorage; 133 final boolean hasWriteMediaStorageGrantedForUid; 134 final boolean isForcedScopedStorage; 135 136 if (appInfo != null) { 137 PackageManager pm = context.getPackageManager(); 138 StorageManagerInternal smInternal = 139 LocalServices.getService(StorageManagerInternal.class); 140 int flags = pm.getPermissionFlags(permission, appInfo.packageName, user); 141 isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; 142 hasLegacyExternalStorage = smInternal.hasLegacyExternalStorage(appInfo.uid); 143 hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage( 144 appInfo.uid, context); 145 hasWriteMediaStorageGrantedForUid = hasWriteMediaStorageGrantedForUid( 146 appInfo.uid, context); 147 hasRequestedPreserveLegacyExternalStorage = 148 pkg.hasPreserveLegacyExternalStorage(); 149 targetSDK = getMinimumTargetSDK(context, appInfo, user); 150 151 shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; 152 isForcedScopedStorage = sForcedScopedStorageAppWhitelist 153 .contains(appInfo.packageName); 154 } else { 155 isWhiteListed = false; 156 shouldApplyRestriction = false; 157 targetSDK = 0; 158 hasLegacyExternalStorage = false; 159 hasRequestedLegacyExternalStorage = false; 160 hasRequestedPreserveLegacyExternalStorage = false; 161 hasWriteMediaStorageGrantedForUid = false; 162 isForcedScopedStorage = false; 163 } 164 165 // We have a check in PermissionPolicyService.PermissionToOpSynchroniser.setUidMode 166 // to prevent apps losing files in legacy storage, because we are holding the 167 // package manager lock here. If we ever remove this policy that check should be 168 // removed as well. 169 return new SoftRestrictedPermissionPolicy() { 170 @Override 171 public boolean mayGrantPermission() { 172 return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q; 173 } 174 @Override 175 public int getExtraAppOpCode() { 176 return OP_LEGACY_STORAGE; 177 } 178 @Override 179 public boolean mayAllowExtraAppOp() { 180 // The only way to get LEGACY_STORAGE (if you didn't already have it) 181 // is that all of the following must be true: 182 // 1. The flag shouldn't be restricted 183 if (shouldApplyRestriction) { 184 return false; 185 } 186 187 // 2. The app shouldn't be in sForcedScopedStorageAppWhitelist 188 if (isForcedScopedStorage) { 189 return false; 190 } 191 192 // 3. The app targetSDK should be less than R 193 if (targetSDK >= Build.VERSION_CODES.R) { 194 return false; 195 } 196 197 // 4. The app has WRITE_MEDIA_STORAGE, 198 // OR the app already has legacy external storage 199 // OR the app requested legacy external storage 200 return hasWriteMediaStorageGrantedForUid || hasLegacyExternalStorage 201 || hasRequestedLegacyExternalStorage; 202 } 203 @Override 204 public boolean mayDenyExtraAppOpIfGranted() { 205 // If you're an app targeting < R, you can keep the app op for 206 // as long as you meet the conditions required to acquire it. 207 if (targetSDK < Build.VERSION_CODES.R) { 208 return !mayAllowExtraAppOp(); 209 } 210 211 // For an app targeting R, the only way to lose LEGACY_STORAGE if you 212 // already had it is in one or more of the following conditions: 213 // 1. The flag became restricted 214 if (shouldApplyRestriction) { 215 return true; 216 } 217 218 // The package is now a part of the forced scoped storage whitelist 219 if (isForcedScopedStorage) { 220 return true; 221 } 222 223 // The package doesn't request legacy storage to be preserved 224 if (!hasRequestedPreserveLegacyExternalStorage) { 225 return true; 226 } 227 228 return false; 229 } 230 }; 231 } 232 case WRITE_EXTERNAL_STORAGE: { 233 final boolean isWhiteListed; 234 final int targetSDK; 235 236 if (appInfo != null) { 237 final int flags = context.getPackageManager().getPermissionFlags(permission, 238 appInfo.packageName, user); 239 isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; 240 targetSDK = getMinimumTargetSDK(context, appInfo, user); 241 } else { 242 isWhiteListed = false; 243 targetSDK = 0; 244 } 245 246 return new SoftRestrictedPermissionPolicy() { 247 @Override 248 public boolean mayGrantPermission() { 249 return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q; 250 } 251 }; 252 } 253 default: 254 return DUMMY_POLICY; 255 } 256 } 257 258 private static boolean hasUidRequestedLegacyExternalStorage(int uid, @NonNull Context context) { 259 PackageManager packageManager = context.getPackageManager(); 260 String[] packageNames = packageManager.getPackagesForUid(uid); 261 if (packageNames == null) { 262 return false; 263 } 264 UserHandle user = UserHandle.getUserHandleForUid(uid); 265 for (String packageName : packageNames) { 266 ApplicationInfo applicationInfo; 267 try { 268 applicationInfo = packageManager.getApplicationInfoAsUser(packageName, 0, user); 269 } catch (PackageManager.NameNotFoundException e) { 270 continue; 271 } 272 if (applicationInfo.hasRequestedLegacyExternalStorage()) { 273 return true; 274 } 275 } 276 return false; 277 } 278 279 private static boolean hasWriteMediaStorageGrantedForUid(int uid, @NonNull Context context) { 280 PackageManager packageManager = context.getPackageManager(); 281 String[] packageNames = packageManager.getPackagesForUid(uid); 282 if (packageNames == null) { 283 return false; 284 } 285 286 for (String packageName : packageNames) { 287 if (packageManager.checkPermission(WRITE_MEDIA_STORAGE, packageName) 288 == PERMISSION_GRANTED) { 289 return true; 290 } 291 } 292 return false; 293 } 294 295 private static String[] getForcedScopedStorageAppWhitelist() { 296 final String rawList = DeviceConfig.getString(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, 297 StorageManager.PROP_FORCED_SCOPED_STORAGE_WHITELIST, /*defaultValue*/""); 298 if (rawList == null || rawList.equals("")) { 299 return new String[0]; 300 } 301 return rawList.split(","); 302 } 303 304 /** 305 * @return If the permission can be granted 306 */ 307 public abstract boolean mayGrantPermission(); 308 309 /** 310 * @return An app op to be changed based on the state of the permission or 311 * {@link AppOpsManager#OP_NONE} if not app-op should be set. 312 */ 313 public int getExtraAppOpCode() { 314 return OP_NONE; 315 } 316 317 /** 318 * @return Whether the {@link #getExtraAppOpCode() app op} may be granted. 319 */ 320 public boolean mayAllowExtraAppOp() { 321 return false; 322 } 323 324 /** 325 * @return Whether the {@link #getExtraAppOpCode() app op} may be denied if was granted. 326 */ 327 public boolean mayDenyExtraAppOpIfGranted() { 328 return false; 329 } 330 } 331