Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 859856d8 authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Jeff Sharkey
Browse files

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
parent 13fa4652
Loading
Loading
Loading
Loading
+117 −0
Original line number Diff line number Diff line
@@ -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(
+7 −19
Original line number Diff line number Diff line
@@ -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,22 +595,12 @@ 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 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)
                : null;
                MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context);
    }

    private void setFilterColumnsList(int type) {
+4 −2
Original line number Diff line number Diff line
@@ -33,8 +33,10 @@
        android:protectionLevel="signature" />

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <!-- Used to read wallpaper -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

    <!-- Used to read storage for all users -->
    <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
+3 −16
Original line number Diff line number Diff line
@@ -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(),