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