Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ba2974c8 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

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
parent e41ad67f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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);
+9 −0
Original line number Diff line number Diff line
@@ -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<java.lang.String> 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<java.lang.String>);
  }
  public class ContextWrapper extends android.content.Context {
    method public android.content.Context createCredentialProtectedStorageContext();
    method @Nullable public java.io.File getPreloadsFileCache();
+46 −27
Original line number Diff line number Diff line
@@ -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);
    }

+8 −0
Original line number Diff line number Diff line
@@ -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();

+74 −8
Original line number Diff line number Diff line
@@ -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<String> mRenouncedPermissions;

    /** {@hide} */
    public static final ContextParams EMPTY = new ContextParams.Builder().build();

    private ContextParams() {
        /* hide ctor */
    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<String> 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<String> 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;
        }

        /**
         * Sets permissions which have been voluntarily "renounced" by the
         * calling app.
         * <p>
         * 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.
         * <p>
         * 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.
         * <p>
         * 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<String> renouncedPermissions) {
            mRenouncedPermissions = Collections.unmodifiableSet(renouncedPermissions);
            return this;
        }

        /**
         * Creates a new instance. You need to either specify an attribution tag
         * or a receiver package or both.
         * Creates a new instance.
         *
         * @return The new instance.
         */
        @NonNull
        public ContextParams build() {
            return new ContextParams();
            return new ContextParams(this);
        }
    }
}
Loading