From 08da7a1143b0c9cfb703971d882e0886bbd7d9de Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Sun, 11 Aug 2013 20:53:18 -0700 Subject: [PATCH] API to discover granted Uri permissions. Now that granted Uri permissions can be persisted across reboots, offer APIs to discover them. Returns incoming or outgoing grants matching the requested flags and mask. Add helper method to discover "open" documents using this new API and filtering for non-documents. Require that callers own at least of the filtering packages to avoid exposing all grants. Switch internal grant tracking to use ArrayMap. Change-Id: I0a755f221d0d160b411f8d3cfc48279b64345733 --- api/current.txt | 6 ++ .../android/app/ActivityManagerNative.java | 31 +++++++++ core/java/android/app/IActivityManager.java | 5 ++ .../java/android/content/ContentResolver.java | 57 ++++++++++++++++- .../android/provider/DocumentsContract.java | 46 ++++++++++++- .../server/am/ActivityManagerService.java | 64 ++++++++++++++++--- 6 files changed, 194 insertions(+), 15 deletions(-) diff --git a/api/current.txt b/api/current.txt index 3baeea3251d2d..7744ecc803415 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5587,8 +5587,10 @@ package android.content { method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public static deprecated android.content.SyncInfo getCurrentSync(); method public static java.util.List getCurrentSyncs(); + method public android.net.Uri[] getIncomingUriPermissionGrants(int, int); method public static int getIsSyncable(android.accounts.Account, java.lang.String); method public static boolean getMasterSyncAutomatically(); + method public android.net.Uri[] getOutgoingUriPermissionGrants(int, int); method public static java.util.List getPeriodicSyncs(android.accounts.Account, java.lang.String); method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String); method public static android.content.SyncAdapterType[] getSyncAdapterTypes(); @@ -20320,6 +20322,7 @@ package android.provider { method public static android.net.Uri buildSearchUri(java.lang.String, java.lang.String, java.lang.String, java.lang.String); method public static android.net.Uri buildSearchUri(android.net.Uri, java.lang.String); method public static java.lang.String getDocId(android.net.Uri); + method public static android.net.Uri[] getOpenDocuments(android.content.Context); method public static java.lang.String getRootId(android.net.Uri); method public static java.lang.String getSearchQuery(android.net.Uri); method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point); @@ -23335,11 +23338,14 @@ package android.test.mock { method public java.io.File getDatabasePath(java.lang.String); method public java.io.File getDir(java.lang.String, int); method public java.io.File getExternalCacheDir(); + method public java.io.File[] getExternalCacheDirs(); method public java.io.File getExternalFilesDir(java.lang.String); + method public java.io.File[] getExternalFilesDirs(java.lang.String); method public java.io.File getFileStreamPath(java.lang.String); method public java.io.File getFilesDir(); method public android.os.Looper getMainLooper(); method public java.io.File getObbDir(); + method public java.io.File[] getObbDirs(); method public java.lang.String getPackageCodePath(); method public android.content.pm.PackageManager getPackageManager(); method public java.lang.String getPackageName(); diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index cc964c29721f3..6a295521a2aa9 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1980,6 +1980,19 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } + + case GET_GRANTED_URI_PERMISSIONS_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + final String sourcePackage = data.readString(); + final String targetPackage = data.readString(); + final int modeFlags = data.readInt(); + final int modeMask = data.readInt(); + final Uri[] uris = getGrantedUriPermissions( + sourcePackage, targetPackage, modeFlags, modeMask); + reply.writeNoException(); + reply.writeParcelableArray(uris, 0); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -4540,5 +4553,23 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + public Uri[] getGrantedUriPermissions( + String sourcePackage, String targetPackage, int modeFlags, int modeMask) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(sourcePackage); + data.writeString(targetPackage); + data.writeInt(modeFlags); + data.writeInt(modeMask); + mRemote.transact(GET_GRANTED_URI_PERMISSIONS_TRANSACTION, data, reply, 0); + reply.readException(); + final Uri[] uris = (Uri[]) reply.readParcelableArray(null); + data.recycle(); + reply.recycle(); + return uris; + } + private IBinder mRemote; } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 3851eb3d85817..6d1b6fc7dd04e 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -399,6 +399,10 @@ public interface IActivityManager extends IInterface { public void restart() throws RemoteException; + public Uri[] getGrantedUriPermissions( + String sourcePackage, String targetPackage, int modeFlags, int modeMask) + throws RemoteException; + /* * Private non-Binder interfaces */ @@ -680,4 +684,5 @@ public interface IActivityManager extends IInterface { int NOTIFY_ACTIVITY_DRAWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+175; int REPORT_ACTIVITY_FULLY_DRAWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+176; int RESTART_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+177; + int GET_GRANTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+178; } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 243c91a95edca..45fed2defce56 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -16,8 +16,6 @@ package android.content; -import dalvik.system.CloseGuard; - import android.accounts.Account; import android.app.ActivityManagerNative; import android.app.ActivityThread; @@ -44,7 +42,8 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; -import android.util.Pair; + +import dalvik.system.CloseGuard; import java.io.File; import java.io.FileInputStream; @@ -1388,6 +1387,58 @@ public abstract class ContentResolver { } } + /** + * Return list of all Uri permissions that have been granted to the + * calling package, and which exactly match the requested flags. For + * example, to return all Uris that the calling application has + * non-persistent read access to: + * + *
+     * getIncomingUriPermissionGrants(Intent.FLAG_GRANT_READ_URI_PERMISSION,
+     *         Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
+     * 
+ * + * @param modeFlags any combination of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}, or + * {@link Intent#FLAG_PERSIST_GRANT_URI_PERMISSION}. + * @param modeMask mask indicating which flags must match. + */ + public Uri[] getIncomingUriPermissionGrants(int modeFlags, int modeMask) { + try { + return ActivityManagerNative.getDefault() + .getGrantedUriPermissions(null, getPackageName(), modeFlags, modeMask); + } catch (RemoteException e) { + return new Uri[0]; + } + } + + /** + * Return list of all Uri permissions that have been granted from the + * calling package, and which exactly match the requested flags. For + * example, to return all Uris that the calling application has granted + * non-persistent read access to: + * + *
+     * getOutgoingUriPermissionGrants(Intent.FLAG_GRANT_READ_URI_PERMISSION,
+     *         Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
+     * 
+ * + * @param modeFlags any combination of + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}, or + * {@link Intent#FLAG_PERSIST_GRANT_URI_PERMISSION}. + * @param modeMask mask indicating which flags must match. + */ + public Uri[] getOutgoingUriPermissionGrants(int modeFlags, int modeMask) { + try { + return ActivityManagerNative.getDefault() + .getGrantedUriPermissions(getPackageName(), null, modeFlags, modeMask); + } catch (RemoteException e) { + return new Uri[0]; + } + } + /** * Start an asynchronous sync operation. If you want to monitor the progress * of the sync you may register a SyncObserver. Only values of the following diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index acaed73ada9ca..53f5d58210e06 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -19,7 +19,9 @@ package android.provider; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.res.AssetFileDescriptor; import android.database.Cursor; @@ -30,6 +32,8 @@ import android.net.Uri; import android.os.Bundle; import android.util.Log; +import com.google.android.collect.Lists; + import libcore.io.IoUtils; import java.io.IOException; @@ -201,19 +205,25 @@ public final class DocumentsContract { public static String getRootId(Uri documentUri) { final List paths = documentUri.getPathSegments(); + if (paths.size() < 2) { + throw new IllegalArgumentException("Not a root: " + documentUri); + } if (!PATH_ROOTS.equals(paths.get(0))) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Not a root: " + documentUri); } return paths.get(1); } public static String getDocId(Uri documentUri) { final List paths = documentUri.getPathSegments(); + if (paths.size() < 4) { + throw new IllegalArgumentException("Not a document: " + documentUri); + } if (!PATH_ROOTS.equals(paths.get(0))) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Not a document: " + documentUri); } if (!PATH_DOCS.equals(paths.get(2))) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Not a document: " + documentUri); } return paths.get(3); } @@ -358,6 +368,36 @@ public final class DocumentsContract { public static final String AVAILABLE_BYTES = "available_bytes"; } + /** + * Return list of all documents that the calling package has "open." These + * are Uris matching {@link DocumentsContract} to which persistent + * read/write access has been granted, usually through + * {@link Intent#ACTION_OPEN_DOCUMENT} or + * {@link Intent#ACTION_CREATE_DOCUMENT}. + * + * @see Context#grantUriPermission(String, Uri, int) + * @see ContentResolver#getIncomingUriPermissionGrants(int, int) + */ + public static Uri[] getOpenDocuments(Context context) { + final int openedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION; + final Uri[] uris = context.getContentResolver() + .getIncomingUriPermissionGrants(openedFlags, openedFlags); + + // Filter to only include document providers + final PackageManager pm = context.getPackageManager(); + final List result = Lists.newArrayList(); + for (Uri uri : uris) { + final ProviderInfo info = pm.resolveContentProvider( + uri.getAuthority(), PackageManager.GET_META_DATA); + if (info.metaData.containsKey(META_DATA_DOCUMENT_PROVIDER)) { + result.add(uri); + } + } + + return result.toArray(new Uri[result.size()]); + } + /** * Return thumbnail representing the document at the given URI. Callers are * responsible for their own caching. Given document must have diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 9dba2ed332c8f..ebc275d0bbdda 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -36,6 +36,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.MemInfoReader; @@ -705,8 +706,8 @@ public final class ActivityManagerService extends ActivityManagerNative * to {@link UriPermission#uri} to {@link UriPermission}. */ @GuardedBy("this") - private final SparseArray> - mGrantedUriPermissions = new SparseArray>(); + private final SparseArray> + mGrantedUriPermissions = new SparseArray>(); CoreSettingsObserver mCoreSettingsObserver; @@ -5446,9 +5447,9 @@ public final class ActivityManagerService extends ActivityManagerNative private UriPermission findOrCreateUriPermissionLocked( String sourcePkg, String targetPkg, int targetUid, Uri uri) { - HashMap targetUris = mGrantedUriPermissions.get(targetUid); + ArrayMap targetUris = mGrantedUriPermissions.get(targetUid); if (targetUris == null) { - targetUris = Maps.newHashMap(); + targetUris = Maps.newArrayMap(); mGrantedUriPermissions.put(targetUid, targetUris); } @@ -5467,7 +5468,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (uid == 0) { return true; } - HashMap perms = mGrantedUriPermissions.get(uid); + ArrayMap perms = mGrantedUriPermissions.get(uid); if (perms == null) return false; UriPermission perm = perms.get(uri); if (perm == null) return false; @@ -5794,7 +5795,7 @@ public final class ActivityManagerService extends ActivityManagerNative void removeUriPermissionIfNeededLocked(UriPermission perm) { if ((perm.modeFlags&(Intent.FLAG_GRANT_READ_URI_PERMISSION |Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) == 0) { - HashMap perms + ArrayMap perms = mGrantedUriPermissions.get(perm.targetUid); if (perms != null) { if (DEBUG_URI_PERMISSION) Slog.v(TAG, @@ -5837,7 +5838,7 @@ public final class ActivityManagerService extends ActivityManagerNative final int NS = SEGMENTS.size(); int N = mGrantedUriPermissions.size(); for (int i=0; i perms + ArrayMap perms = mGrantedUriPermissions.valueAt(i); Iterator it = perms.values().iterator(); toploop: @@ -6114,6 +6115,51 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override + public Uri[] getGrantedUriPermissions( + String sourcePackage, String targetPackage, int modeFlags, int modeMask) { + enforceNotIsolatedCaller("getGrantedUriPermissions"); + synchronized (this) { + // Verify that caller owns at least one of the requested packages + final int uid = Binder.getCallingUid(); + final IPackageManager pm = AppGlobals.getPackageManager(); + final String[] callerPackages; + try { + callerPackages = pm.getPackagesForUid(uid); + } catch (RemoteException e) { + throw new SecurityException("Failed to find packages for UID " + uid); + } + final boolean callerOwnsSource = sourcePackage != null + && ArrayUtils.contains(callerPackages, sourcePackage); + final boolean callerOwnsTarget = targetPackage != null + && ArrayUtils.contains(callerPackages, targetPackage); + if (!(callerOwnsSource || callerOwnsTarget)) { + throw new SecurityException("Caller " + Arrays.toString(callerPackages) + + " doesn't own " + sourcePackage + " or " + targetPackage); + } + + final ArrayList result = Lists.newArrayList(); + final int size = mGrantedUriPermissions.size(); + for (int i = 0; i < size; i++) { + final ArrayMap map = mGrantedUriPermissions.valueAt(i); + final int mapSize = map.size(); + for (int j = 0; j < mapSize; j++) { + final UriPermission perm = map.valueAt(j); + final boolean sourceMatch = sourcePackage == null + || sourcePackage.equals(perm.sourcePkg); + final boolean targetMatch = targetPackage == null + || targetPackage.equals(perm.targetPkg); + final boolean modeMatch = (perm.modeFlags & modeMask) == modeFlags; + if (sourceMatch && targetMatch && modeMatch) { + result.add(perm.uri); + } + } + } + + return result.toArray(new Uri[result.size()]); + } + } + @Override public void showWaitingForDebugger(IApplicationThread who, boolean waiting) { synchronized (this) { @@ -6830,7 +6876,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - HashMap perms = mGrantedUriPermissions.get(callingUid); + ArrayMap perms = mGrantedUriPermissions.get(callingUid); if (perms != null) { for (Map.Entry uri : perms.entrySet()) { if (uri.getKey().getAuthority().equals(cpi.authority)) { @@ -10731,7 +10777,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (dumpUid >= -1 && UserHandle.getAppId(uid) != dumpUid) { continue; } - HashMap perms + ArrayMap perms = mGrantedUriPermissions.valueAt(i); if (!printed) { if (needSep) pw.println();