From 859856d8d69ebcbe2f32d27a2884a58ed4cae802 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Mon, 25 Mar 2019 11:44:11 -0600 Subject: [PATCH] Unify media permissions enforcement in framework. This opens the door to the same consistent logic being shared by anyone across the OS who wants to enforce storage permissions. Bug: 126788266 Test: atest --test-mapping packages/apps/MediaProvider Exempted-From-Owner-Approval: Trivial permissions refactoring Change-Id: I3107425f8dafa6ba05918bb67c3c0cb5d3899657 --- .../android/os/storage/StorageManager.java | 117 ++++++++++++++++++ media/java/android/media/RingtoneManager.java | 26 ++-- packages/SystemUI/AndroidManifest.xml | 6 +- .../wallpaper/WallpaperManagerService.java | 19 +-- 4 files changed, 131 insertions(+), 37 deletions(-) diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 3e60d5119fb91..f6fcdb0fcc760 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -16,7 +16,21 @@ package android.os.storage; +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static android.Manifest.permission.READ_MEDIA_AUDIO; +import static android.Manifest.permission.READ_MEDIA_IMAGES; +import static android.Manifest.permission.READ_MEDIA_VIDEO; +import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE; +import static android.app.AppOpsManager.OP_READ_MEDIA_AUDIO; +import static android.app.AppOpsManager.OP_READ_MEDIA_IMAGES; +import static android.app.AppOpsManager.OP_READ_MEDIA_VIDEO; +import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE; +import static android.app.AppOpsManager.OP_WRITE_MEDIA_AUDIO; +import static android.app.AppOpsManager.OP_WRITE_MEDIA_IMAGES; +import static android.app.AppOpsManager.OP_WRITE_MEDIA_VIDEO; import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.annotation.BytesLong; import android.annotation.IntDef; @@ -33,6 +47,7 @@ import android.annotation.WorkerThread; import android.app.Activity; import android.app.ActivityThread; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -1635,6 +1650,108 @@ public class StorageManager { } } + /** + * Check that given app holds both permission and appop. + * + * @return {@code null} if the permission and appop are held, otherwise + * returns a string indicating why access was denied. + */ + private boolean checkPermissionAndAppOp(boolean enforce, int pid, int uid, String packageName, + String permission, int op) { + if (mContext.checkPermission(permission, pid, uid) != PERMISSION_GRANTED) { + if (enforce) { + throw new SecurityException( + "Permission " + permission + " denied for package " + packageName); + } else { + return false; + } + } + + final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); + final int mode = appOps.noteOpNoThrow(op, uid, packageName); + switch (mode) { + case AppOpsManager.MODE_ALLOWED: + return true; + case AppOpsManager.MODE_DEFAULT: + case AppOpsManager.MODE_IGNORED: + case AppOpsManager.MODE_ERRORED: + if (enforce) { + throw new SecurityException("Op " + AppOpsManager.opToName(op) + " " + + AppOpsManager.modeToName(mode) + " for package " + packageName); + } else { + return false; + } + default: + throw new IllegalStateException( + AppOpsManager.opToName(op) + " has unknown mode " + + AppOpsManager.modeToName(mode)); + } + } + + // Callers must hold both the old and new permissions, so that we can + // handle obscure cases like when an app targets Q but was installed on + // a device that was originally running on P before being upgraded to Q. + + /** {@hide} */ + public boolean checkPermissionReadAudio(boolean enforce, + int pid, int uid, String packageName) { + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) return false; + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + READ_MEDIA_AUDIO, OP_READ_MEDIA_AUDIO)) return false; + return true; + } + + /** {@hide} */ + public boolean checkPermissionWriteAudio(boolean enforce, + int pid, int uid, String packageName) { + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) return false; + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + READ_MEDIA_AUDIO, OP_WRITE_MEDIA_AUDIO)) return false; + return true; + } + + /** {@hide} */ + public boolean checkPermissionReadVideo(boolean enforce, + int pid, int uid, String packageName) { + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) return false; + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + READ_MEDIA_VIDEO, OP_READ_MEDIA_VIDEO)) return false; + return true; + } + + /** {@hide} */ + public boolean checkPermissionWriteVideo(boolean enforce, + int pid, int uid, String packageName) { + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) return false; + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + READ_MEDIA_VIDEO, OP_WRITE_MEDIA_VIDEO)) return false; + return true; + } + + /** {@hide} */ + public boolean checkPermissionReadImages(boolean enforce, + int pid, int uid, String packageName) { + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) return false; + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + READ_MEDIA_IMAGES, OP_READ_MEDIA_IMAGES)) return false; + return true; + } + + /** {@hide} */ + public boolean checkPermissionWriteImages(boolean enforce, + int pid, int uid, String packageName) { + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) return false; + if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, + READ_MEDIA_IMAGES, OP_WRITE_MEDIA_IMAGES)) return false; + return true; + } + /** {@hide} */ @VisibleForTesting public @NonNull ParcelFileDescriptor openProxyFileDescriptor( diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index e0e657bb4474d..fefb0d7a55bf6 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -29,7 +29,6 @@ import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; -import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.AssetFileDescriptor; @@ -40,7 +39,6 @@ import android.os.Environment; import android.os.FileUtils; import android.os.IBinder; import android.os.ParcelFileDescriptor; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -597,24 +595,14 @@ public class RingtoneManager { @UnsupportedAppUsage private Cursor getMediaRingtones(Context context) { - if (PackageManager.PERMISSION_GRANTED != context.checkPermission( - android.Manifest.permission.READ_EXTERNAL_STORAGE, - Process.myPid(), Process.myUid())) { - Log.w(TAG, "No READ_EXTERNAL_STORAGE permission, ignoring ringtones on ext storage"); - return null; - } - // Get the external media cursor. First check to see if it is mounted. - final String status = Environment.getExternalStorageState(); - - return (status.equals(Environment.MEDIA_MOUNTED) || - status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) - ? query( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns), null, - MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context) - : null; + // MediaStore now returns ringtones on other storage devices, even when + // we don't have storage or audio permissions + return query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, + constructBooleanTrueWhereClause(mFilterColumns), null, + MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context); } - + private void setFilterColumnsList(int type) { List columns = mFilterColumns; columns.clear(); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index d654f5aa23e01..938c457cf33f3 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -33,8 +33,10 @@ android:protectionLevel="signature" /> - - + + + + diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index e53fde96e844e..5502bb98564fc 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -77,6 +77,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; import android.service.wallpaper.IWallpaperService; @@ -2089,28 +2090,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void enforceCallingOrSelfPermissionAndAppOp(String permission, final String callingPkg, - final int callingUid, String message) { - mContext.enforceCallingOrSelfPermission(permission, message); - - final String opName = AppOpsManager.permissionToOp(permission); - if (opName != null) { - final int appOpMode = mAppOpsManager.noteOp(opName, callingUid, callingPkg); - if (appOpMode != AppOpsManager.MODE_ALLOWED) { - throw new SecurityException( - message + ": " + callingPkg + " is not allowed to " + permission); - } - } - } - @Override public ParcelFileDescriptor getWallpaper(String callingPkg, IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId) { final int hasPrivilege = mContext.checkCallingOrSelfPermission( android.Manifest.permission.READ_WALLPAPER_INTERNAL); if (hasPrivilege != PackageManager.PERMISSION_GRANTED) { - enforceCallingOrSelfPermissionAndAppOp(android.Manifest.permission.READ_EXTERNAL_STORAGE, - callingPkg, Binder.getCallingUid(), "read wallpaper"); + mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true, + Binder.getCallingPid(), Binder.getCallingUid(), callingPkg); } wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),