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.providers.media.util; 18 19 import static android.Manifest.permission.BACKUP; 20 import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; 21 import static android.Manifest.permission.READ_EXTERNAL_STORAGE; 22 import static android.Manifest.permission.UPDATE_DEVICE_STATS; 23 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; 24 import static android.app.AppOpsManager.MODE_ALLOWED; 25 import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE; 26 import static android.app.AppOpsManager.OPSTR_READ_MEDIA_AUDIO; 27 import static android.app.AppOpsManager.OPSTR_READ_MEDIA_IMAGES; 28 import static android.app.AppOpsManager.OPSTR_READ_MEDIA_VIDEO; 29 import static android.app.AppOpsManager.OPSTR_WRITE_MEDIA_AUDIO; 30 import static android.app.AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES; 31 import static android.app.AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO; 32 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 33 34 import android.app.AppOpsManager; 35 import android.app.DownloadManager; 36 import android.content.Context; 37 import android.content.pm.PackageManager; 38 39 import androidx.annotation.NonNull; 40 import androidx.annotation.Nullable; 41 import androidx.annotation.VisibleForTesting; 42 43 public class PermissionUtils { 44 45 public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage"; 46 47 // Callers must hold both the old and new permissions, so that we can 48 // handle obscure cases like when an app targets Q but was installed on 49 // a device that was originally running on P before being upgraded to Q. 50 51 private static ThreadLocal<String> sOpDescription = new ThreadLocal<>(); 52 setOpDescription(@ullable String description)53 public static void setOpDescription(@Nullable String description) { 54 sOpDescription.set(description); 55 } 56 clearOpDescription()57 public static void clearOpDescription() { sOpDescription.set(null); } 58 checkPermissionSelf(@onNull Context context, int pid, int uid)59 public static boolean checkPermissionSelf(@NonNull Context context, int pid, int uid) { 60 return android.os.Process.myUid() == uid; 61 } 62 checkPermissionShell(@onNull Context context, int pid, int uid)63 public static boolean checkPermissionShell(@NonNull Context context, int pid, int uid) { 64 switch (uid) { 65 case android.os.Process.ROOT_UID: 66 case android.os.Process.SHELL_UID: 67 return true; 68 default: 69 return false; 70 } 71 } 72 73 /** 74 * Check if the given package has been granted the "file manager" role on 75 * the device, which should grant them certain broader access. 76 */ checkPermissionManager(@onNull Context context, int pid, int uid, @NonNull String packageName, @Nullable String attributionTag)77 public static boolean checkPermissionManager(@NonNull Context context, int pid, 78 int uid, @NonNull String packageName, @Nullable String attributionTag) { 79 if (checkPermissionForDataDelivery(context, MANAGE_EXTERNAL_STORAGE, pid, uid, 80 packageName, attributionTag, 81 generateAppOpMessage(packageName,sOpDescription.get()))) { 82 return true; 83 } 84 // Fallback to OPSTR_NO_ISOLATED_STORAGE app op. 85 return checkNoIsolatedStorageGranted(context, uid, packageName, attributionTag); 86 } 87 88 /** 89 * Check if the given package has the ability to "delegate" the ownership of 90 * media items that they own to other apps, typically when they've finished 91 * performing operations on behalf of those apps. 92 * <p> 93 * One use-case for this is backup/restore apps, where the app restoring the 94 * content needs to shift the ownership back to the app that originally 95 * owned that media. 96 * <p> 97 * Another use-case is {@link DownloadManager}, which shifts ownership of 98 * finished downloads to the app that originally requested them. 99 */ checkPermissionDelegator(@onNull Context context, int pid, int uid)100 public static boolean checkPermissionDelegator(@NonNull Context context, int pid, int uid) { 101 return (context.checkPermission(BACKUP, pid, uid) == PERMISSION_GRANTED) 102 || (context.checkPermission(UPDATE_DEVICE_STATS, pid, uid) == PERMISSION_GRANTED); 103 } 104 checkPermissionWriteStorage(@onNull Context context, int pid, int uid, @NonNull String packageName, @Nullable String attributionTag)105 public static boolean checkPermissionWriteStorage(@NonNull Context context, int pid, int uid, 106 @NonNull String packageName, @Nullable String attributionTag) { 107 return checkPermissionForDataDelivery(context, WRITE_EXTERNAL_STORAGE, pid, uid, 108 packageName, attributionTag, 109 generateAppOpMessage(packageName,sOpDescription.get())); 110 } 111 checkPermissionReadStorage(@onNull Context context, int pid, int uid, @NonNull String packageName, @Nullable String attributionTag)112 public static boolean checkPermissionReadStorage(@NonNull Context context, int pid, int uid, 113 @NonNull String packageName, @Nullable String attributionTag) { 114 return checkPermissionForDataDelivery(context, READ_EXTERNAL_STORAGE, pid, uid, 115 packageName, attributionTag, 116 generateAppOpMessage(packageName,sOpDescription.get())); 117 } 118 checkIsLegacyStorageGranted( @onNull Context context, int uid, String packageName)119 public static boolean checkIsLegacyStorageGranted( 120 @NonNull Context context, int uid, String packageName) { 121 return context.getSystemService(AppOpsManager.class) 122 .unsafeCheckOp(OPSTR_LEGACY_STORAGE, uid, packageName) == MODE_ALLOWED; 123 } 124 checkPermissionReadAudio(@onNull Context context, int pid, int uid, @NonNull String packageName, @Nullable String attributionTag)125 public static boolean checkPermissionReadAudio(@NonNull Context context, int pid, int uid, 126 @NonNull String packageName, @Nullable String attributionTag) { 127 if (!checkPermissionForPreflight(context, READ_EXTERNAL_STORAGE, pid, uid, packageName)) { 128 return false; 129 } 130 return checkAppOpAllowingLegacy(context, OPSTR_READ_MEDIA_AUDIO, pid, 131 uid, packageName, attributionTag, 132 generateAppOpMessage(packageName, sOpDescription.get())); 133 } 134 checkPermissionWriteAudio(@onNull Context context, int pid, int uid, @NonNull String packageName, @Nullable String attributionTag)135 public static boolean checkPermissionWriteAudio(@NonNull Context context, int pid, int uid, 136 @NonNull String packageName, @Nullable String attributionTag) { 137 if (!checkPermissionAllowingNonLegacy( 138 context, WRITE_EXTERNAL_STORAGE, pid, uid, packageName)) { 139 return false; 140 } 141 return checkAppOpAllowingLegacy(context, OPSTR_WRITE_MEDIA_AUDIO, pid, 142 uid, packageName, attributionTag, 143 generateAppOpMessage(packageName, sOpDescription.get())); 144 } 145 checkPermissionReadVideo(@onNull Context context, int pid, int uid, @NonNull String packageName, @Nullable String attributionTag)146 public static boolean checkPermissionReadVideo(@NonNull Context context, int pid, int uid, 147 @NonNull String packageName, @Nullable String attributionTag) { 148 if (!checkPermissionForPreflight(context, READ_EXTERNAL_STORAGE, pid, uid, packageName)) { 149 return false; 150 } 151 return checkAppOpAllowingLegacy(context, OPSTR_READ_MEDIA_VIDEO, pid, 152 uid, packageName, attributionTag, 153 generateAppOpMessage(packageName, sOpDescription.get())); 154 } 155 checkPermissionWriteVideo(@onNull Context context, int pid, int uid, @NonNull String packageName, @Nullable String attributionTag)156 public static boolean checkPermissionWriteVideo(@NonNull Context context, int pid, int uid, 157 @NonNull String packageName, @Nullable String attributionTag) { 158 if (!checkPermissionAllowingNonLegacy( 159 context, WRITE_EXTERNAL_STORAGE, pid, uid, packageName)) { 160 return false; 161 } 162 return checkAppOpAllowingLegacy(context, OPSTR_WRITE_MEDIA_VIDEO, pid, 163 uid, packageName, attributionTag, 164 generateAppOpMessage(packageName, sOpDescription.get())); 165 } 166 checkPermissionReadImages(@onNull Context context, int pid, int uid, @NonNull String packageName, @Nullable String attributionTag)167 public static boolean checkPermissionReadImages(@NonNull Context context, int pid, int uid, 168 @NonNull String packageName, @Nullable String attributionTag) { 169 if (!checkPermissionForPreflight(context, READ_EXTERNAL_STORAGE, pid, uid, packageName)) { 170 return false; 171 } 172 return checkAppOpAllowingLegacy(context, OPSTR_READ_MEDIA_IMAGES, pid, 173 uid, packageName, attributionTag, 174 generateAppOpMessage(packageName, sOpDescription.get())); 175 } 176 checkPermissionWriteImages(@onNull Context context, int pid, int uid, @NonNull String packageName, @Nullable String attributionTag)177 public static boolean checkPermissionWriteImages(@NonNull Context context, int pid, int uid, 178 @NonNull String packageName, @Nullable String attributionTag) { 179 if (!checkPermissionAllowingNonLegacy( 180 context, WRITE_EXTERNAL_STORAGE, pid, uid, packageName)) { 181 return false; 182 } 183 return checkAppOpAllowingLegacy(context, OPSTR_WRITE_MEDIA_IMAGES, pid, 184 uid, packageName, attributionTag, 185 generateAppOpMessage(packageName, sOpDescription.get())); 186 } 187 188 @VisibleForTesting checkNoIsolatedStorageGranted(@onNull Context context, int uid, @NonNull String packageName, @Nullable String attributionTag)189 static boolean checkNoIsolatedStorageGranted(@NonNull Context context, int uid, 190 @NonNull String packageName, @Nullable String attributionTag) { 191 final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); 192 int ret = appOps.noteOpNoThrow(OPSTR_NO_ISOLATED_STORAGE, uid, packageName, attributionTag, 193 generateAppOpMessage(packageName, "am instrument --no-isolated-storage")); 194 return ret == AppOpsManager.MODE_ALLOWED; 195 } 196 197 /** 198 * Generates a message to be used with the different {@link AppOpsManager#noteOp} variations. 199 * If the supplied description is {@code null}, the returned message will be {@code null}. 200 */ generateAppOpMessage( @onNull String packageName, @Nullable String description)201 private static String generateAppOpMessage( 202 @NonNull String packageName, @Nullable String description) { 203 if (description == null) { 204 return null; 205 } 206 return "Package: " + packageName + ". Description: " + description + "."; 207 } 208 209 /** 210 * Similar to {@link #checkPermissionForPreflight(Context, String, int, int, String)}, 211 * but also returns true for non-legacy apps. 212 */ checkPermissionAllowingNonLegacy(@onNull Context context, @NonNull String permission, int pid, int uid, @NonNull String packageName)213 private static boolean checkPermissionAllowingNonLegacy(@NonNull Context context, 214 @NonNull String permission, int pid, int uid, @NonNull String packageName) { 215 final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); 216 217 // Allowing non legacy apps to bypass this check 218 if (appOps.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, uid, 219 packageName) != AppOpsManager.MODE_ALLOWED) return true; 220 221 // Seems like it's a legacy app, so it has to pass the permission check 222 return checkPermissionForPreflight(context, permission, pid, uid, packageName); 223 } 224 225 /** 226 * Checks *only* App Ops, also returns true for legacy apps. 227 */ checkAppOpAllowingLegacy(@onNull Context context, @NonNull String op, int pid, int uid, @NonNull String packageName, @Nullable String attributionTag, @Nullable String opMessage)228 private static boolean checkAppOpAllowingLegacy(@NonNull Context context, 229 @NonNull String op, int pid, int uid, @NonNull String packageName, 230 @Nullable String attributionTag, @Nullable String opMessage) { 231 final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); 232 final int mode = appOps.noteOpNoThrow(op, uid, packageName, attributionTag, opMessage); 233 switch (mode) { 234 case AppOpsManager.MODE_ALLOWED: 235 return true; 236 case AppOpsManager.MODE_DEFAULT: 237 case AppOpsManager.MODE_IGNORED: 238 case AppOpsManager.MODE_ERRORED: 239 // Legacy apps technically have the access granted by this op, 240 // even when the op is denied 241 if ((appOps.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, uid, 242 packageName) == AppOpsManager.MODE_ALLOWED)) return true; 243 244 return false; 245 default: 246 throw new IllegalStateException(op + " has unknown mode " + mode); 247 } 248 } 249 250 /** 251 * Checks whether a given package in a UID and PID has a given permission 252 * and whether the app op that corresponds to this permission is allowed. 253 * 254 * <strong>NOTE:</strong> Use this method only for permission checks at the 255 * preflight point where you will not deliver the permission protected data 256 * to clients but schedule permission data delivery, apps register listeners, 257 * etc. 258 * 259 * <p>For example, if an app registers a location listener it should have the location 260 * permission but no data is actually sent to the app at the moment of registration 261 * and you should use this method to determine if the app has or may have location 262 * permission (if app has only foreground location the grant state depends on the app's 263 * fg/gb state) and this check will not leave a trace that permission protected data 264 * was delivered. When you are about to deliver the location data to a registered 265 * listener you should use {@link #checkPermissionForDataDelivery(Context, String, 266 * int, int, String, String, String)} which will evaluate the permission access based on the 267 * current fg/bg state of the app and leave a record that the data was accessed. 268 * 269 * @param context Context for accessing resources. 270 * @param permission The permission to check. 271 * @param pid The process id for which to check. 272 * @param uid The uid for which to check. 273 * @param packageName The package name for which to check. If null the 274 * the first package for the calling UID will be used. 275 * @return boolean if permission is {@link #PERMISSION_GRANTED} 276 * 277 * @see #checkPermissionForDataDelivery(Context, String, int, int, String, String, String) 278 */ checkPermissionForPreflight(@onNull Context context, @NonNull String permission, int pid, int uid, @Nullable String packageName)279 private static boolean checkPermissionForPreflight(@NonNull Context context, 280 @NonNull String permission, int pid, int uid, @Nullable String packageName) { 281 return checkPermissionCommon(context, permission, pid, uid, packageName, 282 null /*attributionTag*/, null /*message*/, 283 false /*forDataDelivery*/); 284 } 285 286 /** 287 * Checks whether a given package in a UID and PID has a given permission 288 * and whether the app op that corresponds to this permission is allowed. 289 * 290 * <strong>NOTE:</strong> Use this method only for permission checks at the 291 * point where you will deliver the permission protected data to clients. 292 * 293 * <p>For example, if an app registers a location listener it should have the location 294 * permission but no data is actually sent to the app at the moment of registration 295 * and you should use {@link #checkPermissionForPreflight(Context, String, int, int, String)} 296 * to determine if the app has or may have location permission (if app has only foreground 297 * location the grant state depends on the app's fg/gb state) and this check will not 298 * leave a trace that permission protected data was delivered. When you are about to 299 * deliver the location data to a registered listener you should use this method which 300 * will evaluate the permission access based on the current fg/bg state of the app and 301 * leave a record that the data was accessed. 302 * 303 * @param context Context for accessing resources. 304 * @param permission The permission to check. 305 * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID 306 * is not known. 307 * @param uid The uid for which to check. 308 * @param packageName The package name for which to check. If null the 309 * the first package for the calling UID will be used. 310 * @param attributionTag attribution tag 311 * @return boolean true if {@link #PERMISSION_GRANTED} 312 * @param message A message describing the reason the permission was checked 313 * 314 * @see #checkPermissionForPreflight(Context, String, int, int, String) 315 */ checkPermissionForDataDelivery(@onNull Context context, @NonNull String permission, int pid, int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String message)316 private static boolean checkPermissionForDataDelivery(@NonNull Context context, 317 @NonNull String permission, int pid, int uid, @Nullable String packageName, 318 @Nullable String attributionTag, @Nullable String message) { 319 return checkPermissionCommon(context, permission, pid, uid, packageName, attributionTag, 320 message, true /*forDataDelivery*/); 321 } 322 checkPermissionCommon(@onNull Context context, @NonNull String permission, int pid, int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String message, boolean forDataDelivery)323 private static boolean checkPermissionCommon(@NonNull Context context, 324 @NonNull String permission, int pid, int uid, @Nullable String packageName, 325 @Nullable String attributionTag, @Nullable String message, boolean forDataDelivery) { 326 if (packageName == null) { 327 String[] packageNames = context.getPackageManager().getPackagesForUid(uid); 328 if (packageNames != null && packageNames.length > 0) { 329 packageName = packageNames[0]; 330 } 331 } 332 333 if (isAppOpPermission(permission)) { 334 return checkAppOpPermission(context, permission, pid, uid, packageName, attributionTag, 335 message, forDataDelivery); 336 } 337 if (isRuntimePermission(permission)) { 338 return checkRuntimePermission(context, permission, pid, uid, packageName, 339 attributionTag, message, forDataDelivery); 340 } 341 return context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED; 342 } 343 isAppOpPermission(String permission)344 private static boolean isAppOpPermission(String permission) { 345 switch (permission) { 346 case MANAGE_EXTERNAL_STORAGE: 347 return true; 348 } 349 return false; 350 } 351 isRuntimePermission(String permission)352 private static boolean isRuntimePermission(String permission) { 353 switch (permission) { 354 case READ_EXTERNAL_STORAGE: 355 case WRITE_EXTERNAL_STORAGE: 356 return true; 357 } 358 return false; 359 } 360 checkAppOpPermission(@onNull Context context, @NonNull String permission, int pid, int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String message, boolean forDataDelivery)361 private static boolean checkAppOpPermission(@NonNull Context context, 362 @NonNull String permission, int pid, int uid, @Nullable String packageName, 363 @Nullable String attributionTag, @Nullable String message, boolean forDataDelivery) { 364 final String op = AppOpsManager.permissionToOp(permission); 365 if (op == null || packageName == null) { 366 return false; 367 } 368 369 final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); 370 final int opMode = (forDataDelivery) 371 ? appOpsManager.noteOpNoThrow(op, uid, packageName, attributionTag, message) 372 : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName); 373 374 switch (opMode) { 375 case AppOpsManager.MODE_ALLOWED: 376 case AppOpsManager.MODE_FOREGROUND: 377 return true; 378 case AppOpsManager.MODE_DEFAULT: 379 return context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED; 380 default: 381 return false; 382 } 383 } 384 checkRuntimePermission(@onNull Context context, @NonNull String permission, int pid, int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String message, boolean forDataDelivery)385 private static boolean checkRuntimePermission(@NonNull Context context, 386 @NonNull String permission, int pid, int uid, @Nullable String packageName, 387 @Nullable String attributionTag, @Nullable String message, boolean forDataDelivery) { 388 if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) { 389 return false; 390 } 391 392 final String op = AppOpsManager.permissionToOp(permission); 393 if (op == null || packageName == null) { 394 return true; 395 } 396 397 final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); 398 final int opMode = (forDataDelivery) 399 ? appOpsManager.noteOpNoThrow(op, uid, packageName, attributionTag, message) 400 : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName); 401 402 switch (opMode) { 403 case AppOpsManager.MODE_ALLOWED: 404 case AppOpsManager.MODE_FOREGROUND: 405 return true; 406 default: 407 return false; 408 } 409 } 410 } 411