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