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