summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Azhara Assanova <azharaa@google.com> 2023-11-15 14:03:02 +0000
committer Azhara Assanova <azharaa@google.com> 2024-01-10 11:52:17 +0000
commiteeccbd76c4dac5b2669b76dcd153d14774468f8c (patch)
tree0060f0ad6131abf8102200503daa702bb87de145
parente4584f38917b5500e0f6f6afd88be59b4bd0df7a (diff)
Add new API Context#checkContentUriPermission
Currently, apps can't check if an app has access to a content URI. That is because the existing checkUriPermission() API is limited to checking permissions via URI grant flags. Hence, this change adds a new API that focuses on content URIs and on top of grant flags, it checks for general access to the URI's content provider. In addition to this API, we'll be introducing Activity APIs and a Manifest attribute to help apps with checking content URI permissions on activity callers. Finally, to minimise code duplication, we merged checkUriPermission()'s implementation in ActivityManagerService and UriGrantsManagerInternal. Bug: 293467489 Test: atest WmTests:ActivityStarterTests Test: atest FrameworksServicesTests:com.android.server.uri Test: atest CtsWindowManagerDeviceActivity:ActivityStarterTests Test: atest CtsWindowManagerDeviceWindow:CrossAppDragAndDropTests Test: atest AppEnumerationTests Test: atest CtsContentTestCases:android.content.cts.ContextTest Change-Id: I61a0574e3979702ed32f4d926c664f134046f555
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/app/ContextImpl.java11
-rw-r--r--core/java/android/app/IActivityManager.aidl1
-rw-r--r--core/java/android/content/Context.java43
-rw-r--r--core/java/android/content/ContextWrapper.java7
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java25
-rw-r--r--services/core/java/com/android/server/uri/UriGrantsManagerInternal.java12
-rw-r--r--services/core/java/com/android/server/uri/UriGrantsManagerService.java78
-rw-r--r--services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java9
9 files changed, 165 insertions, 22 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index c7b921c8f6d5..7c9e30f10f11 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10379,6 +10379,7 @@ package android.content {
method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
method @CheckResult(suggest="#enforceCallingUriPermission(Uri,int,String)") public abstract int checkCallingUriPermission(android.net.Uri, int);
method @NonNull public int[] checkCallingUriPermissions(@NonNull java.util.List<android.net.Uri>, int);
+ method @FlaggedApi("android.security.content_uri_permission_apis") public int checkContentUriPermissionFull(@NonNull android.net.Uri, int, int, int);
method @CheckResult(suggest="#enforcePermission(String,int,int,String)") public abstract int checkPermission(@NonNull String, int, int);
method public abstract int checkSelfPermission(@NonNull String);
method @CheckResult(suggest="#enforceUriPermission(Uri,int,int,String)") public abstract int checkUriPermission(android.net.Uri, int, int, int);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index edeec77d48fe..ed00d9c1ddde 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2409,6 +2409,17 @@ class ContextImpl extends Context {
}
}
+ @Override
+ public int checkContentUriPermissionFull(Uri uri, int pid, int uid, int modeFlags) {
+ try {
+ return ActivityManager.getService().checkContentUriPermissionFull(
+ ContentProvider.getUriWithoutUserId(uri), pid, uid, modeFlags,
+ resolveUserId(uri));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
@Override
public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 260e9859c72d..c636f8a64dfb 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -274,6 +274,7 @@ interface IActivityManager {
int getProcessLimit();
int checkUriPermission(in Uri uri, int pid, int uid, int mode, int userId,
in IBinder callerToken);
+ int checkContentUriPermissionFull(in Uri uri, int pid, int uid, int mode, int userId);
int[] checkUriPermissions(in List<Uri> uris, int pid, int uid, int mode, int userId,
in IBinder callerToken);
void grantUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fa76e3976a58..c69790d71910 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6734,7 +6734,7 @@ public abstract class Context {
@Intent.AccessUriMode int modeFlags);
/**
- * Determine whether a particular process and user ID has been granted
+ * Determine whether a particular process and uid has been granted
* permission to access a specific URI. This only checks for permissions
* that have been explicitly granted -- if the given process/uid has
* more general access to the URI's content provider then this check will
@@ -6758,7 +6758,38 @@ public abstract class Context {
@Intent.AccessUriMode int modeFlags);
/**
- * Determine whether a particular process and user ID has been granted
+ * Determine whether a particular process and uid has been granted
+ * permission to access a specific content URI.
+ *
+ * <p>Unlike {@link #checkUriPermission(Uri, int, int, int)}, this method
+ * checks for general access to the URI's content provider, as well as
+ * explicitly granted permissions.</p>
+ *
+ * <p>Note, this check will throw an {@link IllegalArgumentException}
+ * for non-content URIs.</p>
+ *
+ * @param uri The content uri that is being checked.
+ * @param pid (Optional) The process ID being checked against. If the
+ * pid is unknown, pass -1.
+ * @param uid The UID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ * @param modeFlags The access modes to check.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the given
+ * pid/uid is allowed to access that uri, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #checkUriPermission(Uri, int, int, int)
+ */
+ @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+ @PackageManager.PermissionResult
+ public int checkContentUriPermissionFull(@NonNull Uri uri, int pid, int uid,
+ @Intent.AccessUriMode int modeFlags) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
+ * Determine whether a particular process and uid has been granted
* permission to access a list of URIs. This only checks for permissions
* that have been explicitly granted -- if the given process/uid has
* more general access to the URI's content provider then this check will
@@ -6794,7 +6825,7 @@ public abstract class Context {
@Intent.AccessUriMode int modeFlags, IBinder callerToken);
/**
- * Determine whether the calling process and user ID has been
+ * Determine whether the calling process and uid has been
* granted permission to access a specific URI. This is basically
* the same as calling {@link #checkUriPermission(Uri, int, int,
* int)} with the pid and uid returned by {@link
@@ -6817,7 +6848,7 @@ public abstract class Context {
public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
/**
- * Determine whether the calling process and user ID has been
+ * Determine whether the calling process and uid has been
* granted permission to access a list of URIs. This is basically
* the same as calling {@link #checkUriPermissions(List, int, int, int)}
* with the pid and uid returned by {@link
@@ -6911,7 +6942,7 @@ public abstract class Context {
@Intent.AccessUriMode int modeFlags);
/**
- * If a particular process and user ID has not been granted
+ * If a particular process and uid has not been granted
* permission to access a specific URI, throw {@link
* SecurityException}. This only checks for permissions that have
* been explicitly granted -- if the given process/uid has more
@@ -6931,7 +6962,7 @@ public abstract class Context {
Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags, String message);
/**
- * If the calling process and user ID has not been granted
+ * If the calling process and uid has not been granted
* permission to access a specific URI, throw {@link
* SecurityException}. This is basically the same as calling
* {@link #enforceUriPermission(Uri, int, int, int, String)} with
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 0a8029c44d73..e0cf0a5f8178 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -17,6 +17,7 @@
package android.content;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -1003,6 +1004,12 @@ public class ContextWrapper extends Context {
return mBase.checkUriPermission(uri, pid, uid, modeFlags);
}
+ @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+ @Override
+ public int checkContentUriPermissionFull(@NonNull Uri uri, int pid, int uid, int modeFlags) {
+ return mBase.checkContentUriPermissionFull(uri, pid, uid, modeFlags);
+ }
+
@NonNull
@Override
public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index fddb5707b78e..fb2eb986ca5b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6525,7 +6525,24 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public int checkUriPermission(Uri uri, int pid, int uid,
final int modeFlags, int userId, IBinder callerToken) {
- enforceNotIsolatedCaller("checkUriPermission");
+ return checkUriPermission(uri, pid, uid, modeFlags, userId,
+ /* isFullAccessForContentUri */ false, "checkUriPermission");
+ }
+
+ /**
+ * @param uri This uri must NOT contain an embedded userId.
+ * @param userId The userId in which the uri is to be resolved.
+ */
+ @Override
+ public int checkContentUriPermissionFull(Uri uri, int pid, int uid,
+ final int modeFlags, int userId) {
+ return checkUriPermission(uri, pid, uid, modeFlags, userId,
+ /* isFullAccessForContentUri */ true, "checkContentUriPermissionFull");
+ }
+
+ private int checkUriPermission(Uri uri, int pid, int uid,
+ final int modeFlags, int userId, boolean isFullAccessForContentUri, String methodName) {
+ enforceNotIsolatedCaller(methodName);
// Our own process gets to do everything.
if (pid == MY_PID) {
@@ -6536,8 +6553,10 @@ public class ActivityManagerService extends IActivityManager.Stub
return PackageManager.PERMISSION_DENIED;
}
}
- return mUgmInternal.checkUriPermission(new GrantUri(userId, uri, modeFlags), uid, modeFlags)
- ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
+ boolean granted = mUgmInternal.checkUriPermission(new GrantUri(userId, uri, modeFlags), uid,
+ modeFlags, isFullAccessForContentUri);
+
+ return granted ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
}
@Override
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index e54b40eb334c..03c75e018dab 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -37,7 +37,17 @@ public interface UriGrantsManagerInternal {
void revokeUriPermission(String targetPackage, int callingUid,
GrantUri grantUri, final int modeFlags);
- boolean checkUriPermission(GrantUri grantUri, int uid, final int modeFlags);
+ /**
+ * Check if the uid has permission to the URI in grantUri.
+ *
+ * @param isFullAccessForContentUri If true, the URI has to be a content URI
+ * and the method will consider full access.
+ * Otherwise, the method will only consider
+ * URI grants.
+ */
+ boolean checkUriPermission(GrantUri grantUri, int uid, int modeFlags,
+ boolean isFullAccessForContentUri);
+
int checkGrantUriPermission(
int callingUid, String targetPkg, Uri uri, int modeFlags, int userId);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index e501b9dc9959..ce2cbed0c9a9 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -25,6 +25,7 @@ import static android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
@@ -1103,7 +1104,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements
*/
private int checkGrantUriPermissionUnlocked(int callingUid, String targetPkg, GrantUri grantUri,
int modeFlags, int lastTargetUid) {
- if (!Intent.isAccessUriMode(modeFlags)) {
+ if (!isContentUriWithAccessModeFlags(grantUri, modeFlags,
+ /* logAction */ "grant URI permission")) {
return -1;
}
@@ -1111,12 +1113,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements
if (DEBUG) Slog.v(TAG, "Checking grant " + targetPkg + " permission to " + grantUri);
}
- // If this is not a content: uri, we can't do anything with it.
- if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
- if (DEBUG) Slog.v(TAG, "Can't grant URI permission for non-content URI: " + grantUri);
- return -1;
- }
-
// Bail early if system is trying to hand out permissions directly; it
// must always grant permissions on behalf of someone explicit.
final int callingAppId = UserHandle.getAppId(callingUid);
@@ -1137,7 +1133,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements
final String authority = grantUri.uri.getAuthority();
final ProviderInfo pi = getProviderInfo(authority, grantUri.sourceUserId,
- MATCH_DEBUG_TRIAGED_MISSING, callingUid);
+ MATCH_DIRECT_BOOT_AUTO, callingUid);
if (pi == null) {
Slog.w(TAG, "No content provider found for permission check: " +
grantUri.uri.toSafeString());
@@ -1285,6 +1281,65 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements
return targetUid;
}
+ private boolean isContentUriWithAccessModeFlags(GrantUri grantUri, int modeFlags,
+ String logAction) {
+ if (!Intent.isAccessUriMode(modeFlags)) {
+ if (DEBUG) Slog.v(TAG, "Mode flags are not access URI mode flags: " + modeFlags);
+ return false;
+ }
+
+ if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
+ if (DEBUG) {
+ Slog.v(TAG, "Can't " + logAction + " on non-content URI: " + grantUri);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /** Check if the uid has permission to the content URI in grantUri. */
+ private boolean checkContentUriPermissionFullUnlocked(GrantUri grantUri, int uid,
+ int modeFlags) {
+ if (uid < 0) {
+ throw new IllegalArgumentException("Uid must be positive for the content URI "
+ + "permission check of " + grantUri.uri.toSafeString());
+ }
+
+ if (!isContentUriWithAccessModeFlags(grantUri, modeFlags,
+ /* logAction */ "check content URI permission")) {
+ throw new IllegalArgumentException("The URI must be a content URI and the mode "
+ + "flags must be at least read and/or write for the content URI permission "
+ + "check of " + grantUri.uri.toSafeString());
+ }
+
+ final int appId = UserHandle.getAppId(uid);
+ if ((appId == SYSTEM_UID) || (appId == ROOT_UID)) {
+ return true;
+ }
+
+ // Retrieve the URI's content provider
+ final String authority = grantUri.uri.getAuthority();
+ ProviderInfo pi = getProviderInfo(authority, grantUri.sourceUserId, MATCH_DIRECT_BOOT_AUTO,
+ uid);
+
+ if (pi == null) {
+ Slog.w(TAG, "No content provider found for content URI permission check: "
+ + grantUri.uri.toSafeString());
+ return false;
+ }
+
+ // Check if it has general permission to the URI's content provider
+ if (checkHoldingPermissionsUnlocked(pi, grantUri, uid, modeFlags)) {
+ return true;
+ }
+
+ // Check if it has explicitly granted permissions to the URI
+ synchronized (mLock) {
+ return checkUriPermissionLocked(grantUri, uid, modeFlags);
+ }
+ }
+
/**
* @param userId The userId in which the uri is to be resolved.
*/
@@ -1482,7 +1537,12 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements
}
@Override
- public boolean checkUriPermission(GrantUri grantUri, int uid, int modeFlags) {
+ public boolean checkUriPermission(GrantUri grantUri, int uid, int modeFlags,
+ boolean isFullAccessForContentUri) {
+ if (isFullAccessForContentUri) {
+ return UriGrantsManagerService.this.checkContentUriPermissionFullUnlocked(grantUri,
+ uid, modeFlags);
+ }
synchronized (mLock) {
return UriGrantsManagerService.this.checkUriPermissionLocked(grantUri, uid,
modeFlags);
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
index 769ec5fac023..321858685e38 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
@@ -345,15 +345,18 @@ public class UriGrantsManagerServiceTest {
intent, UID_PRIMARY_CAMERA, PKG_SOCIAL, USER_PRIMARY), service);
// Verify that everything is good with the world
- assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+ assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ,
+ /* isFullAccessForContentUri */ false));
// Finish activity; service should hold permission
activity.removeUriPermissions();
- assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+ assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ,
+ /* isFullAccessForContentUri */ false));
// And finishing service should wrap things up
service.removeUriPermissions();
- assertFalse(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+ assertFalse(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ,
+ /* isFullAccessForContentUri */ false));
}
@Test