Uri permission grants improvements, tests, fixes.

The primary work in this CL is fixing two subtle security bugs that
have recently been reported in how checkGrantUriPermission() handles
advanced grant features like "persistable" and "prefix".  In general,
these advanced features should only be available when the underlying
provider has enabled permission grants.  The only narrow case where
we should allow granting without provider consent is for simple "read"
or "write" grants when crossing user boundaries.

To verify these fixes, this change adds thorough unit testing of
the core granting functionality.  This helped uncover a few subtle
bugs in how prefix grants were being issued, and in how persistable
grants were being released.

To support mocking in tests, shift all AM/PM calls to using Internal
interfaces, and initialize using best-practice onBootPhase().  This
also means we no longer have to handle RemoteExceptions.

Shift NeededUriGrants to using an ArraySet to avoid duplication of
grant data structures.

Define TEST_MAPPING to ensure future changes are tested.

Bug: 140729426, 138791358
Test: atest FrameworksServicesTests:com.android.server.uri
Test: atest CtsAppSecurityHostTestCases:android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert
Change-Id: I8dac08280981c3cd15071226319efe9ebd8b4db5
This commit is contained in:
Jeff Sharkey
2020-04-11 21:12:58 -06:00
parent 51ecd453b3
commit 1d194f9237
11 changed files with 972 additions and 229 deletions

View File

@@ -2681,7 +2681,6 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.d("AppOps", "AppOpsService published");
LocalServices.addService(ActivityManagerInternal.class, mInternal);
mActivityTaskManager.onActivityManagerInternalAdded();
mUgmInternal.onActivityManagerInternalAdded();
mPendingIntentController.onActivityManagerInternalAdded();
// Wait for the synchronized block started in mProcessCpuThread,
// so that any other access to mProcessCpuTracker from main thread
@@ -6410,7 +6409,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
return mUgmInternal.checkUriPermission(new GrantUri(userId, uri, false), uid, modeFlags)
return mUgmInternal.checkUriPermission(new GrantUri(userId, uri, modeFlags), uid, modeFlags)
? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
}
@@ -6422,7 +6421,7 @@ public class ActivityManagerService extends IActivityManager.Stub
public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri,
final int modeFlags, int userId) {
enforceNotIsolatedCaller("grantUriPermission");
GrantUri grantUri = new GrantUri(userId, uri, false);
GrantUri grantUri = new GrantUri(userId, uri, modeFlags);
synchronized(this) {
final ProcessRecord r = getRecordForAppLocked(caller);
if (r == null) {
@@ -6480,8 +6479,8 @@ public class ActivityManagerService extends IActivityManager.Stub
return;
}
mUgmInternal.revokeUriPermission(targetPackage, r.uid, new GrantUri(userId, uri, false),
modeFlags);
mUgmInternal.revokeUriPermission(targetPackage, r.uid,
new GrantUri(userId, uri, modeFlags), modeFlags);
}
}

View File

@@ -18,6 +18,7 @@ package com.android.server.uri;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
import android.util.proto.ProtoOutputStream;
@@ -27,12 +28,12 @@ import com.android.server.am.GrantUriProto;
public class GrantUri {
public final int sourceUserId;
public final Uri uri;
public boolean prefix;
public final boolean prefix;
public GrantUri(int sourceUserId, Uri uri, boolean prefix) {
public GrantUri(int sourceUserId, Uri uri, int modeFlags) {
this.sourceUserId = sourceUserId;
this.uri = uri;
this.prefix = prefix;
this.prefix = (modeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0;
}
@Override
@@ -74,12 +75,12 @@ public class GrantUri {
proto.end(token);
}
public static GrantUri resolve(int defaultSourceUserHandle, Uri uri) {
public static GrantUri resolve(int defaultSourceUserHandle, Uri uri, int modeFlags) {
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
return new GrantUri(ContentProvider.getUserIdFromUri(uri, defaultSourceUserHandle),
ContentProvider.getUriWithoutUserId(uri), false);
ContentProvider.getUriWithoutUserId(uri), modeFlags);
} else {
return new GrantUri(defaultSourceUserHandle, uri, false);
return new GrantUri(defaultSourceUserHandle, uri, modeFlags);
}
}
}

View File

@@ -16,22 +16,23 @@
package com.android.server.uri;
import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
import com.android.server.am.NeededUriGrantsProto;
import java.util.ArrayList;
/** List of {@link GrantUri} a process needs. */
public class NeededUriGrants extends ArrayList<GrantUri> {
public class NeededUriGrants {
final String targetPkg;
final int targetUid;
final int flags;
final ArraySet<GrantUri> uris;
public NeededUriGrants(String targetPkg, int targetUid, int flags) {
this.targetPkg = targetPkg;
this.targetUid = targetUid;
this.flags = flags;
this.uris = new ArraySet<>();
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
@@ -40,9 +41,9 @@ public class NeededUriGrants extends ArrayList<GrantUri> {
proto.write(NeededUriGrantsProto.TARGET_UID, targetUid);
proto.write(NeededUriGrantsProto.FLAGS, flags);
final int N = this.size();
final int N = uris.size();
for (int i = 0; i < N; i++) {
this.get(i).dumpDebug(proto, NeededUriGrantsProto.GRANTS);
uris.valueAt(i).dumpDebug(proto, NeededUriGrantsProto.GRANTS);
}
proto.end(token);
}

View File

@@ -0,0 +1,30 @@
{
"presubmit": [
{
"name": "FrameworksServicesTests",
"options": [
{
"include-filter": "com.android.server.uri."
}
]
}
],
"postsubmit": [
{
"name": "CtsAppSecurityHostTestCases",
"options": [
{
"include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert"
}
]
},
{
"name": "CtsWindowManagerDeviceTestCases",
"options": [
{
"include-filter": "android.server.wm.CrossAppDragAndDropTests"
}
]
}
]
}

View File

@@ -30,7 +30,6 @@ import java.io.PrintWriter;
*/
public interface UriGrantsManagerInternal {
void onSystemReady();
void onActivityManagerInternalAdded();
void removeUriPermissionIfNeeded(UriPermission perm);
void grantUriPermission(int callingUid, String targetPkg, GrantUri grantUri,
final int modeFlags, UriPermissionOwner owner, int targetUserId);

View File

@@ -21,6 +21,9 @@ import static android.Manifest.permission.FORCE_PERSISTABLE_URI_PERMISSIONS;
import static android.Manifest.permission.GET_APP_GRANTED_URI_PERMISSIONS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_WRITE_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_AWARE;
@@ -53,7 +56,6 @@ import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
@@ -76,6 +78,8 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.server.IoThread;
@@ -83,11 +87,11 @@ import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import libcore.io.IoUtils;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -114,7 +118,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false;
private final Object mLock = new Object();
private final Context mContext;
private final H mH;
ActivityManagerInternal mAmInternal;
PackageManagerInternal mPmInternal;
@@ -143,27 +146,38 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
private final SparseArray<ArrayMap<GrantUri, UriPermission>>
mGrantedUriPermissions = new SparseArray<>();
private UriGrantsManagerService(Context context) {
mContext = context;
private UriGrantsManagerService() {
this(SystemServiceManager.ensureSystemDir());
}
private UriGrantsManagerService(File systemDir) {
mH = new H(IoThread.get().getLooper());
final File systemDir = SystemServiceManager.ensureSystemDir();
mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"), "uri-grants");
}
@VisibleForTesting
static UriGrantsManagerService createForTest(File systemDir) {
final UriGrantsManagerService service = new UriGrantsManagerService(systemDir);
service.mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
service.mPmInternal = LocalServices.getService(PackageManagerInternal.class);
return service;
}
@VisibleForTesting
UriGrantsManagerInternal getLocalService() {
return new LocalService();
}
private void start() {
LocalServices.addService(UriGrantsManagerInternal.class, new LocalService());
}
void onActivityManagerInternalAdded() {
mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
}
public static final class Lifecycle extends SystemService {
private final UriGrantsManagerService mService;
public Lifecycle(Context context) {
super(context);
mService = new UriGrantsManagerService(context);
mService = new UriGrantsManagerService();
}
@Override
@@ -172,11 +186,27 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
mService.start();
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
mService.mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
mService.mPmInternal = LocalServices.getService(PackageManagerInternal.class);
}
}
public UriGrantsManagerService getService() {
return mService;
}
}
private int checkUidPermission(String permission, int uid) {
try {
return AppGlobals.getPackageManager().checkUidPermission(permission, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @param uri This uri must NOT contain an embedded userId.
* @param sourceUserId The userId in which the uri is to be resolved.
@@ -207,7 +237,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
throw new IllegalArgumentException("null uri");
}
grantUriPermission(fromUid, targetPkg, new GrantUri(sourceUserId, uri, false),
grantUriPermission(fromUid, targetPkg, new GrantUri(sourceUserId, uri, modeFlags),
modeFlags, owner, targetUserId);
}
}
@@ -220,16 +250,12 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
final IPackageManager pm = AppGlobals.getPackageManager();
try {
final int packageUid = pm.getPackageUid(packageName,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, callingUserId);
if (packageUid != callingUid) {
throw new SecurityException(
"Package " + packageName + " does not belong to calling UID " + callingUid);
}
} catch (RemoteException e) {
throw new SecurityException("Failed to verify package name ownership");
final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
final int packageUid = pm.getPackageUidInternal(packageName,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, callingUserId);
if (packageUid != callingUid) {
throw new SecurityException(
"Package " + packageName + " does not belong to calling UID " + callingUid);
}
final ArrayList<android.content.UriPermission> result = Lists.newArrayList();
@@ -303,7 +329,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (toPackage != null) {
mAmInternal.enforceCallingPermission(FORCE_PERSISTABLE_URI_PERMISSIONS,
"takePersistableUriPermission");
uid = getPmInternal().getPackageUid(toPackage, 0, userId);
uid = mPmInternal.getPackageUidInternal(toPackage, 0, userId);
} else {
enforceNotIsolatedCaller("takePersistableUriPermission");
uid = Binder.getCallingUid();
@@ -314,11 +340,11 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
synchronized (mLock) {
boolean persistChanged = false;
GrantUri grantUri = new GrantUri(userId, uri, false);
UriPermission exactPerm = findUriPermissionLocked(uid, grantUri);
UriPermission exactPerm = findUriPermissionLocked(uid,
new GrantUri(userId, uri, 0));
UriPermission prefixPerm = findUriPermissionLocked(uid,
new GrantUri(userId, uri, true));
new GrantUri(userId, uri, FLAG_GRANT_PREFIX_URI_PERMISSION));
final boolean exactValid = (exactPerm != null)
&& ((modeFlags & exactPerm.persistableModeFlags) == modeFlags);
@@ -327,7 +353,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (!(exactValid || prefixValid)) {
throw new SecurityException("No persistable permission grants found for UID "
+ uid + " and Uri " + grantUri.toSafeString());
+ uid + " and Uri " + uri.toSafeString());
}
if (exactValid) {
@@ -368,7 +394,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (toPackage != null) {
mAmInternal.enforceCallingPermission(FORCE_PERSISTABLE_URI_PERMISSIONS,
"releasePersistableUriPermission");
uid = getPmInternal().getPackageUid(toPackage, 0, userId);
uid = mPmInternal.getPackageUidInternal(toPackage, 0, userId);
} else {
enforceNotIsolatedCaller("releasePersistableUriPermission");
uid = Binder.getCallingUid();
@@ -381,9 +407,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
boolean persistChanged = false;
UriPermission exactPerm = findUriPermissionLocked(uid,
new GrantUri(userId, uri, false));
new GrantUri(userId, uri, 0));
UriPermission prefixPerm = findUriPermissionLocked(uid,
new GrantUri(userId, uri, true));
new GrantUri(userId, uri, FLAG_GRANT_PREFIX_URI_PERMISSION));
if (exactPerm == null && prefixPerm == null && toPackage == null) {
throw new SecurityException("No permission grants found for UID " + uid
+ " and Uri " + uri.toSafeString());
@@ -559,16 +585,12 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (contentUserHint == UserHandle.USER_CURRENT) {
contentUserHint = UserHandle.getUserId(callingUid);
}
final IPackageManager pm = AppGlobals.getPackageManager();
int targetUid;
if (needed != null) {
targetUid = needed.targetUid;
} else {
try {
targetUid = pm.getPackageUid(targetPkg, MATCH_DEBUG_TRIAGED_MISSING, targetUserId);
} catch (RemoteException ex) {
return null;
}
targetUid = mPmInternal.getPackageUidInternal(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
targetUserId);
if (targetUid < 0) {
if (DEBUG) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg
+ " on user " + targetUserId);
@@ -576,27 +598,27 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
}
if (data != null) {
GrantUri grantUri = GrantUri.resolve(contentUserHint, data);
GrantUri grantUri = GrantUri.resolve(contentUserHint, data, mode);
targetUid = checkGrantUriPermission(callingUid, targetPkg, grantUri, mode, targetUid);
if (targetUid > 0) {
if (needed == null) {
needed = new NeededUriGrants(targetPkg, targetUid, mode);
}
needed.add(grantUri);
needed.uris.add(grantUri);
}
}
if (clip != null) {
for (int i=0; i<clip.getItemCount(); i++) {
Uri uri = clip.getItemAt(i).getUri();
if (uri != null) {
GrantUri grantUri = GrantUri.resolve(contentUserHint, uri);
GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode);
targetUid = checkGrantUriPermission(callingUid, targetPkg,
grantUri, mode, targetUid);
if (targetUid > 0) {
if (needed == null) {
needed = new NeededUriGrants(targetPkg, targetUid, mode);
}
needed.add(grantUri);
needed.uris.add(grantUri);
}
} else {
Intent clipIntent = clip.getItemAt(i).getIntent();
@@ -666,16 +688,13 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
final ProviderInfo pi = getProviderInfo(uri.getAuthority(), sourceUserId,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
if (pi != null && sourcePkg.equals(pi.packageName)) {
int targetUid = -1;
try {
targetUid = AppGlobals.getPackageManager().getPackageUid(
int targetUid = mPmInternal.getPackageUidInternal(
targetPkg, MATCH_UNINSTALLED_PACKAGES, targetUserId);
} catch (RemoteException e) {
}
if (targetUid != -1) {
final GrantUri grantUri = new GrantUri(sourceUserId, uri,
prefix ? Intent.FLAG_GRANT_PREFIX_URI_PERMISSION : 0);
final UriPermission perm = findOrCreateUriPermission(
sourcePkg, targetPkg, targetUid,
new GrantUri(sourceUserId, uri, prefix));
sourcePkg, targetPkg, targetUid, grantUri);
perm.initPersistedModes(modeFlags, createdTime);
}
} else {
@@ -733,13 +752,10 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
return;
}
if ((modeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0) {
grantUri.prefix = true;
}
final UriPermission perm = findOrCreateUriPermission(
pi.packageName, targetPkg, targetUid, grantUri);
perm.grantModes(modeFlags, owner);
getPmInternal().grantImplicitAccess(UserHandle.getUserId(targetUid), null,
mPmInternal.grantImplicitAccess(UserHandle.getUserId(targetUid), null,
UserHandle.getAppId(targetUid), pi.applicationInfo.uid, false /*direct*/);
}
@@ -748,10 +764,10 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (needed == null) {
return;
}
for (int i=0; i<needed.size(); i++) {
GrantUri grantUri = needed.get(i);
final int N = needed.uris.size();
for (int i = 0; i < N; i++) {
grantUriPermissionUnchecked(needed.targetUid, needed.targetPkg,
grantUri, needed.flags, owner);
needed.uris.valueAt(i), needed.flags, owner);
}
}
@@ -760,13 +776,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (targetPkg == null) {
throw new NullPointerException("targetPkg");
}
int targetUid;
final IPackageManager pm = AppGlobals.getPackageManager();
try {
targetUid = pm.getPackageUid(targetPkg, MATCH_DEBUG_TRIAGED_MISSING, targetUserId);
} catch (RemoteException ex) {
return;
}
int targetUid = mPmInternal.getPackageUidInternal(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
targetUserId);
targetUid = checkGrantUriPermission(callingUid, targetPkg, grantUri, modeFlags, targetUid);
if (targetUid < 0) {
@@ -780,7 +791,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
final int modeFlags) {
if (DEBUG) Slog.v(TAG, "Revoking all granted permissions to " + grantUri);
final IPackageManager pm = AppGlobals.getPackageManager();
final String authority = grantUri.uri.getAuthority();
final ProviderInfo pi = getProviderInfo(authority, grantUri.sourceUserId,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
@@ -791,7 +801,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
// Does the caller have this permission on the URI?
if (!checkHoldingPermissions(pm, pi, grantUri, callingUid, modeFlags)) {
if (!checkHoldingPermissions(pi, grantUri, callingUid, modeFlags)) {
// If they don't have direct access to the URI, then revoke any
// ownerless URI permissions that have been granted to them.
final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
@@ -864,7 +874,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
* in {@link ContentProvider}.
*/
private boolean checkHoldingPermissions(
IPackageManager pm, ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) {
ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) {
if (DEBUG) Slog.v(TAG, "checkHoldingPermissions: uri=" + grantUri + " uid=" + uid);
if (UserHandle.getUserId(uid) != grantUri.sourceUserId) {
if (ActivityManager.checkComponentPermission(INTERACT_ACROSS_USERS, uid, -1, true)
@@ -872,10 +882,10 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
return false;
}
}
return checkHoldingPermissionsInternal(pm, pi, grantUri, uid, modeFlags, true);
return checkHoldingPermissionsInternal(pi, grantUri, uid, modeFlags, true);
}
private boolean checkHoldingPermissionsInternal(IPackageManager pm, ProviderInfo pi,
private boolean checkHoldingPermissionsInternal(ProviderInfo pi,
GrantUri grantUri, int uid, final int modeFlags, boolean considerUidPermissions) {
if (pi.applicationInfo.uid == uid) {
return true;
@@ -885,74 +895,70 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
boolean readMet = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0;
boolean writeMet = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
try {
// check if target holds top-level <provider> permissions
if (!readMet && pi.readPermission != null && considerUidPermissions
&& (pm.checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
readMet = true;
}
if (!writeMet && pi.writePermission != null && considerUidPermissions
&& (pm.checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
writeMet = 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;
// check if target holds top-level <provider> permissions
if (!readMet && pi.readPermission != null && considerUidPermissions
&& (checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
readMet = true;
}
if (!writeMet && pi.writePermission != null && considerUidPermissions
&& (checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
writeMet = true;
}
// check if target holds any <path-permission> that match uri
final PathPermission[] pps = pi.pathPermissions;
if (pps != null) {
final String path = grantUri.uri.getPath();
int i = pps.length;
while (i > 0 && (!readMet || !writeMet)) {
i--;
PathPermission pp = pps[i];
if (pp.match(path)) {
if (!readMet) {
final String pprperm = pp.getReadPermission();
if (DEBUG) Slog.v(TAG,
"Checking read perm for " + pprperm + " for " + pp.getPath()
+ ": match=" + pp.match(path)
+ " check=" + pm.checkUidPermission(pprperm, uid));
if (pprperm != null) {
if (considerUidPermissions && pm.checkUidPermission(pprperm, uid)
== PERMISSION_GRANTED) {
readMet = true;
} else {
allowDefaultRead = false;
}
// 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;
// check if target holds any <path-permission> that match uri
final PathPermission[] pps = pi.pathPermissions;
if (pps != null) {
final String path = grantUri.uri.getPath();
int i = pps.length;
while (i > 0 && (!readMet || !writeMet)) {
i--;
PathPermission pp = pps[i];
if (pp.match(path)) {
if (!readMet) {
final String pprperm = pp.getReadPermission();
if (DEBUG) Slog.v(TAG,
"Checking read perm for " + pprperm + " for " + pp.getPath()
+ ": match=" + pp.match(path)
+ " check=" + checkUidPermission(pprperm, uid));
if (pprperm != null) {
if (considerUidPermissions && checkUidPermission(pprperm, uid)
== PERMISSION_GRANTED) {
readMet = true;
} else {
allowDefaultRead = false;
}
}
if (!writeMet) {
final String ppwperm = pp.getWritePermission();
if (DEBUG) Slog.v(TAG,
"Checking write perm " + ppwperm + " for " + pp.getPath()
+ ": match=" + pp.match(path)
+ " check=" + pm.checkUidPermission(ppwperm, uid));
if (ppwperm != null) {
if (considerUidPermissions && pm.checkUidPermission(ppwperm, uid)
== PERMISSION_GRANTED) {
writeMet = true;
} else {
allowDefaultWrite = false;
}
}
if (!writeMet) {
final String ppwperm = pp.getWritePermission();
if (DEBUG) Slog.v(TAG,
"Checking write perm " + ppwperm + " for " + pp.getPath()
+ ": match=" + pp.match(path)
+ " check=" + checkUidPermission(ppwperm, uid));
if (ppwperm != null) {
if (considerUidPermissions && 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;
}
// grant unprotected <provider> read/write, if not blocked by
// <path-permission> above
if (allowDefaultRead) readMet = true;
if (allowDefaultWrite) writeMet = true;
// If this provider says that grants are always required, we need to
// consult it directly to determine if the UID has permission
final boolean forceMet;
@@ -1013,14 +1019,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
private ProviderInfo getProviderInfo(String authority, int userHandle, int pmFlags) {
ProviderInfo pi = null;
try {
pi = AppGlobals.getPackageManager().resolveContentProvider(
authority, PackageManager.GET_URI_PERMISSION_PATTERNS | pmFlags,
userHandle);
} catch (RemoteException ex) {
}
return pi;
return mPmInternal.resolveContentProvider(authority,
PackageManager.GET_URI_PERMISSION_PATTERNS | pmFlags, userHandle);
}
/**
@@ -1033,7 +1033,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
* lastTargetUid else set that to -1.
*/
int checkGrantUriPermission(int callingUid, String targetPkg, GrantUri grantUri,
final int modeFlags, int lastTargetUid) {
int modeFlags, int lastTargetUid) {
if (!Intent.isAccessUriMode(modeFlags)) {
return -1;
}
@@ -1042,8 +1042,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (DEBUG) Slog.v(TAG, "Checking grant " + targetPkg + " permission to " + grantUri);
}
final IPackageManager pm = AppGlobals.getPackageManager();
// 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);
@@ -1079,39 +1077,22 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
int targetUid = lastTargetUid;
if (targetUid < 0 && targetPkg != null) {
try {
targetUid = pm.getPackageUid(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
UserHandle.getUserId(callingUid));
if (targetUid < 0) {
if (DEBUG) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg);
return -1;
}
} catch (RemoteException ex) {
targetUid = mPmInternal.getPackageUidInternal(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
UserHandle.getUserId(callingUid));
if (targetUid < 0) {
if (DEBUG) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg);
return -1;
}
}
// Figure out the value returned when access is allowed
final int allowedResult;
if ((modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0
|| pi.forceUriPermissions) {
// If we're extending a persistable grant or need to force, then we need to return
// "targetUid" so that we always create a grant data structure to
// support take/release APIs
allowedResult = targetUid;
} else {
// Otherwise, we can return "-1" to indicate that no grant data
// structures need to be created
allowedResult = -1;
}
boolean targetHoldsPermission = false;
if (targetUid >= 0) {
// First... does the target actually need this permission?
if (checkHoldingPermissions(pm, pi, grantUri, targetUid, modeFlags)) {
if (checkHoldingPermissions(pi, grantUri, targetUid, modeFlags)) {
// No need to grant the target this permission.
if (DEBUG) Slog.v(TAG,
"Target " + targetPkg + " already has full permission to " + grantUri);
return allowedResult;
targetHoldsPermission = true;
}
} else {
// First... there is no target package, so can anyone access it?
@@ -1145,9 +1126,25 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
}
}
if (allowed) {
return allowedResult;
if (pi.forceUriPermissions) {
// When provider requires dynamic permission checks, the only
// way to be safe is to issue permission grants for each item by
// assuming no generic access
allowed = false;
}
if (allowed) {
targetHoldsPermission = true;
}
}
final boolean basicGrant = (modeFlags & ~(FLAG_GRANT_READ_URI_PERMISSION
| FLAG_GRANT_WRITE_URI_PERMISSION)) == 0;
if (basicGrant && targetHoldsPermission) {
// When caller holds permission, and this is a simple permission
// grant, we can skip generating any bookkeeping; when any advanced
// features have been requested, we proceed below to make sure the
// provider supports granting permissions
return -1;
}
/* There is a special cross user grant if:
@@ -1158,38 +1155,42 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
*/
boolean specialCrossUserGrant = targetUid >= 0
&& UserHandle.getUserId(targetUid) != grantUri.sourceUserId
&& checkHoldingPermissionsInternal(pm, pi, grantUri, callingUid,
&& checkHoldingPermissionsInternal(pi, grantUri, callingUid,
modeFlags, false /*without considering the uid permissions*/);
// Second... is the provider allowing granting of URI permissions?
if (!specialCrossUserGrant) {
if (!pi.grantUriPermissions) {
boolean grantAllowed = pi.grantUriPermissions;
if (!ArrayUtils.isEmpty(pi.uriPermissionPatterns)) {
final int N = pi.uriPermissionPatterns.length;
grantAllowed = false;
for (int i = 0; i < N; i++) {
if (pi.uriPermissionPatterns[i] != null
&& pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
grantAllowed = true;
break;
}
}
}
if (!grantAllowed) {
if (specialCrossUserGrant) {
// We're only okay issuing basic grant access across user
// boundaries; advanced flags are blocked here
if (!basicGrant) {
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
+ " does not allow granting of advanced Uri permissions (uri "
+ grantUri + ")");
}
} else {
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
+ " does not allow granting of Uri permissions (uri "
+ grantUri + ")");
}
if (pi.uriPermissionPatterns != null) {
final int N = pi.uriPermissionPatterns.length;
boolean allowed = false;
for (int i=0; i<N; i++) {
if (pi.uriPermissionPatterns[i] != null
&& pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
allowed = true;
break;
}
}
if (!allowed) {
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
+ " does not allow granting of permission to path of Uri "
+ grantUri);
}
}
}
// Third... does the caller itself have permission to access this uri?
if (!checkHoldingPermissions(pm, pi, grantUri, callingUid, modeFlags)) {
if (!checkHoldingPermissions(pi, grantUri, callingUid, modeFlags)) {
// Require they hold a strong enough Uri permission
if (!checkUriPermission(grantUri, callingUid, modeFlags)) {
if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(pi.readPermission)) {
@@ -1203,6 +1204,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
}
}
return targetUid;
}
@@ -1212,7 +1214,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri, int modeFlags,
int userId) {
return checkGrantUriPermission(callingUid, targetPkg,
new GrantUri(userId, uri, false), modeFlags, -1);
new GrantUri(userId, uri, modeFlags), modeFlags, -1);
}
boolean checkUriPermission(GrantUri grantUri, int uid, final int modeFlags) {
@@ -1297,14 +1299,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
}
private PackageManagerInternal getPmInternal() {
// Don't need to synchonize; worst-case scenario LocalServices will be called twice.
if (mPmInternal == null) {
mPmInternal = LocalServices.getService(PackageManagerInternal.class);
}
return mPmInternal;
}
final class H extends Handler {
static final int PERSIST_URI_GRANTS_MSG = 1;
@@ -1324,7 +1318,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
final class LocalService implements UriGrantsManagerInternal {
@Override
public void removeUriPermissionIfNeeded(UriPermission perm) {
synchronized (mLock) {
@@ -1418,13 +1411,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
}
@Override
public void onActivityManagerInternalAdded() {
synchronized (mLock) {
UriGrantsManagerService.this.onActivityManagerInternalAdded();
}
}
@Override
public IBinder newUriPermissionOwner(String name) {
enforceNotIsolatedCaller("newUriPermissionOwner");
@@ -1454,8 +1440,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (uri == null) {
owner.removeUriPermissions(mode);
} else {
final boolean prefix = (mode & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0;
owner.removeUriPermission(new GrantUri(userId, uri, prefix), mode);
owner.removeUriPermission(new GrantUri(userId, uri, mode), mode);
}
}
}
@@ -1478,12 +1463,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
boolean printed = false;
int dumpUid = -2;
if (dumpPackage != null) {
try {
dumpUid = mContext.getPackageManager().getPackageUidAsUser(dumpPackage,
MATCH_ANY_USER, 0);
} catch (PackageManager.NameNotFoundException e) {
dumpUid = -1;
}
dumpUid = mPmInternal.getPackageUidInternal(dumpPackage, MATCH_ANY_USER, 0);
}
for (int i = 0; i < mGrantedUriPermissions.size(); i++) {
int uid = mGrantedUriPermissions.keyAt(i);

View File

@@ -16,6 +16,7 @@
package com.android.server.uri;
import android.annotation.Nullable;
import android.app.GrantedUriPermission;
import android.content.Intent;
import android.os.Binder;
@@ -79,7 +80,7 @@ final class UriPermission {
*/
long persistedCreateTime = INVALID_TIME;
private static final long INVALID_TIME = Long.MIN_VALUE;
static final long INVALID_TIME = Long.MIN_VALUE;
private ArraySet<UriPermissionOwner> mReadOwners;
private ArraySet<UriPermissionOwner> mWriteOwners;
@@ -96,7 +97,7 @@ final class UriPermission {
private void updateModeFlags() {
final int oldModeFlags = modeFlags;
modeFlags = ownedModeFlags | globalModeFlags | persistableModeFlags | persistedModeFlags;
modeFlags = ownedModeFlags | globalModeFlags | persistedModeFlags;
if (Log.isLoggable(TAG, Log.VERBOSE) && (modeFlags != oldModeFlags)) {
Slog.d(TAG,
@@ -123,7 +124,7 @@ final class UriPermission {
updateModeFlags();
}
void grantModes(int modeFlags, UriPermissionOwner owner) {
boolean grantModes(int modeFlags, @Nullable UriPermissionOwner owner) {
final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -144,6 +145,7 @@ final class UriPermission {
}
updateModeFlags();
return false;
}
/**
@@ -176,8 +178,6 @@ final class UriPermission {
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
final int before = persistedModeFlags;
persistableModeFlags &= ~modeFlags;
persistedModeFlags &= ~modeFlags;
if (persistedModeFlags == 0) {

View File

@@ -25,6 +25,7 @@ import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
import com.android.server.am.UriPermissionOwnerProto;
import com.google.android.collect.Sets;
import java.io.PrintWriter;

View File

@@ -0,0 +1,355 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.uri;
import static com.android.server.uri.UriGrantsMockContext.FLAG_PERSISTABLE;
import static com.android.server.uri.UriGrantsMockContext.FLAG_PREFIX;
import static com.android.server.uri.UriGrantsMockContext.FLAG_READ;
import static com.android.server.uri.UriGrantsMockContext.PKG_CAMERA;
import static com.android.server.uri.UriGrantsMockContext.PKG_COMPLEX;
import static com.android.server.uri.UriGrantsMockContext.PKG_SOCIAL;
import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_CAMERA;
import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_COMPLEX;
import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_PRIVATE;
import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_PUBLIC;
import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_SOCIAL;
import static com.android.server.uri.UriGrantsMockContext.UID_SECONDARY_CAMERA;
import static com.android.server.uri.UriGrantsMockContext.UID_SECONDARY_SOCIAL;
import static com.android.server.uri.UriGrantsMockContext.URI_PHOTO_1;
import static com.android.server.uri.UriGrantsMockContext.URI_PRIVATE;
import static com.android.server.uri.UriGrantsMockContext.URI_PUBLIC;
import static com.android.server.uri.UriGrantsMockContext.USER_PRIMARY;
import static com.android.server.uri.UriGrantsMockContext.USER_SECONDARY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.ClipData;
import android.content.Intent;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.util.ArraySet;
import androidx.test.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.Set;
public class UriGrantsManagerServiceTest {
private UriGrantsMockContext mContext;
private UriGrantsManagerService mService;
private UriGrantsManagerInternal mLocalService;
@Before
public void setUp() throws Exception {
mContext = new UriGrantsMockContext(InstrumentationRegistry.getContext());
mService = UriGrantsManagerService.createForTest(mContext.getFilesDir());
mLocalService = mService.getLocalService();
}
@Test
public void testNeeded_normal() {
final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PHOTO_1).addFlags(FLAG_READ);
final GrantUri expectedGrant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null,
USER_PRIMARY);
assertEquals(PKG_SOCIAL, needed.targetPkg);
assertEquals(UID_PRIMARY_SOCIAL, needed.targetUid);
assertEquals(FLAG_READ, needed.flags);
assertEquals(asSet(expectedGrant), needed.uris);
}
/**
* No need to issue grants for public authorities.
*/
@Test
public void testNeeded_public() {
final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PUBLIC).addFlags(FLAG_READ);
final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_PUBLIC, PKG_SOCIAL, intent, intent.getFlags(), null,
USER_PRIMARY);
assertNull(needed);
}
/**
* But we're willing to issue grants to public authorities when crossing
* user boundaries.
*/
@Test
public void testNeeded_public_differentUser() {
final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PUBLIC).addFlags(FLAG_READ);
final GrantUri expectedGrant = new GrantUri(USER_PRIMARY, URI_PUBLIC, FLAG_READ);
final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_PUBLIC, PKG_SOCIAL, intent, intent.getFlags(), null, USER_SECONDARY);
assertEquals(PKG_SOCIAL, needed.targetPkg);
assertEquals(UID_SECONDARY_SOCIAL, needed.targetUid);
assertEquals(FLAG_READ, needed.flags);
assertEquals(asSet(expectedGrant), needed.uris);
}
/**
* Refuse to issue grants for private authorities.
*/
@Test
public void testNeeded_private() {
final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PRIVATE).addFlags(FLAG_READ);
try {
mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_PRIVATE, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY);
fail();
} catch (SecurityException expected) {
}
}
/**
* Verify that we can't grant permissions to top level of a provider with
* complex permission model.
*/
@Test
public void testNeeded_complex_top() {
final Uri uri = Uri.parse("content://" + PKG_COMPLEX + "/");
{
final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
.addFlags(FLAG_READ);
assertNull(mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_COMPLEX, PKG_SOCIAL,
intent, intent.getFlags(), null, USER_PRIMARY));
}
{
final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
.addFlags(FLAG_READ | FLAG_PREFIX);
try {
mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_COMPLEX, PKG_SOCIAL,
intent, intent.getFlags(), null, USER_PRIMARY);
fail();
} catch (SecurityException expected) {
}
}
{
final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
.addFlags(FLAG_READ | FLAG_PERSISTABLE);
try {
mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_COMPLEX, PKG_SOCIAL,
intent, intent.getFlags(), null, USER_PRIMARY);
fail();
} catch (SecurityException expected) {
}
}
}
/**
* Verify that we allow special cross-user grants to top level of a provider
* that normally wouldn't allow it. Only basic permission modes are allowed;
* advanced modes throw.
*/
@Test
public void testNeeded_complex_top_differentUser() {
final Uri uri = Uri.parse("content://" + PKG_COMPLEX + "/");
{
final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
.addFlags(FLAG_READ);
final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null,
USER_SECONDARY);
assertEquals(FLAG_READ, needed.flags);
}
{
final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
.addFlags(FLAG_READ | FLAG_PREFIX);
try {
mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null,
USER_SECONDARY);
fail();
} catch (SecurityException expected) {
}
}
{
final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
.addFlags(FLAG_READ | FLAG_PERSISTABLE);
try {
mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null,
USER_SECONDARY);
fail();
} catch (SecurityException expected) {
}
}
}
/**
* Verify that we can grant permissions to middle level of a provider with
* complex permission model.
*/
@Test
public void testNeeded_complex_middle() {
final Uri uri = Uri.parse("content://" + PKG_COMPLEX + "/secure/12");
{
final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
.addFlags(FLAG_READ);
final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY);
assertEquals(asSet(new GrantUri(USER_PRIMARY, uri, 0)), needed.uris);
}
{
final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
.addFlags(FLAG_READ | FLAG_PREFIX);
final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY);
assertEquals(asSet(new GrantUri(USER_PRIMARY, uri, FLAG_PREFIX)), needed.uris);
}
{
final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
.addFlags(FLAG_READ | FLAG_PERSISTABLE);
final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY);
assertEquals(asSet(new GrantUri(USER_PRIMARY, uri, 0)), needed.uris);
}
}
/**
* Verify that when we try sending a list of mixed items that the actual
* grants are verified based on the capabilities of the caller.
*/
@Test
public void testNeeded_mixedPersistable() {
final ClipData clip = ClipData.newRawUri("test", URI_PHOTO_1);
clip.addItem(new ClipData.Item(URI_PUBLIC));
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(FLAG_READ | FLAG_PERSISTABLE);
intent.setClipData(clip);
{
// When granting towards primary, persistable can't be honored so
// the entire grant fails
try {
mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_CAMERA, PKG_SOCIAL, intent,
intent.getFlags(), null, USER_PRIMARY);
fail();
} catch (SecurityException expected) {
}
}
{
// When granting towards secondary, persistable can't be honored so
// the entire grant fails
try {
mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_CAMERA, PKG_SOCIAL, intent,
intent.getFlags(), null, USER_SECONDARY);
fail();
} catch (SecurityException expected) {
}
}
}
/**
* Verify that two overlapping owners require separate grants and that they
* don't interfere with each other.
*/
@Test
public void testGrant_overlap() {
final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PHOTO_1).addFlags(FLAG_READ);
final UriPermissionOwner activity = new UriPermissionOwner(mLocalService, "activity");
final UriPermissionOwner service = new UriPermissionOwner(mLocalService, "service");
final GrantUri expectedGrant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
// Grant read via activity and write via service
mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY),
activity);
mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY),
service);
// Verify that everything is good with the world
assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
// Finish activity; service should hold permission
activity.removeUriPermissions();
assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
// And finishing service should wrap things up
service.removeUriPermissions();
assertFalse(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
}
@Test
public void testCheckAuthorityGrants() {
final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PHOTO_1).addFlags(FLAG_READ);
final UriPermissionOwner owner = new UriPermissionOwner(mLocalService, "primary");
final ProviderInfo cameraInfo = mContext.mPmInternal.resolveContentProvider(
PKG_CAMERA, 0, USER_PRIMARY);
// By default no social can see any camera
assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
cameraInfo, USER_PRIMARY, true));
assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
cameraInfo, USER_SECONDARY, true));
assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
cameraInfo, USER_PRIMARY, true));
assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
cameraInfo, USER_SECONDARY, true));
// Granting primary camera to primary social
mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent(
UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY),
owner);
assertTrue(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
cameraInfo, USER_PRIMARY, true));
assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
cameraInfo, USER_SECONDARY, true));
assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
cameraInfo, USER_PRIMARY, true));
assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
cameraInfo, USER_SECONDARY, true));
// Granting secondary camera to primary social
mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent(
UID_SECONDARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY),
owner);
assertTrue(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
cameraInfo, USER_PRIMARY, true));
assertTrue(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
cameraInfo, USER_SECONDARY, true));
assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
cameraInfo, USER_PRIMARY, true));
assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
cameraInfo, USER_SECONDARY, true));
// And releasing the grant means we lose access
owner.removeUriPermissions();
assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
cameraInfo, USER_PRIMARY, true));
assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
cameraInfo, USER_SECONDARY, true));
}
private static <T> Set<T> asSet(T... values) {
return new ArraySet<T>(Arrays.asList(values));
}
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.uri;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.ActivityManagerInternal;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.FileUtils;
import android.os.PatternMatcher;
import android.os.UserHandle;
import android.test.mock.MockContentResolver;
import android.test.mock.MockPackageManager;
import com.android.server.LocalServices;
import java.io.File;
public class UriGrantsMockContext extends ContextWrapper {
static final String TAG = "UriGrants";
static final int FLAG_READ = Intent.FLAG_GRANT_READ_URI_PERMISSION;
static final int FLAG_WRITE = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
static final int FLAG_PERSISTABLE = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
static final int FLAG_PREFIX = Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
static final int USER_PRIMARY = 10;
static final int USER_SECONDARY = 11;
/** Typical social network app */
static final String PKG_SOCIAL = "com.example.social";
/** Typical camera app that allows grants */
static final String PKG_CAMERA = "com.example.camera";
/** Completely private app/provider that offers no grants */
static final String PKG_PRIVATE = "com.example.private";
/** Completely public app/provider that needs no grants */
static final String PKG_PUBLIC = "com.example.public";
/** Complex provider that offers nested grants */
static final String PKG_COMPLEX = "com.example.complex";
private static final int UID_SOCIAL = android.os.Process.LAST_APPLICATION_UID - 1;
private static final int UID_CAMERA = android.os.Process.LAST_APPLICATION_UID - 2;
private static final int UID_PRIVATE = android.os.Process.LAST_APPLICATION_UID - 3;
private static final int UID_PUBLIC = android.os.Process.LAST_APPLICATION_UID - 4;
private static final int UID_COMPLEX = android.os.Process.LAST_APPLICATION_UID - 5;
static final int UID_PRIMARY_SOCIAL = UserHandle.getUid(USER_PRIMARY, UID_SOCIAL);
static final int UID_PRIMARY_CAMERA = UserHandle.getUid(USER_PRIMARY, UID_CAMERA);
static final int UID_PRIMARY_PRIVATE = UserHandle.getUid(USER_PRIMARY, UID_PRIVATE);
static final int UID_PRIMARY_PUBLIC = UserHandle.getUid(USER_PRIMARY, UID_PUBLIC);
static final int UID_PRIMARY_COMPLEX = UserHandle.getUid(USER_PRIMARY, UID_COMPLEX);
static final int UID_SECONDARY_SOCIAL = UserHandle.getUid(USER_SECONDARY, UID_SOCIAL);
static final int UID_SECONDARY_CAMERA = UserHandle.getUid(USER_SECONDARY, UID_CAMERA);
static final int UID_SECONDARY_PRIVATE = UserHandle.getUid(USER_SECONDARY, UID_PRIVATE);
static final int UID_SECONDARY_PUBLIC = UserHandle.getUid(USER_SECONDARY, UID_PUBLIC);
static final int UID_SECONDARY_COMPLEX = UserHandle.getUid(USER_PRIMARY, UID_COMPLEX);
static final Uri URI_PHOTO_1 = Uri.parse("content://" + PKG_CAMERA + "/1");
static final Uri URI_PHOTO_2 = Uri.parse("content://" + PKG_CAMERA + "/2");
static final Uri URI_PRIVATE = Uri.parse("content://" + PKG_PRIVATE + "/42");
static final Uri URI_PUBLIC = Uri.parse("content://" + PKG_PUBLIC + "/42");
private final File mDir;
private final MockPackageManager mPackage;
private final MockContentResolver mResolver;
final ActivityManagerInternal mAmInternal;
final PackageManagerInternal mPmInternal;
public UriGrantsMockContext(@NonNull Context base) {
super(base);
mDir = new File(base.getFilesDir(), TAG);
mDir.mkdirs();
FileUtils.deleteContents(mDir);
mPackage = new MockPackageManager();
mResolver = new MockContentResolver(this);
mAmInternal = mock(ActivityManagerInternal.class);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
mPmInternal = mock(PackageManagerInternal.class);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPmInternal);
for (int userId : new int[] { USER_PRIMARY, USER_SECONDARY }) {
when(mPmInternal.getPackageUidInternal(eq(PKG_SOCIAL), anyInt(), eq(userId)))
.thenReturn(UserHandle.getUid(userId, UID_SOCIAL));
when(mPmInternal.getPackageUidInternal(eq(PKG_CAMERA), anyInt(), eq(userId)))
.thenReturn(UserHandle.getUid(userId, UID_CAMERA));
when(mPmInternal.getPackageUidInternal(eq(PKG_PRIVATE), anyInt(), eq(userId)))
.thenReturn(UserHandle.getUid(userId, UID_PRIVATE));
when(mPmInternal.getPackageUidInternal(eq(PKG_PUBLIC), anyInt(), eq(userId)))
.thenReturn(UserHandle.getUid(userId, UID_PUBLIC));
when(mPmInternal.getPackageUidInternal(eq(PKG_COMPLEX), anyInt(), eq(userId)))
.thenReturn(UserHandle.getUid(userId, UID_COMPLEX));
when(mPmInternal.resolveContentProvider(eq(PKG_CAMERA), anyInt(), eq(userId)))
.thenReturn(buildCameraProvider(userId));
when(mPmInternal.resolveContentProvider(eq(PKG_PRIVATE), anyInt(), eq(userId)))
.thenReturn(buildPrivateProvider(userId));
when(mPmInternal.resolveContentProvider(eq(PKG_PUBLIC), anyInt(), eq(userId)))
.thenReturn(buildPublicProvider(userId));
when(mPmInternal.resolveContentProvider(eq(PKG_COMPLEX), anyInt(), eq(userId)))
.thenReturn(buildComplexProvider(userId));
}
}
private static ProviderInfo buildCameraProvider(int userId) {
final ProviderInfo pi = new ProviderInfo();
pi.packageName = PKG_CAMERA;
pi.authority = PKG_CAMERA;
pi.readPermission = android.Manifest.permission.READ_EXTERNAL_STORAGE;
pi.writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
pi.grantUriPermissions = true;
pi.applicationInfo = new ApplicationInfo();
pi.applicationInfo.uid = UserHandle.getUid(userId, UID_CAMERA);
return pi;
}
private static ProviderInfo buildPrivateProvider(int userId) {
final ProviderInfo pi = new ProviderInfo();
pi.packageName = PKG_PRIVATE;
pi.authority = PKG_PRIVATE;
pi.exported = false;
pi.grantUriPermissions = false;
pi.applicationInfo = new ApplicationInfo();
pi.applicationInfo.uid = UserHandle.getUid(userId, UID_PRIVATE);
return pi;
}
private static ProviderInfo buildPublicProvider(int userId) {
final ProviderInfo pi = new ProviderInfo();
pi.packageName = PKG_PUBLIC;
pi.authority = PKG_PUBLIC;
pi.exported = true;
pi.grantUriPermissions = false;
pi.applicationInfo = new ApplicationInfo();
pi.applicationInfo.uid = UserHandle.getUid(userId, UID_PUBLIC);
return pi;
}
private static ProviderInfo buildComplexProvider(int userId) {
final ProviderInfo pi = new ProviderInfo();
pi.packageName = PKG_COMPLEX;
pi.authority = PKG_COMPLEX;
pi.exported = true;
pi.grantUriPermissions = true;
pi.applicationInfo = new ApplicationInfo();
pi.applicationInfo.uid = UserHandle.getUid(userId, UID_COMPLEX);
pi.pathPermissions = new PathPermission[] {
new PathPermission("/secure", PathPermission.PATTERN_PREFIX,
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE),
};
pi.uriPermissionPatterns = new PatternMatcher[] {
new PatternMatcher("/secure", PathPermission.PATTERN_PREFIX),
new PatternMatcher("/insecure", PathPermission.PATTERN_PREFIX),
};
return pi;
}
@Override
public PackageManager getPackageManager() {
return mPackage;
}
@Override
public ContentResolver getContentResolver() {
return mResolver;
}
@Override
public File getFilesDir() {
return mDir;
}
}

View File

@@ -0,0 +1,170 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.uri;
import static com.android.server.uri.UriGrantsMockContext.FLAG_PERSISTABLE;
import static com.android.server.uri.UriGrantsMockContext.FLAG_READ;
import static com.android.server.uri.UriGrantsMockContext.FLAG_WRITE;
import static com.android.server.uri.UriGrantsMockContext.PKG_CAMERA;
import static com.android.server.uri.UriGrantsMockContext.PKG_SOCIAL;
import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_SOCIAL;
import static com.android.server.uri.UriGrantsMockContext.URI_PHOTO_1;
import static com.android.server.uri.UriGrantsMockContext.URI_PHOTO_2;
import static com.android.server.uri.UriGrantsMockContext.USER_PRIMARY;
import static com.android.server.uri.UriPermission.INVALID_TIME;
import static com.android.server.uri.UriPermission.STRENGTH_GLOBAL;
import static com.android.server.uri.UriPermission.STRENGTH_NONE;
import static com.android.server.uri.UriPermission.STRENGTH_OWNED;
import static com.android.server.uri.UriPermission.STRENGTH_PERSISTABLE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class UriPermissionTest {
@Mock
private UriGrantsManagerInternal mService;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void testNone() {
final GrantUri grant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
final UriPermission perm = new UriPermission(PKG_CAMERA,
PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant);
assertEquals(STRENGTH_NONE, perm.getStrength(FLAG_READ));
}
@Test
public void testGlobal() {
final GrantUri grant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
final UriPermission perm = new UriPermission(PKG_CAMERA,
PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant);
assertFalse(perm.grantModes(FLAG_READ, null));
assertEquals(STRENGTH_GLOBAL, perm.getStrength(FLAG_READ));
assertFalse(perm.revokeModes(FLAG_READ, true));
assertEquals(STRENGTH_NONE, perm.getStrength(FLAG_READ));
}
@Test
public void testOwned() {
final GrantUri grant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
final UriPermission perm = new UriPermission(PKG_CAMERA,
PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant);
final UriPermissionOwner owner = new UriPermissionOwner(mService, "test");
assertFalse(perm.grantModes(FLAG_READ, owner));
assertEquals(STRENGTH_OWNED, perm.getStrength(FLAG_READ));
assertFalse(perm.revokeModes(FLAG_READ, false));
assertEquals(STRENGTH_OWNED, perm.getStrength(FLAG_READ));
assertFalse(perm.revokeModes(FLAG_READ, true));
assertEquals(STRENGTH_NONE, perm.getStrength(FLAG_READ));
}
@Test
public void testOverlap() {
final GrantUri grant1 = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
final GrantUri grant2 = new GrantUri(USER_PRIMARY, URI_PHOTO_2, FLAG_READ);
final UriPermission photo1 = new UriPermission(PKG_CAMERA,
PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant1);
final UriPermission photo2 = new UriPermission(PKG_CAMERA,
PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant2);
// Verify behavior when we have multiple owners within the same app
final UriPermissionOwner activity = new UriPermissionOwner(mService, "activity");
final UriPermissionOwner service = new UriPermissionOwner(mService, "service");
photo1.grantModes(FLAG_READ | FLAG_WRITE, activity);
photo1.grantModes(FLAG_READ, service);
photo2.grantModes(FLAG_READ, activity);
photo2.grantModes(FLAG_WRITE, null);
assertEquals(FLAG_READ | FLAG_WRITE, photo1.modeFlags);
assertEquals(FLAG_READ | FLAG_WRITE, photo2.modeFlags);
// Shutting down activity should only trim away write access
activity.removeUriPermissions();
assertEquals(FLAG_READ, photo1.modeFlags);
assertEquals(FLAG_WRITE, photo2.modeFlags);
// Shutting down service should bring everything else down
service.removeUriPermissions();
assertEquals(0, photo1.modeFlags);
assertEquals(FLAG_WRITE, photo2.modeFlags);
}
@Test
public void testPersist() {
final GrantUri grant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
final UriPermission perm = new UriPermission(PKG_CAMERA,
PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant);
assertFalse(perm.grantModes(FLAG_READ, null));
assertFalse(perm.grantModes(FLAG_WRITE | FLAG_PERSISTABLE, null));
assertEquals(STRENGTH_GLOBAL, perm.getStrength(FLAG_READ));
assertEquals(STRENGTH_PERSISTABLE, perm.getStrength(FLAG_WRITE));
// Verify behavior of non-persistable mode; nothing happens
{
assertFalse(perm.takePersistableModes(FLAG_READ));
assertEquals(0, perm.persistedModeFlags);
assertFalse(perm.releasePersistableModes(FLAG_READ));
assertEquals(0, perm.persistedModeFlags);
}
// Verify behavior of persistable mode
{
assertEquals(FLAG_WRITE, perm.persistableModeFlags);
assertEquals(0, perm.persistedModeFlags);
assertTrue(perm.takePersistableModes(FLAG_WRITE));
assertEquals(FLAG_WRITE, perm.persistableModeFlags);
assertEquals(FLAG_WRITE, perm.persistedModeFlags);
// Attempting to take a second time should be a no-op
final long createTime = perm.persistedCreateTime;
assertFalse(perm.takePersistableModes(FLAG_WRITE));
assertEquals(createTime, perm.persistedCreateTime);
assertTrue(perm.releasePersistableModes(FLAG_WRITE));
assertEquals(FLAG_WRITE, perm.persistableModeFlags);
assertEquals(0, perm.persistedModeFlags);
assertEquals(INVALID_TIME, perm.persistedCreateTime);
// Attempting to release a second time should be a no-op
assertFalse(perm.releasePersistableModes(FLAG_WRITE));
// We should still be able to take again
assertTrue(perm.takePersistableModes(FLAG_WRITE));
assertEquals(FLAG_WRITE, perm.persistableModeFlags);
assertEquals(FLAG_WRITE, perm.persistedModeFlags);
}
}
}