Let <path-permission> block unprotected providers.
When accessing a ContentProvider that is unprotected by top-level
permissions, and caller doesn't hold a matching <path-permission>
permission, revoke the default top-level access.
This enables an otherwise unprotected provider to enforce permissions
on specific paths.
Bug: 6131916
Change-Id: Icab89f765ccd90b1acea6988b05f00877fe2c11e
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index fd968e0..86f4bc0 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import com.android.internal.R;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.ProcessStats;
@@ -55,6 +57,7 @@
import android.content.ClipData;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
@@ -4543,76 +4546,91 @@
throw new SecurityException(msg);
}
- private final boolean checkHoldingPermissionsLocked(IPackageManager pm,
- ProviderInfo pi, Uri uri, int uid, int modeFlags) {
- boolean readPerm = (modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0;
- boolean writePerm = (modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
+ /**
+ * Determine if UID is holding permissions required to access {@link Uri} in
+ * the given {@link ProviderInfo}. Final permission checking is always done
+ * in {@link ContentProvider}.
+ */
+ private final boolean checkHoldingPermissionsLocked(
+ IPackageManager pm, ProviderInfo pi, Uri uri, int uid, int modeFlags) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG,
"checkHoldingPermissionsLocked: uri=" + uri + " uid=" + uid);
+
+ if (pi.applicationInfo.uid == uid) {
+ return true;
+ } else if (!pi.exported) {
+ return false;
+ }
+
+ boolean readMet = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0;
+ boolean writeMet = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
try {
- // Is the component private from the target uid?
- final boolean prv = !pi.exported && pi.applicationInfo.uid != uid;
-
- // Acceptable if the there is no read permission needed from the
- // target or the target is holding the read permission.
- if (!readPerm) {
- if ((!prv && pi.readPermission == null) ||
- (pm.checkUidPermission(pi.readPermission, uid)
- == PackageManager.PERMISSION_GRANTED)) {
- readPerm = true;
- }
+ // check if target holds top-level <provider> permissions
+ if (!readMet && pi.readPermission != null
+ && (pm.checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
+ readMet = true;
+ }
+ if (!writeMet && pi.writePermission != null
+ && (pm.checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
+ writeMet = true;
}
- // Acceptable if the there is no write permission needed from the
- // target or the target is holding the read permission.
- if (!writePerm) {
- if (!prv && (pi.writePermission == null) ||
- (pm.checkUidPermission(pi.writePermission, uid)
- == PackageManager.PERMISSION_GRANTED)) {
- writePerm = true;
- }
- }
+ // track if unprotected read/write is allowed; any denied
+ // <path-permission> below removes this ability
+ boolean allowDefaultRead = pi.readPermission == null;
+ boolean allowDefaultWrite = pi.writePermission == null;
- // Acceptable if there is a path permission matching the URI that
- // the target holds the permission on.
- PathPermission[] pps = pi.pathPermissions;
- if (pps != null && (!readPerm || !writePerm)) {
+ // check if target holds any <path-permission> that match uri
+ final PathPermission[] pps = pi.pathPermissions;
+ if (pps != null) {
final String path = uri.getPath();
int i = pps.length;
- while (i > 0 && (!readPerm || !writePerm)) {
+ while (i > 0 && (!readMet || !writeMet)) {
i--;
PathPermission pp = pps[i];
- if (!readPerm) {
- final String pprperm = pp.getReadPermission();
- if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking read perm for "
- + pprperm + " for " + pp.getPath()
- + ": match=" + pp.match(path)
- + " check=" + pm.checkUidPermission(pprperm, uid));
- if (pprperm != null && pp.match(path) &&
- (pm.checkUidPermission(pprperm, uid)
- == PackageManager.PERMISSION_GRANTED)) {
- readPerm = true;
+ if (pp.match(path)) {
+ if (!readMet) {
+ final String pprperm = pp.getReadPermission();
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking read perm for "
+ + pprperm + " for " + pp.getPath()
+ + ": match=" + pp.match(path)
+ + " check=" + pm.checkUidPermission(pprperm, uid));
+ if (pprperm != null) {
+ if (pm.checkUidPermission(pprperm, uid) == PERMISSION_GRANTED) {
+ readMet = true;
+ } else {
+ allowDefaultRead = false;
+ }
+ }
}
- }
- if (!writePerm) {
- final String ppwperm = pp.getWritePermission();
- if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking write perm "
- + ppwperm + " for " + pp.getPath()
- + ": match=" + pp.match(path)
- + " check=" + pm.checkUidPermission(ppwperm, uid));
- if (ppwperm != null && pp.match(path) &&
- (pm.checkUidPermission(ppwperm, uid)
- == PackageManager.PERMISSION_GRANTED)) {
- writePerm = true;
+ if (!writeMet) {
+ final String ppwperm = pp.getWritePermission();
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking write perm "
+ + ppwperm + " for " + pp.getPath()
+ + ": match=" + pp.match(path)
+ + " check=" + pm.checkUidPermission(ppwperm, uid));
+ if (ppwperm != null) {
+ if (pm.checkUidPermission(ppwperm, uid) == PERMISSION_GRANTED) {
+ writeMet = true;
+ } else {
+ allowDefaultWrite = false;
+ }
+ }
}
}
}
}
+
+ // grant unprotected <provider> read/write, if not blocked by
+ // <path-permission> above
+ if (allowDefaultRead) readMet = true;
+ if (allowDefaultWrite) writeMet = true;
+
} catch (RemoteException e) {
return false;
}
- return readPerm && writePerm;
+ return readMet && writeMet;
}
private final boolean checkUriPermissionLocked(Uri uri, int uid,
@@ -5819,6 +5837,11 @@
return providers;
}
+ /**
+ * Check if {@link ProcessRecord} has a possible chance at accessing the
+ * given {@link ProviderInfo}. Final permission checking is always done
+ * in {@link ContentProvider}.
+ */
private final String checkContentProviderPermissionLocked(
ProviderInfo cpi, ProcessRecord r) {
final int callingPid = (r != null) ? r.pid : Binder.getCallingPid();