Merge "Uri permission grants improvements, tests, fixes." into rvc-dev

This commit is contained in:
Jeff Sharkey
2020-04-17 17:57:16 +00:00
committed by Android (Google) Code Review
11 changed files with 972 additions and 229 deletions

View File

@@ -2683,7 +2683,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
@@ -6412,7 +6411,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;
}
@@ -6424,7 +6423,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) {
@@ -6482,8 +6481,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);
}
}
}