From ba2974c8a9b284a313b3b895905abf76c5a89ada Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Thu, 4 Mar 2021 12:52:52 -0700 Subject: [PATCH] Support for a Context to "renounce" permissions. Different logical components within an app may have no intention of interacting with data or services that are protected by specific permissions. The new overload added in this change provides the initial mechanism for a logical component to indicate a set of permissions that should be treated as "renounced". Interactions performed through the returned Context will ideally be treated as if the renounced permissions have not actually been granted to the application, regardless of their actual grant status. This is a low-risk change from a security standpoint, since it can only reduce the set of permissions that might have been granted to an app; it can never be used to expand the set of permissions. Note that this change only provides an initial implementation which only applies to local permission checks within the app; future changes will begin wiring this up across process boundaries. Bug: 181812281 Test: atest CtsContentTestCases:android.content.cts.ContextTest Change-Id: I96439e5344c85300fb6a0f03e572746c3c96ee95 --- core/api/current.txt | 1 + core/api/system-current.txt | 9 ++ core/java/android/app/ContextImpl.java | 73 +++++++++++------ core/java/android/content/Context.java | 8 ++ core/java/android/content/ContextParams.java | 82 +++++++++++++++++-- core/java/android/content/ContextWrapper.java | 11 +++ core/res/AndroidManifest.xml | 5 ++ 7 files changed, 154 insertions(+), 35 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 310e95990b85d..94a7d67509be7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -10376,6 +10376,7 @@ package android.content { method public abstract android.content.pm.PackageManager getPackageManager(); method public abstract String getPackageName(); method public abstract String getPackageResourcePath(); + method @Nullable public android.content.ContextParams getParams(); method public abstract android.content.res.Resources getResources(); method public abstract android.content.SharedPreferences getSharedPreferences(String, int); method @NonNull public final String getString(@StringRes int); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 3a238e29dd232..d4835e9af7748 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -228,6 +228,7 @@ package android { field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER"; field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; + field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS"; field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES"; field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; @@ -2184,6 +2185,14 @@ package android.content { field public static final String WIFI_SCANNING_SERVICE = "wifiscanner"; } + public final class ContextParams { + method @Nullable @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public java.util.Set getRenouncedPermissions(); + } + + public static final class ContextParams.Builder { + method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public android.content.ContextParams.Builder setRenouncedPermissions(@NonNull java.util.Set); + } + public class ContextWrapper extends android.content.Context { method public android.content.Context createCredentialProtectedStorageContext(); method @Nullable public java.io.File getPreloadsFileCache(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index fd56c449012f9..d040938803f6b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -24,7 +24,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; -import android.content.ContextParams; import android.content.AutofillOptions; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -32,6 +31,7 @@ import android.content.ContentCaptureOptions; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; +import android.content.ContextParams; import android.content.ContextWrapper; import android.content.IContentProvider; import android.content.IIntentReceiver; @@ -221,8 +221,7 @@ class ContextImpl extends Context { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final String mOpPackageName; - /** Attribution tag of this context */ - private final @Nullable String mAttributionTag; + private final @NonNull ContextParams mParams; private final @NonNull ResourcesManager mResourcesManager; @UnsupportedAppUsage @@ -470,7 +469,12 @@ class ContextImpl extends Context { /** @hide */ @Override public @Nullable String getAttributionTag() { - return mAttributionTag; + return mParams.getAttributionTag(); + } + + @Override + public @Nullable ContextParams getParams() { + return mParams; } @Override @@ -2047,6 +2051,11 @@ class ContextImpl extends Context { if (permission == null) { throw new IllegalArgumentException("permission is null"); } + if (mParams.isRenouncedPermission(permission) + && pid == android.os.Process.myPid() && uid == android.os.Process.myUid()) { + Log.v(TAG, "Treating renounced permission " + permission + " as denied"); + return PERMISSION_DENIED; + } return PermissionManager.checkPermission(permission, pid, uid); } @@ -2056,6 +2065,11 @@ class ContextImpl extends Context { if (permission == null) { throw new IllegalArgumentException("permission is null"); } + if (mParams.isRenouncedPermission(permission) + && pid == android.os.Process.myPid() && uid == android.os.Process.myUid()) { + Log.v(TAG, "Treating renounced permission " + permission + " as denied"); + return PERMISSION_DENIED; + } try { return ActivityManager.getService().checkPermissionWithToken( @@ -2093,6 +2107,10 @@ class ContextImpl extends Context { if (permission == null) { throw new IllegalArgumentException("permission is null"); } + if (mParams.isRenouncedPermission(permission)) { + Log.v(TAG, "Treating renounced permission " + permission + " as denied"); + return PERMISSION_DENIED; + } return checkPermission(permission, Process.myPid(), Process.myUid()); } @@ -2393,8 +2411,9 @@ class ContextImpl extends Context { LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE); if (pi != null) { - ContextImpl c = new ContextImpl(this, mMainThread, pi, null, null, mToken, - new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null); + ContextImpl c = new ContextImpl(this, mMainThread, pi, ContextParams.EMPTY, null, + mToken, new UserHandle(UserHandle.getUserId(application.uid)), + flags, null, null); final int displayId = getDisplayId(); final Integer overrideDisplayId = mForceDisplayOverrideInResources @@ -2423,14 +2442,14 @@ class ContextImpl extends Context { if (packageName.equals("system") || packageName.equals("android")) { // The system resources are loaded in every application, so we can safely copy // the context without reloading Resources. - return new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag, null, + return new ContextImpl(this, mMainThread, mPackageInfo, mParams, null, mToken, user, flags, null, null); } LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier()); if (pi != null) { - ContextImpl c = new ContextImpl(this, mMainThread, pi, mAttributionTag, null, + ContextImpl c = new ContextImpl(this, mMainThread, pi, mParams, null, mToken, user, flags, null, null); final int displayId = getDisplayId(); @@ -2469,7 +2488,7 @@ class ContextImpl extends Context { final String[] paths = mPackageInfo.getSplitPaths(splitName); final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, - mAttributionTag, splitName, mToken, mUser, mFlags, classLoader, null); + mParams, splitName, mToken, mUser, mFlags, classLoader, null); context.setResources(ResourcesManager.getInstance().getResources( mToken, @@ -2502,7 +2521,7 @@ class ContextImpl extends Context { overrideConfiguration = displayAdjustedConfig; } - ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams, mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = getDisplayId(); @@ -2520,7 +2539,7 @@ class ContextImpl extends Context { throw new IllegalArgumentException("display must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams, mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = display.getDisplayId(); @@ -2578,7 +2597,7 @@ class ContextImpl extends Context { ContextImpl createBaseWindowContext(IBinder token, Display display) { - ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams, mSplitName, token, mUser, mFlags, mClassLoader, null); // Window contexts receive configurations directly from the server and as such do not // need to override their display in ResourcesManager. @@ -2609,21 +2628,21 @@ class ContextImpl extends Context { @NonNull @Override - public Context createContext(@NonNull ContextParams contextParams) { - return this; + public Context createContext(@NonNull ContextParams params) { + return new ContextImpl(this, mMainThread, mPackageInfo, params, mSplitName, + mToken, mUser, mFlags, mClassLoader, null); } @Override public @NonNull Context createAttributionContext(@Nullable String attributionTag) { - return new ContextImpl(this, mMainThread, mPackageInfo, attributionTag, mSplitName, - mToken, mUser, mFlags, mClassLoader, null); + return createContext(new ContextParams.Builder().setAttributionTag(attributionTag).build()); } @Override public Context createDeviceProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) | Context.CONTEXT_DEVICE_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag, mSplitName, + return new ContextImpl(this, mMainThread, mPackageInfo, mParams, mSplitName, mToken, mUser, flags, mClassLoader, null); } @@ -2631,7 +2650,7 @@ class ContextImpl extends Context { public Context createCredentialProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE) | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag, mSplitName, + return new ContextImpl(this, mMainThread, mPackageInfo, mParams, mSplitName, mToken, mUser, flags, mClassLoader, null); } @@ -2805,8 +2824,8 @@ class ContextImpl extends Context { @UnsupportedAppUsage static ContextImpl createSystemContext(ActivityThread mainThread) { LoadedApk packageInfo = new LoadedApk(mainThread); - ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, null, - 0, null, null); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, + ContextParams.EMPTY, null, null, null, 0, null, null); context.setResources(packageInfo.getResources()); context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), context.mResourcesManager.getDisplayMetrics()); @@ -2823,8 +2842,8 @@ class ContextImpl extends Context { */ static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) { final LoadedApk packageInfo = systemContext.mPackageInfo; - ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null, - null, null, null, 0, null, null); + ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, + ContextParams.EMPTY, null, null, null, 0, null, null); context.setResources(createResources(null, packageInfo, null, displayId, null, packageInfo.getCompatibilityInfo(), null)); context.updateDisplay(displayId); @@ -2848,8 +2867,8 @@ class ContextImpl extends Context { static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo, String opPackageName) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); - ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, null, - 0, null, opPackageName); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, + ContextParams.EMPTY, null, null, null, 0, null, opPackageName); context.setResources(packageInfo.getResources()); context.mContextType = isSystemOrSystemUI(context) ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI : CONTEXT_TYPE_NON_UI; @@ -2878,7 +2897,7 @@ class ContextImpl extends Context { } } - ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, ContextParams.EMPTY, activityInfo.splitName, activityToken, null, 0, classLoader, null); context.mContextType = CONTEXT_TYPE_ACTIVITY; @@ -2911,7 +2930,7 @@ class ContextImpl extends Context { } private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread, - @NonNull LoadedApk packageInfo, @Nullable String attributionTag, + @NonNull LoadedApk packageInfo, @NonNull ContextParams params, @Nullable String splitName, @Nullable IBinder token, @Nullable UserHandle user, int flags, @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName) { mOuterContext = this; @@ -2966,7 +2985,7 @@ class ContextImpl extends Context { } mOpPackageName = overrideOpPackageName != null ? overrideOpPackageName : opPackageName; - mAttributionTag = attributionTag; + mParams = Objects.requireNonNull(params); mContentResolver = new ApplicationContentResolver(this, mainThread); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 48754cccfe0d7..f055ab7ef47b6 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -888,6 +888,14 @@ public abstract class Context { return getAttributionTag(); } + /** + * Return the set of parameters which this Context was created with, if it + * was created via {@link #createContext(ContextParams)}. + */ + public @Nullable ContextParams getParams() { + return null; + } + /** Return the full application info for this context's package. */ public abstract ApplicationInfo getApplicationInfo(); diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java index 16128a650c123..17ec2a847d4fe 100644 --- a/core/java/android/content/ContextParams.java +++ b/core/java/android/content/ContextParams.java @@ -18,6 +18,13 @@ package android.content; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; /** * This class represents rules around how a context being created via @@ -48,9 +55,19 @@ import android.annotation.Nullable; * @see Context#createContext(ContextParams) */ public final class ContextParams { + private final String mAttributionTag; + private final String mReceiverPackage; + private final String mReceiverAttributionTag; + private final Set mRenouncedPermissions; - private ContextParams() { - /* hide ctor */ + /** {@hide} */ + public static final ContextParams EMPTY = new ContextParams.Builder().build(); + + private ContextParams(@NonNull ContextParams.Builder builder) { + mAttributionTag = builder.mAttributionTag; + mReceiverPackage = builder.mReceiverPackage; + mReceiverAttributionTag = builder.mReceiverAttributionTag; + mRenouncedPermissions = builder.mRenouncedPermissions; } /** @@ -58,7 +75,7 @@ public final class ContextParams { */ @Nullable public String getAttributionTag() { - return null; + return mAttributionTag; } /** @@ -66,7 +83,7 @@ public final class ContextParams { */ @Nullable public String getReceiverPackage() { - return null; + return mReceiverPackage; } /** @@ -74,13 +91,33 @@ public final class ContextParams { */ @Nullable public String getReceiverAttributionTag() { - return null; + return mReceiverAttributionTag; + } + + /** + * @return The set of permissions to treat as renounced. + * @hide + */ + @SystemApi + @SuppressLint("NullableCollection") + @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) + public @Nullable Set getRenouncedPermissions() { + return mRenouncedPermissions; + } + + /** @hide */ + public boolean isRenouncedPermission(@NonNull String permission) { + return mRenouncedPermissions != null && mRenouncedPermissions.contains(permission); } /** * Builder for creating a {@link ContextParams}. */ public static final class Builder { + private String mAttributionTag; + private String mReceiverPackage; + private String mReceiverAttributionTag; + private Set mRenouncedPermissions; /** * Sets an attribution tag against which to track permission accesses. @@ -90,6 +127,7 @@ public final class ContextParams { */ @NonNull public Builder setAttributionTag(@NonNull String attributionTag) { + mAttributionTag = Objects.requireNonNull(attributionTag); return this; } @@ -104,18 +142,46 @@ public final class ContextParams { @NonNull public Builder setReceiverPackage(@NonNull String packageName, @Nullable String attributionTag) { + mReceiverPackage = Objects.requireNonNull(packageName); + mReceiverAttributionTag = attributionTag; return this; } /** - * Creates a new instance. You need to either specify an attribution tag - * or a receiver package or both. + * Sets permissions which have been voluntarily "renounced" by the + * calling app. + *

+ * Interactions performed through the created Context will ideally be + * treated as if these "renounced" permissions have not actually been + * granted to the app, regardless of their actual grant status. + *

+ * This is designed for use by separate logical components within an app + * which have no intention of interacting with data or services that are + * protected by the renounced permissions. + *

+ * Note that only {@link PermissionInfo#PROTECTION_DANGEROUS} + * permissions are supported by this mechanism. + * + * @param renouncedPermissions The set of permissions to treat as + * renounced. + * @return This builder. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) + public @NonNull Builder setRenouncedPermissions(@NonNull Set renouncedPermissions) { + mRenouncedPermissions = Collections.unmodifiableSet(renouncedPermissions); + return this; + } + + /** + * Creates a new instance. * * @return The new instance. */ @NonNull public ContextParams build() { - return new ContextParams(); + return new ContextParams(this); } } } diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index b71fb2712c24d..609f417a80084 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -171,6 +171,11 @@ public class ContextWrapper extends Context { return mBase.getAttributionTag(); } + @Override + public @Nullable ContextParams getParams() { + return mBase.getParams(); + } + @Override public ApplicationInfo getApplicationInfo() { return mBase.getApplicationInfo(); @@ -1044,6 +1049,12 @@ public class ContextWrapper extends Context { return mBase.createWindowContext(display, type, options); } + @Override + @NonNull + public Context createContext(@NonNull ContextParams contextParams) { + return mBase.createContext(contextParams); + } + @Override public @NonNull Context createAttributionContext(@Nullable String attributionTag) { return mBase.createAttributionContext(attributionTag); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 072bb8799e593..4bbb69fdeb978 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5561,6 +5561,11 @@ + + +