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
This commit is contained in:
Jeff Sharkey
2019-03-25 11:44:11 -06:00
committed by Jeff Sharkey
parent 13fa465291
commit 859856d8d6
4 changed files with 131 additions and 37 deletions

View File

@@ -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(

View File

@@ -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<String> columns = mFilterColumns;
columns.clear();

View File

@@ -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" />

View File

@@ -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(),