diff options
| author | Jeff Sharkey <jsharkey@google.com> | 2020-04-17 17:57:16 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-04-17 17:57:16 +0000 |
| commit | 64bd9c7be84bbb1ff2a94de4ba917fdf5af75a93 (patch) | |
| tree | 7120869cdc9ca34431a00b8b92cc10af3ef370de | |
| parent | 26e55eb8ab8a8a476ca63d81e5567c16443c056b (diff) | |
| parent | 1d194f92379711308c7eb1ce3bf07896391ea045 (diff) | |
Merge "Uri permission grants improvements, tests, fixes." into rvc-dev
11 files changed, 972 insertions, 229 deletions
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2a9f503602ac..89aee78677c6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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); } } 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<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); } 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<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"); } - 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<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 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; - } + // 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; + } + + // 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,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<N; i++) { - if (pi.uriPermissionPatterns[i] != null - && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) { - allowed = true; - break; - } + 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 (!allowed) { + } + } + 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 permission to path of Uri " - + grantUri); + + " 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 + ")"); } } // 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) { @@ -1419,13 +1412,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } @Override - public void onActivityManagerInternalAdded() { - synchronized (mLock) { - UriGrantsManagerService.this.onActivityManagerInternalAdded(); - } - } - - @Override public IBinder newUriPermissionOwner(String name) { enforceNotIsolatedCaller("newUriPermissionOwner"); synchronized(mLock) { @@ -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); diff --git a/services/core/java/com/android/server/uri/UriPermission.java b/services/core/java/com/android/server/uri/UriPermission.java index bd6348a5661b..6db781ac419a 100644 --- a/services/core/java/com/android/server/uri/UriPermission.java +++ b/services/core/java/com/android/server/uri/UriPermission.java @@ -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) { 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 <T> Set<T> asSet(T... values) { + return new ArraySet<T>(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); + } + } +} |
