From 1d194f92379711308c7eb1ce3bf07896391ea045 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Sat, 11 Apr 2020 21:12:58 -0600 Subject: 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 --- .../android/server/am/ActivityManagerService.java | 9 +- .../core/java/com/android/server/uri/GrantUri.java | 13 +- .../com/android/server/uri/NeededUriGrants.java | 11 +- .../core/java/com/android/server/uri/TEST_MAPPING | 30 ++ .../server/uri/UriGrantsManagerInternal.java | 1 - .../server/uri/UriGrantsManagerService.java | 394 ++++++++++----------- .../java/com/android/server/uri/UriPermission.java | 10 +- .../com/android/server/uri/UriPermissionOwner.java | 1 + .../server/uri/UriGrantsManagerServiceTest.java | 355 +++++++++++++++++++ .../android/server/uri/UriGrantsMockContext.java | 207 +++++++++++ .../com/android/server/uri/UriPermissionTest.java | 170 +++++++++ 11 files changed, 972 insertions(+), 229 deletions(-) create mode 100644 services/core/java/com/android/server/uri/TEST_MAPPING create mode 100644 services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java create mode 100644 services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java create mode 100644 services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e41ba0e1745d..e697d9f481ed 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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); } } diff --git a/services/core/java/com/android/server/uri/GrantUri.java b/services/core/java/com/android/server/uri/GrantUri.java index 15715757d6e0..f9b6a7a81dee 100644 --- a/services/core/java/com/android/server/uri/GrantUri.java +++ b/services/core/java/com/android/server/uri/GrantUri.java @@ -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); } } } diff --git a/services/core/java/com/android/server/uri/NeededUriGrants.java b/services/core/java/com/android/server/uri/NeededUriGrants.java index 012039484dc9..8c8f55304fbb 100644 --- a/services/core/java/com/android/server/uri/NeededUriGrants.java +++ b/services/core/java/com/android/server/uri/NeededUriGrants.java @@ -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 { +public class NeededUriGrants { final String targetPkg; final int targetUid; final int flags; + final ArraySet 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 { 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); } diff --git a/services/core/java/com/android/server/uri/TEST_MAPPING b/services/core/java/com/android/server/uri/TEST_MAPPING new file mode 100644 index 000000000000..e5cda03be25d --- /dev/null +++ b/services/core/java/com/android/server/uri/TEST_MAPPING @@ -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" + } + ] + } + ] +} diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java index 2f50fcb08c02..8afb87f3020d 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java @@ -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); diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index fe34e86a27ae..b8c2f90cf047 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -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,19 +146,30 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { private final SparseArray> 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"); } - private void start() { - LocalServices.addService(UriGrantsManagerInternal.class, new LocalService()); + @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; } - void onActivityManagerInternalAdded() { - mAmInternal = LocalServices.getService(ActivityManagerInternal.class); + @VisibleForTesting + UriGrantsManagerInternal getLocalService() { + return new LocalService(); + } + + private void start() { + LocalServices.addService(UriGrantsManagerInternal.class, new LocalService()); } public static final class Lifecycle extends SystemService { @@ -163,7 +177,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { 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 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 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 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 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 - // below removes this ability - boolean allowDefaultRead = pi.readPermission == null; - boolean allowDefaultWrite = pi.writePermission == null; - - // check if target holds any 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; - } + // check if target holds top-level 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; + } + + // track if unprotected read/write is allowed; any denied + // below removes this ability + boolean allowDefaultRead = pi.readPermission == null; + boolean allowDefaultWrite = pi.writePermission == null; + + // check if target holds any 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 read/write, if not blocked by - // above - if (allowDefaultRead) readMet = true; - if (allowDefaultWrite) writeMet = true; - - } catch (RemoteException e) { - return false; } + // grant unprotected read/write, if not blocked by + // 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,11 +1126,27 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } } } + 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) { - return allowedResult; + 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: * - The target is on another user. * - Apps on the current user can access the uri without any uid permissions. @@ -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) { - 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 mReadOwners; private ArraySet 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) { diff --git a/services/core/java/com/android/server/uri/UriPermissionOwner.java b/services/core/java/com/android/server/uri/UriPermissionOwner.java index f2c06cd696b2..2b404a43a338 100644 --- a/services/core/java/com/android/server/uri/UriPermissionOwner.java +++ b/services/core/java/com/android/server/uri/UriPermissionOwner.java @@ -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; diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java new file mode 100644 index 000000000000..cf1978ec47a1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java @@ -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 Set asSet(T... values) { + return new ArraySet(Arrays.asList(values)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java new file mode 100644 index 000000000000..989928dbbcc4 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java @@ -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; + } +} diff --git a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java new file mode 100644 index 000000000000..07005a9902d7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java @@ -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); + } + } +} -- cgit v1.2.3-59-g8ed1b