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; 18 19 import static android.Manifest.permission.ACCESS_MEDIA_LOCATION; 20 import static android.app.AppOpsManager.MODE_ALLOWED; 21 import static android.app.AppOpsManager.permissionToOp; 22 import static android.content.pm.PackageManager.PERMISSION_DENIED; 23 24 import static com.android.providers.media.util.PermissionUtils.checkIsLegacyStorageGranted; 25 import static com.android.providers.media.util.PermissionUtils.checkPermissionDelegator; 26 import static com.android.providers.media.util.PermissionUtils.checkPermissionManager; 27 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadAudio; 28 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadImages; 29 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadStorage; 30 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadVideo; 31 import static com.android.providers.media.util.PermissionUtils.checkPermissionSelf; 32 import static com.android.providers.media.util.PermissionUtils.checkPermissionShell; 33 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteAudio; 34 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages; 35 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage; 36 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteVideo; 37 38 import android.annotation.Nullable; 39 import android.app.AppOpsManager; 40 import android.app.compat.CompatChanges; 41 import android.content.ContentProvider; 42 import android.content.Context; 43 import android.content.pm.ApplicationInfo; 44 import android.content.pm.PackageManager.NameNotFoundException; 45 import android.os.Binder; 46 import android.os.Build; 47 import android.os.Process; 48 import android.os.SystemProperties; 49 import android.os.UserHandle; 50 import android.os.UserManager; 51 import android.util.ArrayMap; 52 53 import androidx.annotation.NonNull; 54 55 import com.android.providers.media.util.LongArray; 56 57 public class LocalCallingIdentity { 58 public final Context context; 59 public final int pid; 60 public final int uid; 61 public final String packageNameUnchecked; 62 // Info used for logging permission checks 63 public @Nullable String attributionTag; 64 LocalCallingIdentity(Context context, int pid, int uid, String packageNameUnchecked, @Nullable String attributionTag)65 private LocalCallingIdentity(Context context, int pid, int uid, String packageNameUnchecked, 66 @Nullable String attributionTag) { 67 this.context = context; 68 this.pid = pid; 69 this.uid = uid; 70 this.packageNameUnchecked = packageNameUnchecked; 71 this.attributionTag = attributionTag; 72 } 73 74 /** 75 * See definition in {@link android.os.Environment} 76 */ 77 private static final long DEFAULT_SCOPED_STORAGE = 149924527L; 78 79 /** 80 * See definition in {@link android.os.Environment} 81 */ 82 private static final long FORCE_ENABLE_SCOPED_STORAGE = 132649864L; 83 84 private static final long UNKNOWN_ROW_ID = -1; 85 fromBinder(Context context, ContentProvider provider)86 public static LocalCallingIdentity fromBinder(Context context, ContentProvider provider) { 87 String callingPackage = provider.getCallingPackageUnchecked(); 88 if (callingPackage == null) { 89 callingPackage = context.getOpPackageName(); 90 } 91 String callingAttributionTag = provider.getCallingAttributionTag(); 92 if (callingAttributionTag == null) { 93 callingAttributionTag = context.getAttributionTag(); 94 } 95 return new LocalCallingIdentity(context, Binder.getCallingPid(), Binder.getCallingUid(), 96 callingPackage, callingAttributionTag); 97 } 98 fromExternal(Context context, int uid)99 public static LocalCallingIdentity fromExternal(Context context, int uid) { 100 final String[] sharedPackageNames = context.getPackageManager().getPackagesForUid(uid); 101 if (sharedPackageNames == null || sharedPackageNames.length == 0) { 102 throw new IllegalArgumentException("UID " + uid + " has no associated package"); 103 } 104 LocalCallingIdentity ident = fromExternal(context, uid, sharedPackageNames[0], null); 105 ident.sharedPackageNames = sharedPackageNames; 106 ident.sharedPackageNamesResolved = true; 107 if (uid == Process.SHELL_UID) { 108 // This is useful for debugging/testing/development 109 if (SystemProperties.getBoolean("persist.sys.fuse.shell.redaction-needed", false)) { 110 ident.hasPermission |= PERMISSION_IS_REDACTION_NEEDED; 111 ident.hasPermissionResolved = PERMISSION_IS_REDACTION_NEEDED; 112 } 113 } 114 return ident; 115 } 116 fromExternal(Context context, int uid, String packageName, @Nullable String attributionTag)117 public static LocalCallingIdentity fromExternal(Context context, int uid, String packageName, 118 @Nullable String attributionTag) { 119 return new LocalCallingIdentity(context, -1, uid, packageName, attributionTag); 120 } 121 fromSelf(Context context)122 public static LocalCallingIdentity fromSelf(Context context) { 123 final LocalCallingIdentity ident = new LocalCallingIdentity( 124 context, 125 android.os.Process.myPid(), 126 android.os.Process.myUid(), 127 context.getOpPackageName(), 128 context.getAttributionTag()); 129 130 ident.packageName = ident.packageNameUnchecked; 131 ident.packageNameResolved = true; 132 // Use ident.attributionTag from context, hence no change 133 ident.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; 134 ident.targetSdkVersionResolved = true; 135 ident.hasPermission = ~(PERMISSION_IS_LEGACY_GRANTED | PERMISSION_IS_LEGACY_WRITE 136 | PERMISSION_IS_LEGACY_READ | PERMISSION_IS_REDACTION_NEEDED 137 | PERMISSION_IS_SHELL | PERMISSION_IS_DELEGATOR); 138 ident.hasPermissionResolved = ~0; 139 return ident; 140 } 141 142 private String packageName; 143 private boolean packageNameResolved; 144 getPackageName()145 public String getPackageName() { 146 if (!packageNameResolved) { 147 packageName = getPackageNameInternal(); 148 packageNameResolved = true; 149 } 150 return packageName; 151 } 152 getPackageNameInternal()153 private String getPackageNameInternal() { 154 // Verify that package name is actually owned by UID 155 context.getSystemService(AppOpsManager.class) 156 .checkPackage(uid, packageNameUnchecked); 157 return packageNameUnchecked; 158 } 159 160 private String[] sharedPackageNames; 161 private boolean sharedPackageNamesResolved; 162 getSharedPackageNames()163 public String[] getSharedPackageNames() { 164 if (!sharedPackageNamesResolved) { 165 sharedPackageNames = getSharedPackageNamesInternal(); 166 sharedPackageNamesResolved = true; 167 } 168 return sharedPackageNames; 169 } 170 getSharedPackageNamesInternal()171 private String[] getSharedPackageNamesInternal() { 172 final String[] packageNames = context.getPackageManager().getPackagesForUid(uid); 173 return (packageNames != null) ? packageNames : new String[0]; 174 } 175 176 private int targetSdkVersion; 177 private boolean targetSdkVersionResolved; 178 getTargetSdkVersion()179 public int getTargetSdkVersion() { 180 if (!targetSdkVersionResolved) { 181 targetSdkVersion = getTargetSdkVersionInternal(); 182 targetSdkVersionResolved = true; 183 } 184 return targetSdkVersion; 185 } 186 getTargetSdkVersionInternal()187 private int getTargetSdkVersionInternal() { 188 try { 189 final ApplicationInfo ai = context.getPackageManager() 190 .getApplicationInfo(getPackageName(), 0); 191 if (ai != null) { 192 return ai.targetSdkVersion; 193 } 194 } catch (NameNotFoundException ignored) { 195 } 196 return Build.VERSION_CODES.CUR_DEVELOPMENT; 197 } 198 199 public static final int PERMISSION_IS_SELF = 1 << 0; 200 public static final int PERMISSION_IS_SHELL = 1 << 1; 201 public static final int PERMISSION_IS_MANAGER = 1 << 2; 202 public static final int PERMISSION_IS_DELEGATOR = 1 << 3; 203 204 public static final int PERMISSION_IS_REDACTION_NEEDED = 1 << 8; 205 public static final int PERMISSION_IS_LEGACY_GRANTED = 1 << 9; 206 public static final int PERMISSION_IS_LEGACY_READ = 1 << 10; 207 public static final int PERMISSION_IS_LEGACY_WRITE = 1 << 11; 208 209 public static final int PERMISSION_READ_AUDIO = 1 << 16; 210 public static final int PERMISSION_READ_VIDEO = 1 << 17; 211 public static final int PERMISSION_READ_IMAGES = 1 << 18; 212 public static final int PERMISSION_WRITE_AUDIO = 1 << 19; 213 public static final int PERMISSION_WRITE_VIDEO = 1 << 20; 214 public static final int PERMISSION_WRITE_IMAGES = 1 << 21; 215 216 private int hasPermission; 217 private int hasPermissionResolved; 218 hasPermission(int permission)219 public boolean hasPermission(int permission) { 220 if ((hasPermissionResolved & permission) == 0) { 221 if (hasPermissionInternal(permission)) { 222 hasPermission |= permission; 223 } 224 hasPermissionResolved |= permission; 225 } 226 return (hasPermission & permission) != 0; 227 } 228 hasPermissionInternal(int permission)229 private boolean hasPermissionInternal(int permission) { 230 // While we're here, enforce any broad user-level restrictions 231 if ((uid == Process.SHELL_UID) && context.getSystemService(UserManager.class) 232 .hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) { 233 throw new SecurityException( 234 "Shell user cannot access files for user " + UserHandle.myUserId()); 235 } 236 237 switch (permission) { 238 case PERMISSION_IS_SELF: 239 return checkPermissionSelf(context, pid, uid); 240 case PERMISSION_IS_SHELL: 241 return checkPermissionShell(context, pid, uid); 242 case PERMISSION_IS_MANAGER: 243 return checkPermissionManager(context, pid, uid, getPackageName(), attributionTag); 244 case PERMISSION_IS_DELEGATOR: 245 return checkPermissionDelegator(context, pid, uid); 246 247 case PERMISSION_IS_REDACTION_NEEDED: 248 return isRedactionNeededInternal(); 249 case PERMISSION_IS_LEGACY_GRANTED: 250 return isLegacyStorageGranted(); 251 case PERMISSION_IS_LEGACY_READ: 252 return isLegacyReadInternal(); 253 case PERMISSION_IS_LEGACY_WRITE: 254 return isLegacyWriteInternal(); 255 256 case PERMISSION_READ_AUDIO: 257 return checkPermissionReadAudio( 258 context, pid, uid, getPackageName(), attributionTag); 259 case PERMISSION_READ_VIDEO: 260 return checkPermissionReadVideo( 261 context, pid, uid, getPackageName(), attributionTag); 262 case PERMISSION_READ_IMAGES: 263 return checkPermissionReadImages( 264 context, pid, uid, getPackageName(), attributionTag); 265 case PERMISSION_WRITE_AUDIO: 266 return checkPermissionWriteAudio( 267 context, pid, uid, getPackageName(), attributionTag); 268 case PERMISSION_WRITE_VIDEO: 269 return checkPermissionWriteVideo( 270 context, pid, uid, getPackageName(), attributionTag); 271 case PERMISSION_WRITE_IMAGES: 272 return checkPermissionWriteImages( 273 context, pid, uid, getPackageName(), attributionTag); 274 default: 275 return false; 276 } 277 } 278 isLegacyStorageGranted()279 private boolean isLegacyStorageGranted() { 280 boolean defaultScopedStorage = CompatChanges.isChangeEnabled( 281 DEFAULT_SCOPED_STORAGE, getPackageName(), UserHandle.getUserHandleForUid(uid)); 282 boolean forceEnableScopedStorage = CompatChanges.isChangeEnabled( 283 FORCE_ENABLE_SCOPED_STORAGE, getPackageName(), UserHandle.getUserHandleForUid(uid)); 284 285 // if Scoped Storage is strictly enforced, the app does *not* have legacy storage access 286 if (isScopedStorageEnforced(defaultScopedStorage, forceEnableScopedStorage)) { 287 return false; 288 } 289 // if Scoped Storage is strictly disabled, the app has legacy storage access 290 if (isScopedStorageDisabled(defaultScopedStorage, forceEnableScopedStorage)) { 291 return true; 292 } 293 294 return checkIsLegacyStorageGranted(context, uid, getPackageName()); 295 } 296 isScopedStorageEnforced(boolean defaultScopedStorage, boolean forceEnableScopedStorage)297 private boolean isScopedStorageEnforced(boolean defaultScopedStorage, 298 boolean forceEnableScopedStorage) { 299 return defaultScopedStorage && forceEnableScopedStorage; 300 } 301 isScopedStorageDisabled(boolean defaultScopedStorage, boolean forceEnableScopedStorage)302 private boolean isScopedStorageDisabled(boolean defaultScopedStorage, 303 boolean forceEnableScopedStorage) { 304 return !defaultScopedStorage && !forceEnableScopedStorage; 305 } 306 isLegacyWriteInternal()307 private boolean isLegacyWriteInternal() { 308 return hasPermission(PERMISSION_IS_LEGACY_GRANTED) 309 && checkPermissionWriteStorage(context, pid, uid, getPackageName(), attributionTag); 310 } 311 isLegacyReadInternal()312 private boolean isLegacyReadInternal() { 313 return hasPermission(PERMISSION_IS_LEGACY_GRANTED) 314 && checkPermissionReadStorage(context, pid, uid, getPackageName(), attributionTag); 315 } 316 317 /** System internals or callers holding permission have no redaction */ isRedactionNeededInternal()318 private boolean isRedactionNeededInternal() { 319 if (hasPermission(PERMISSION_IS_SELF) || hasPermission(PERMISSION_IS_SHELL)) { 320 return false; 321 } 322 323 if (context.checkPermission(ACCESS_MEDIA_LOCATION, pid, uid) == PERMISSION_DENIED 324 || context.getSystemService(AppOpsManager.class).noteProxyOpNoThrow( 325 permissionToOp(ACCESS_MEDIA_LOCATION), getPackageName(), uid, attributionTag, null) 326 != MODE_ALLOWED) { 327 return true; 328 } 329 330 return false; 331 } 332 333 private LongArray ownedIds = new LongArray(); 334 isOwned(long id)335 public boolean isOwned(long id) { 336 return ownedIds.indexOf(id) != -1; 337 } 338 setOwned(long id, boolean owned)339 public void setOwned(long id, boolean owned) { 340 final int index = ownedIds.indexOf(id); 341 if (owned) { 342 if (index == -1) { 343 ownedIds.add(id); 344 } 345 } else { 346 if (index != -1) { 347 ownedIds.remove(index); 348 } 349 } 350 } 351 352 private ArrayMap<String, Long> rowIdOfDeletedPaths = new ArrayMap<>(); 353 addDeletedRowId(@onNull String path, long id)354 public void addDeletedRowId(@NonNull String path, long id) { 355 rowIdOfDeletedPaths.put(path, id); 356 } 357 removeDeletedRowId(long id)358 public boolean removeDeletedRowId(long id) { 359 int index = rowIdOfDeletedPaths.indexOfValue(id); 360 final boolean isDeleted = index > -1; 361 while (index > -1) { 362 rowIdOfDeletedPaths.removeAt(index); 363 index = rowIdOfDeletedPaths.indexOfValue(id); 364 } 365 return isDeleted; 366 } 367 getDeletedRowId(@onNull String path)368 public long getDeletedRowId(@NonNull String path) { 369 return rowIdOfDeletedPaths.getOrDefault(path, UNKNOWN_ROW_ID); 370 } 371 } 372