diff options
-rw-r--r-- | Android.bp | 1 | ||||
-rw-r--r-- | core/java/android/content/AttributionSource.java | 385 | ||||
-rw-r--r-- | core/java/android/content/PermissionChecker.java | 502 | ||||
-rw-r--r-- | core/java/android/permission/PermissionManager.java | 4 | ||||
-rw-r--r-- | services/core/java/com/android/server/pm/permission/PermissionManagerService.java | 420 |
5 files changed, 673 insertions, 639 deletions
diff --git a/Android.bp b/Android.bp index 8db5589d2ef6..685c69df6823 100644 --- a/Android.bp +++ b/Android.bp @@ -325,7 +325,6 @@ java_defaults { "tv_tuner_resource_manager_aidl_interface-java", "soundtrigger_middleware-aidl-java", "modules-utils-os", - "framework-permission-aidl-java", ], } diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 7ab731f15ad2..2c155d5884ac 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -31,9 +31,10 @@ import android.permission.PermissionManager; import android.util.ArraySet; import com.android.internal.annotations.Immutable; +import com.android.internal.util.CollectionUtils; +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; -import java.util.Arrays; -import java.util.Collections; import java.util.Objects; import java.util.Set; @@ -69,10 +70,10 @@ import java.util.Set; * This is supported to handle cases where you don't have access to the caller's attribution * source and you can directly use the {@link AttributionSource.Builder} APIs. However, * if the data flows through more than two apps (more than you access the data for the - * caller) you need to have a handle to the {@link AttributionSource} for the calling app's - * context in order to create an attribution context. This means you either need to have an - * API for the other app to send you its attribution source or use a platform API that pipes - * the callers attribution source. + * caller - which you cannot know ahead of time) you need to have a handle to the {@link + * AttributionSource} for the calling app's context in order to create an attribution context. + * This means you either need to have an API for the other app to send you its attribution + * source or use a platform API that pipes the callers attribution source. * <p> * You cannot forge an attribution chain without the participation of every app in the * attribution chain (aside of the special case mentioned above). To create an attribution @@ -84,11 +85,80 @@ import java.util.Set; * permission protected APIs since some app in the chain may not have the permission. */ @Immutable +// TODO: Codegen doesn't properly verify the class if the parcelling is inner class +// TODO: Codegen doesn't allow overriding the constructor to change its visibility +// TODO: Codegen applies method level annotations to argument vs the generated member (@SystemApi) +// TODO: Codegen doesn't properly read/write IBinder members +// TODO: Codegen doesn't properly handle Set arguments +// TODO: Codegen requires @SystemApi annotations on fields which breaks +// android.signature.cts.api.AnnotationTest (need to update the test) +// @DataClass(genEqualsHashCode = true, genConstructor = false, genBuilder = true) public final class AttributionSource implements Parcelable { - private final @NonNull AttributionSourceState mAttributionSourceState; + /** + * @hide + */ + static class RenouncedPermissionsParcelling implements Parcelling<Set<String>> { + + @Override + public void parcel(Set<String> item, Parcel dest, int parcelFlags) { + if (item == null) { + dest.writeInt(-1); + } else { + dest.writeInt(item.size()); + for (String permission : item) { + dest.writeString8(permission); + } + } + } + + @Override + public Set<String> unparcel(Parcel source) { + final int size = source.readInt(); + if (size < 0) { + return null; + } + final ArraySet<String> result = new ArraySet<>(size); + for (int i = 0; i < size; i++) { + result.add(source.readString8()); + } + return result; + } + } + + /** + * The UID that is accessing the permission protected data. + */ + private final int mUid; + + /** + * The package that is accessing the permission protected data. + */ + private @Nullable String mPackageName = null; - private @Nullable AttributionSource mNextCached; - private @Nullable Set<String> mRenouncedPermissionsCached; + /** + * The attribution tag of the app accessing the permission protected data. + */ + private @Nullable String mAttributionTag = null; + + /** + * Unique token for that source. + * + * @hide + */ + private @Nullable IBinder mToken = null; + + /** + * Permissions that should be considered revoked regardless if granted. + * + * @hide + */ + @DataClass.ParcelWith(RenouncedPermissionsParcelling.class) + private @Nullable Set<String> mRenouncedPermissions = null; + + /** + * The next app to receive the permission protected data. + */ + private @Nullable AttributionSource mNext = null; /** @hide */ @TestApi @@ -101,7 +171,8 @@ public final class AttributionSource implements Parcelable { @TestApi public AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable AttributionSource next) { - this(uid, packageName, attributionTag, /*renouncedPermissions*/ null, next); + this(uid, packageName, attributionTag, /*token*/ null, + /*renouncedPermissions*/ null, next); } /** @hide */ @@ -109,8 +180,8 @@ public final class AttributionSource implements Parcelable { public AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions, @Nullable AttributionSource next) { - this(uid, packageName, attributionTag, /*token*/ null, (renouncedPermissions != null) - ? renouncedPermissions.toArray(new String[0]) : null, next); + this(uid, packageName, attributionTag, /*token*/ null, + renouncedPermissions, next); } /** @hide */ @@ -120,49 +191,16 @@ public final class AttributionSource implements Parcelable { /*token*/ null, /*renouncedPermissions*/ null, next); } - AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, - @Nullable IBinder token, @Nullable String[] renouncedPermissions, - @Nullable AttributionSource next) { - mAttributionSourceState = new AttributionSourceState(); - mAttributionSourceState.uid = uid; - mAttributionSourceState.packageName = packageName; - mAttributionSourceState.attributionTag = attributionTag; - mAttributionSourceState.token = token; - mAttributionSourceState.renouncedPermissions = renouncedPermissions; - mAttributionSourceState.next = (next != null) ? new AttributionSourceState[] - {next.mAttributionSourceState} : null; - } - - AttributionSource(@NonNull Parcel in) { - this(AttributionSourceState.CREATOR.createFromParcel(in)); - } - - /** @hide */ - public AttributionSource(@NonNull AttributionSourceState attributionSourceState) { - mAttributionSourceState = attributionSourceState; - } - /** @hide */ public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) { - return new AttributionSource(getUid(), getPackageName(), getAttributionTag(), - getToken(), mAttributionSourceState.renouncedPermissions, next); + return new AttributionSource(mUid, mPackageName, mAttributionTag, mToken, + mRenouncedPermissions, next); } /** @hide */ public AttributionSource withToken(@Nullable IBinder token) { - return new AttributionSource(getUid(), getPackageName(), getAttributionTag(), - token, mAttributionSourceState.renouncedPermissions, getNext()); - } - - /** @hide */ - public AttributionSource withPackageName(@Nullable String packageName) { - return new AttributionSource(getUid(), packageName, getAttributionTag(), getToken(), - mAttributionSourceState.renouncedPermissions, getNext()); - } - - /** @hide */ - public @NonNull AttributionSourceState asState() { - return mAttributionSourceState; + return new AttributionSource(mUid, mPackageName, mAttributionTag, token, + mRenouncedPermissions, mNext); } /** @@ -175,9 +213,10 @@ public final class AttributionSource implements Parcelable { * from the caller. */ public void enforceCallingUid() { - if (!checkCallingUid()) { - throw new SecurityException("Calling uid: " + Binder.getCallingUid() - + " doesn't match source uid: " + mAttributionSourceState.uid); + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID && callingUid != mUid) { + throw new SecurityException("Calling uid: " + callingUid + + " doesn't match source uid: " + mUid); } // No need to check package as app ops manager does it already. } @@ -192,8 +231,7 @@ public final class AttributionSource implements Parcelable { */ public boolean checkCallingUid() { final int callingUid = Binder.getCallingUid(); - if (callingUid != Process.SYSTEM_UID - && callingUid != mAttributionSourceState.uid) { + if (callingUid != Process.SYSTEM_UID && callingUid != mUid) { return false; } // No need to check package as app ops manager does it already. @@ -204,12 +242,11 @@ public final class AttributionSource implements Parcelable { public String toString() { if (Build.IS_DEBUGGABLE) { return "AttributionSource { " + - "uid = " + mAttributionSourceState.uid + ", " + - "packageName = " + mAttributionSourceState.packageName + ", " + - "attributionTag = " + mAttributionSourceState.attributionTag + ", " + - "token = " + mAttributionSourceState.token + ", " + - "next = " + (mAttributionSourceState.next != null - ? mAttributionSourceState.next[0]: null) + + "uid = " + mUid + ", " + + "packageName = " + mPackageName + ", " + + "attributionTag = " + mAttributionTag + ", " + + "token = " + mToken + ", " + + "next = " + mNext + " }"; } return super.toString(); @@ -221,8 +258,8 @@ public final class AttributionSource implements Parcelable { * @hide */ public int getNextUid() { - if (mAttributionSourceState.next != null) { - return mAttributionSourceState.next[0].uid; + if (mNext != null) { + return mNext.getUid(); } return Process.INVALID_UID; } @@ -233,8 +270,8 @@ public final class AttributionSource implements Parcelable { * @hide */ public @Nullable String getNextPackageName() { - if (mAttributionSourceState.next != null) { - return mAttributionSourceState.next[0].packageName; + if (mNext != null) { + return mNext.getPackageName(); } return null; } @@ -246,8 +283,8 @@ public final class AttributionSource implements Parcelable { * @hide */ public @Nullable String getNextAttributionTag() { - if (mAttributionSourceState.next != null) { - return mAttributionSourceState.next[0].attributionTag; + if (mNext != null) { + return mNext.getAttributionTag(); } return null; } @@ -260,9 +297,8 @@ public final class AttributionSource implements Parcelable { * @return Whether this is a trusted source. */ public boolean isTrusted(@NonNull Context context) { - return mAttributionSourceState.token != null - && context.getSystemService(PermissionManager.class) - .isRegisteredAttributionSource(this); + return mToken != null && context.getSystemService(PermissionManager.class) + .isRegisteredAttributionSource(this); } /** @@ -274,36 +310,71 @@ public final class AttributionSource implements Parcelable { @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) @NonNull public Set<String> getRenouncedPermissions() { - if (mRenouncedPermissionsCached == null) { - if (mAttributionSourceState.renouncedPermissions != null) { - mRenouncedPermissionsCached = new ArraySet<>( - mAttributionSourceState.renouncedPermissions); - } else { - mRenouncedPermissionsCached = Collections.emptySet(); - } - } - return mRenouncedPermissionsCached; + return CollectionUtils.emptyIfNull(mRenouncedPermissions); + } + + @DataClass.Suppress({"setUid", "setToken"}) + static class BaseBuilder {} + + + + + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/AttributionSource.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /* package-private */ AttributionSource( + int uid, + @Nullable String packageName, + @Nullable String attributionTag, + @Nullable IBinder token, + @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) @Nullable Set<String> renouncedPermissions, + @Nullable AttributionSource next) { + this.mUid = uid; + this.mPackageName = packageName; + this.mAttributionTag = attributionTag; + this.mToken = token; + this.mRenouncedPermissions = renouncedPermissions; + com.android.internal.util.AnnotationValidations.validate( + SystemApi.class, null, mRenouncedPermissions); + com.android.internal.util.AnnotationValidations.validate( + RequiresPermission.class, null, mRenouncedPermissions, + "value", android.Manifest.permission.RENOUNCE_PERMISSIONS); + this.mNext = next; + + // onConstructed(); // You can define this method to get a callback } /** * The UID that is accessing the permission protected data. */ public int getUid() { - return mAttributionSourceState.uid; + return mUid; } /** * The package that is accessing the permission protected data. */ public @Nullable String getPackageName() { - return mAttributionSourceState.packageName; + return mPackageName; } /** * The attribution tag of the app accessing the permission protected data. */ public @Nullable String getAttributionTag() { - return mAttributionSourceState.attributionTag; + return mAttributionTag; } /** @@ -312,56 +383,113 @@ public final class AttributionSource implements Parcelable { * @hide */ public @Nullable IBinder getToken() { - return mAttributionSourceState.token; + return mToken; } /** * The next app to receive the permission protected data. */ public @Nullable AttributionSource getNext() { - if (mNextCached == null && mAttributionSourceState.next != null) { - mNextCached = new AttributionSource(mAttributionSourceState.next[0]); - } - return mNextCached; + return mNext; } @Override public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(AttributionSource other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") AttributionSource that = (AttributionSource) o; - return mAttributionSourceState.uid == that.mAttributionSourceState.uid - && Objects.equals(mAttributionSourceState.packageName, - that.mAttributionSourceState.packageName) - && Objects.equals(mAttributionSourceState.attributionTag, - that.mAttributionSourceState.attributionTag) - && Objects.equals(mAttributionSourceState.token, - that.mAttributionSourceState.token) - && Arrays.equals(mAttributionSourceState.renouncedPermissions, - that.mAttributionSourceState.renouncedPermissions) - && Objects.equals(getNext(), that.getNext()); + //noinspection PointlessBooleanExpression + return true + && mUid == that.mUid + && Objects.equals(mPackageName, that.mPackageName) + && Objects.equals(mAttributionTag, that.mAttributionTag) + && Objects.equals(mToken, that.mToken) + && Objects.equals(mRenouncedPermissions, that.mRenouncedPermissions) + && Objects.equals(mNext, that.mNext); } @Override public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + int _hash = 1; - _hash = 31 * _hash + mAttributionSourceState.uid; - _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.packageName); - _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.attributionTag); - _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.token); - _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.renouncedPermissions); - _hash = 31 * _hash + Objects.hashCode(getNext()); + _hash = 31 * _hash + mUid; + _hash = 31 * _hash + Objects.hashCode(mPackageName); + _hash = 31 * _hash + Objects.hashCode(mAttributionTag); + _hash = 31 * _hash + Objects.hashCode(mToken); + _hash = 31 * _hash + Objects.hashCode(mRenouncedPermissions); + _hash = 31 * _hash + Objects.hashCode(mNext); return _hash; } + static Parcelling<Set<String>> sParcellingForRenouncedPermissions = + Parcelling.Cache.get( + RenouncedPermissionsParcelling.class); + static { + if (sParcellingForRenouncedPermissions == null) { + sParcellingForRenouncedPermissions = Parcelling.Cache.put( + new RenouncedPermissionsParcelling()); + } + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - mAttributionSourceState.writeToParcel(dest, flags); + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mPackageName != null) flg |= 0x2; + if (mAttributionTag != null) flg |= 0x4; + if (mToken != null) flg |= 0x8; + if (mRenouncedPermissions != null) flg |= 0x10; + if (mNext != null) flg |= 0x20; + dest.writeByte(flg); + dest.writeInt(mUid); + if (mPackageName != null) dest.writeString(mPackageName); + if (mAttributionTag != null) dest.writeString(mAttributionTag); + if (mToken != null) dest.writeStrongBinder(mToken); + sParcellingForRenouncedPermissions.parcel(mRenouncedPermissions, dest, flags); + if (mNext != null) dest.writeTypedObject(mNext, flags); } @Override public int describeContents() { return 0; } + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + /* package-private */ AttributionSource(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + int uid = in.readInt(); + String packageName = (flg & 0x2) == 0 ? null : in.readString(); + String attributionTag = (flg & 0x4) == 0 ? null : in.readString(); + IBinder token = (flg & 0x8) == 0 ? null : in.readStrongBinder(); + Set<String> renouncedPermissions = sParcellingForRenouncedPermissions.unparcel(in); + AttributionSource next = (flg & 0x20) == 0 ? null : (AttributionSource) in.readTypedObject(AttributionSource.CREATOR); + + this.mUid = uid; + this.mPackageName = packageName; + this.mAttributionTag = attributionTag; + this.mToken = token; + this.mRenouncedPermissions = renouncedPermissions; + com.android.internal.util.AnnotationValidations.validate( + SystemApi.class, null, mRenouncedPermissions); + com.android.internal.util.AnnotationValidations.validate( + RequiresPermission.class, null, mRenouncedPermissions, + "value", android.Manifest.permission.RENOUNCE_PERMISSIONS); + this.mNext = next; + + // onConstructed(); // You can define this method to get a callback + } + public static final @NonNull Parcelable.Creator<AttributionSource> CREATOR = new Parcelable.Creator<AttributionSource>() { @Override @@ -378,9 +506,15 @@ public final class AttributionSource implements Parcelable { /** * A builder for {@link AttributionSource} */ - public static final class Builder { - private @NonNull final AttributionSourceState mAttributionSourceState = - new AttributionSourceState(); + @SuppressWarnings("WeakerAccess") + public static final class Builder extends BaseBuilder { + + private int mUid; + private @Nullable String mPackageName; + private @Nullable String mAttributionTag; + private @Nullable IBinder mToken; + private @Nullable Set<String> mRenouncedPermissions; + private @Nullable AttributionSource mNext; private long mBuilderFieldsSet = 0L; @@ -390,8 +524,9 @@ public final class AttributionSource implements Parcelable { * @param uid * The UID that is accessing the permission protected data. */ - public Builder(int uid) { - mAttributionSourceState.uid = uid; + public Builder( + int uid) { + mUid = uid; } /** @@ -400,7 +535,7 @@ public final class AttributionSource implements Parcelable { public @NonNull Builder setPackageName(@Nullable String value) { checkNotUsed(); mBuilderFieldsSet |= 0x2; - mAttributionSourceState.packageName = value; + mPackageName = value; return this; } @@ -410,7 +545,7 @@ public final class AttributionSource implements Parcelable { public @NonNull Builder setAttributionTag(@Nullable String value) { checkNotUsed(); mBuilderFieldsSet |= 0x4; - mAttributionSourceState.attributionTag = value; + mAttributionTag = value; return this; } @@ -443,8 +578,7 @@ public final class AttributionSource implements Parcelable { public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) { checkNotUsed(); mBuilderFieldsSet |= 0x10; - mAttributionSourceState.renouncedPermissions = (value != null) - ? value.toArray(new String[0]) : null; + mRenouncedPermissions = value; return this; } @@ -454,8 +588,7 @@ public final class AttributionSource implements Parcelable { public @NonNull Builder setNext(@Nullable AttributionSource value) { checkNotUsed(); mBuilderFieldsSet |= 0x20; - mAttributionSourceState.next = (value != null) ? new AttributionSourceState[] - {value.mAttributionSourceState} : null; + mNext = value; return this; } @@ -465,21 +598,28 @@ public final class AttributionSource implements Parcelable { mBuilderFieldsSet |= 0x40; // Mark builder used if ((mBuilderFieldsSet & 0x2) == 0) { - mAttributionSourceState.packageName = null; + mPackageName = null; } if ((mBuilderFieldsSet & 0x4) == 0) { - mAttributionSourceState.attributionTag = null; + mAttributionTag = null; } if ((mBuilderFieldsSet & 0x8) == 0) { - mAttributionSourceState.token = null; + mToken = null; } if ((mBuilderFieldsSet & 0x10) == 0) { - mAttributionSourceState.renouncedPermissions = null; + mRenouncedPermissions = null; } if ((mBuilderFieldsSet & 0x20) == 0) { - mAttributionSourceState.next = null; + mNext = null; } - return new AttributionSource(mAttributionSourceState); + AttributionSource o = new AttributionSource( + mUid, + mPackageName, + mAttributionTag, + mToken, + mRenouncedPermissions, + mNext); + return o; } private void checkNotUsed() { @@ -489,4 +629,9 @@ public final class AttributionSource implements Parcelable { } } } + + + //@formatter:on + // End of generated code + } diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java index 66e088359459..5089f30585b4 100644 --- a/core/java/android/content/PermissionChecker.java +++ b/core/java/android/content/PermissionChecker.java @@ -16,19 +16,21 @@ package android.content; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; import android.os.Binder; -import android.os.IBinder; import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.permission.IPermissionChecker; +import android.util.Slog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * This class provides permission check APIs that verify both the @@ -70,44 +72,34 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public final class PermissionChecker { - /** - * The permission is granted. - * - * @hide - */ - public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED; + private static final String LOG_TAG = PermissionChecker.class.getName(); - /** - * The permission is denied. Applicable only to runtime and app op permissions. - * - * <p>Returned when: - * <ul> - * <li>the runtime permission is granted, but the corresponding app op is denied - * for runtime permissions.</li> - * <li>the app ops is ignored for app op permissions.</li> - * </ul> - * - * @hide - */ - public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED; + private static final String PLATFORM_PACKAGE_NAME = "android"; - /** - * The permission is denied. - * - * <p>Returned when: + /** The permission is granted. */ + public static final int PERMISSION_GRANTED = AppOpsManager.MODE_ALLOWED; + + /** Only for runtime permissions, its returned when the runtime permission + * is granted, but the corresponding app op is denied. */ + public static final int PERMISSION_SOFT_DENIED = AppOpsManager.MODE_IGNORED; + + /** Returned when: * <ul> - * <li>the permission is denied for non app op permissions.</li> - * <li>the app op is denied or app op is {@link AppOpsManager#MODE_DEFAULT} - * and permission is denied.</li> + * <li>For non app op permissions, returned when the permission is denied.</li> + * <li>For app op permissions, returned when the app op is denied or app op is + * {@link AppOpsManager#MODE_DEFAULT} and permission is denied.</li> * </ul> * - * @hide */ - public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED; + public static final int PERMISSION_HARD_DENIED = AppOpsManager.MODE_ERRORED; /** Constant when the PID for which we check permissions is unknown. */ public static final int PID_UNKNOWN = -1; + // Cache for platform defined runtime permissions to avoid multi lookup (name -> info) + private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions + = new ConcurrentHashMap<>(); + /** @hide */ @IntDef({PERMISSION_GRANTED, PERMISSION_SOFT_DENIED, @@ -115,8 +107,6 @@ public final class PermissionChecker { @Retention(RetentionPolicy.SOURCE) public @interface PermissionResult {} - private static volatile IPermissionChecker sService; - private PermissionChecker() { /* do nothing */ } @@ -242,7 +232,7 @@ public final class PermissionChecker { public static int checkPermissionForDataDeliveryFromDataSource(@NonNull Context context, @NonNull String permission, int pid, @NonNull AttributionSource attributionSource, @Nullable String message) { - return checkPermissionForDataDeliveryCommon(context, permission, attributionSource, + return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource, message, false /*startDataDelivery*/, /*fromDatasource*/ true); } @@ -317,23 +307,21 @@ public final class PermissionChecker { public static int checkPermissionForDataDelivery(@NonNull Context context, @NonNull String permission, int pid, @NonNull AttributionSource attributionSource, @Nullable String message, boolean startDataDelivery) { - return checkPermissionForDataDeliveryCommon(context, permission, attributionSource, + return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource, message, startDataDelivery, /*fromDatasource*/ false); } private static int checkPermissionForDataDeliveryCommon(@NonNull Context context, - @NonNull String permission, @NonNull AttributionSource attributionSource, + @NonNull String permission, int pid, @NonNull AttributionSource attributionSource, @Nullable String message, boolean startDataDelivery, boolean fromDatasource) { // If the check failed in the middle of the chain, finish any started op. - try { - final int result = getPermissionCheckerService().checkPermission(permission, - attributionSource.asState(), message, true /*forDataDelivery*/, - startDataDelivery, fromDatasource); - return result; - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + final int result = checkPermissionCommon(context, permission, attributionSource, + message, true /*forDataDelivery*/, startDataDelivery, fromDatasource); + if (startDataDelivery && result != PERMISSION_GRANTED) { + finishDataDelivery(context, AppOpsManager.permissionToOp(permission), + attributionSource); } - return PERMISSION_HARD_DENIED; + return result; } /** @@ -368,14 +356,9 @@ public final class PermissionChecker { public static int checkPermissionAndStartDataDelivery(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message) { - try { - return getPermissionCheckerService().checkPermission(permission, - attributionSource.asState(), message, true /*forDataDelivery*/, - /*startDataDelivery*/ true, /*fromDatasource*/ false); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return PERMISSION_HARD_DENIED; + return checkPermissionCommon(context, permission, attributionSource, + message, true /*forDataDelivery*/, /*startDataDelivery*/ true, + /*fromDatasource*/ false); } /** @@ -407,14 +390,13 @@ public final class PermissionChecker { public static int startOpForDataDelivery(@NonNull Context context, @NonNull String opName, @NonNull AttributionSource attributionSource, @Nullable String message) { - try { - return getPermissionCheckerService().checkOp( - AppOpsManager.strOpToOp(opName), attributionSource.asState(), message, - true /*forDataDelivery*/, true /*startDataDelivery*/); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + final int result = checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource, + message, true /*forDataDelivery*/, true /*startDataDelivery*/); + // It is important to finish any started op if some step in the attribution chain failed. + if (result != PERMISSION_GRANTED) { + finishDataDelivery(context, opName, attributionSource); } - return PERMISSION_HARD_DENIED; + return result; } /** @@ -430,10 +412,15 @@ public final class PermissionChecker { */ public static void finishDataDelivery(@NonNull Context context, @NonNull String op, @NonNull AttributionSource attributionSource) { - try { - getPermissionCheckerService().finishDataDelivery(op, attributionSource.asState()); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + if (op == null || attributionSource.getPackageName() == null) { + return; + } + + final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + appOpsManager.finishProxyOp(op, attributionSource); + + if (attributionSource.getNext() != null) { + finishDataDelivery(context, op, attributionSource.getNext()); } } @@ -469,14 +456,8 @@ public final class PermissionChecker { public static int checkOpForPreflight(@NonNull Context context, @NonNull String opName, @NonNull AttributionSource attributionSource, @Nullable String message) { - try { - return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName), - attributionSource.asState(), message, false /*forDataDelivery*/, - false /*startDataDelivery*/); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return PERMISSION_HARD_DENIED; + return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource, + message, false /*forDataDelivery*/, false /*startDataDelivery*/); } /** @@ -508,14 +489,8 @@ public final class PermissionChecker { public static int checkOpForDataDelivery(@NonNull Context context, @NonNull String opName, @NonNull AttributionSource attributionSource, @Nullable String message) { - try { - return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName), - attributionSource.asState(), message, true /*forDataDelivery*/, - false /*startDataDelivery*/); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return PERMISSION_HARD_DENIED; + return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource, + message, true /*forDataDelivery*/, false /*startDataDelivery*/); } /** @@ -586,14 +561,9 @@ public final class PermissionChecker { @PermissionResult public static int checkPermissionForPreflight(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource) { - try { - return getPermissionCheckerService().checkPermission(permission, - attributionSource.asState(), null /*message*/, false /*forDataDelivery*/, - /*startDataDelivery*/ false, /*fromDatasource*/ false); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return PERMISSION_HARD_DENIED; + return checkPermissionCommon(context, permission, attributionSource, + null /*message*/, false /*forDataDelivery*/, /*startDataDelivery*/ false, + /*fromDatasource*/ false); } /** @@ -828,12 +798,356 @@ public final class PermissionChecker { Binder.getCallingUid(), packageName); } - private static @NonNull IPermissionChecker getPermissionCheckerService() { - // Race is fine, we may end up looking up the same instance twice, no big deal. - if (sService == null) { - final IBinder service = ServiceManager.getService("permission_checker"); - sService = IPermissionChecker.Stub.asInterface(service); + @PermissionResult + private static int checkPermissionCommon(@NonNull Context context, @NonNull String permission, + @NonNull AttributionSource attributionSource, + @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, + boolean fromDatasource) { + PermissionInfo permissionInfo = sPlatformPermissions.get(permission); + + if (permissionInfo == null) { + try { + permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0); + if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) { + // Double addition due to concurrency is fine - the backing store is concurrent. + sPlatformPermissions.put(permission, permissionInfo); + } + } catch (PackageManager.NameNotFoundException ignored) { + return PERMISSION_HARD_DENIED; + } + } + + if (permissionInfo.isAppOp()) { + return checkAppOpPermission(context, permission, attributionSource, message, + forDataDelivery, fromDatasource); + } + if (permissionInfo.isRuntime()) { + return checkRuntimePermission(context, permission, attributionSource, message, + forDataDelivery, startDataDelivery, fromDatasource); + } + + if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(), + attributionSource.getRenouncedPermissions())) { + return PERMISSION_HARD_DENIED; + } + + if (attributionSource.getNext() != null) { + return checkPermissionCommon(context, permission, + attributionSource.getNext(), message, forDataDelivery, + startDataDelivery, /*fromDatasource*/ false); + } + + return PERMISSION_GRANTED; + } + + @PermissionResult + private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission, + @NonNull AttributionSource attributionSource, @Nullable String message, + boolean forDataDelivery, boolean fromDatasource) { + final int op = AppOpsManager.permissionToOpCode(permission); + if (op < 0) { + Slog.wtf(LOG_TAG, "Appop permission " + permission + " with no app op defined!"); + return PERMISSION_HARD_DENIED; + } + + AttributionSource current = attributionSource; + AttributionSource next = null; + + while (true) { + final boolean skipCurrentChecks = (fromDatasource || next != null); + + next = current.getNext(); + + // If the call is from a datasource we need to vet only the chain before it. This + // way we can avoid the datasource creating an attribution context for every call. + if (!(fromDatasource && current == attributionSource) + && next != null && !current.isTrusted(context)) { + return PERMISSION_HARD_DENIED; + } + + // The access is for oneself if this is the single receiver of data + // after the data source or if this is the single attribution source + // in the chain if not from a datasource. + final boolean singleReceiverFromDatasource = (fromDatasource + && current == attributionSource && next != null && next.getNext() == null); + final boolean selfAccess = singleReceiverFromDatasource || next == null; + + final int opMode = performOpTransaction(context, op, current, message, + forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks, + selfAccess, singleReceiverFromDatasource); + + switch (opMode) { + case AppOpsManager.MODE_IGNORED: + case AppOpsManager.MODE_ERRORED: { + return PERMISSION_HARD_DENIED; + } + case AppOpsManager.MODE_DEFAULT: { + if (!skipCurrentChecks && !checkPermission(context, permission, + attributionSource.getUid(), attributionSource + .getRenouncedPermissions())) { + return PERMISSION_HARD_DENIED; + } + if (next != null && !checkPermission(context, permission, + next.getUid(), next.getRenouncedPermissions())) { + return PERMISSION_HARD_DENIED; + } + } + } + + if (next == null || next.getNext() == null) { + return PERMISSION_GRANTED; + } + + current = next; + } + } + + private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission, + @NonNull AttributionSource attributionSource, @Nullable String message, + boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) { + // Now let's check the identity chain... + final int op = AppOpsManager.permissionToOpCode(permission); + + AttributionSource current = attributionSource; + AttributionSource next = null; + + while (true) { + final boolean skipCurrentChecks = (fromDatasource || next != null); + next = current.getNext(); + + // If the call is from a datasource we need to vet only the chain before it. This + // way we can avoid the datasource creating an attribution context for every call. + if (!(fromDatasource && current == attributionSource) + && next != null && !current.isTrusted(context)) { + return PERMISSION_HARD_DENIED; + } + + // If we already checked the permission for this one, skip the work + if (!skipCurrentChecks && !checkPermission(context, permission, + current.getUid(), current.getRenouncedPermissions())) { + return PERMISSION_HARD_DENIED; + } + + if (next != null && !checkPermission(context, permission, + next.getUid(), next.getRenouncedPermissions())) { + return PERMISSION_HARD_DENIED; + } + + if (op < 0) { + // Bg location is one-off runtime modifier permission and has no app op + if (sPlatformPermissions.contains(permission) + && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) { + Slog.wtf(LOG_TAG, "Platform runtime permission " + permission + + " with no app op defined!"); + } + if (next == null) { + return PERMISSION_GRANTED; + } + current = next; + continue; + } + + // The access is for oneself if this is the single receiver of data + // after the data source or if this is the single attribution source + // in the chain if not from a datasource. + final boolean singleReceiverFromDatasource = (fromDatasource + && current == attributionSource && next != null && next.getNext() == null); + final boolean selfAccess = singleReceiverFromDatasource || next == null; + + final int opMode = performOpTransaction(context, op, current, message, + forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess, + singleReceiverFromDatasource); + + switch (opMode) { + case AppOpsManager.MODE_ERRORED: { + return PERMISSION_HARD_DENIED; + } + case AppOpsManager.MODE_IGNORED: { + return PERMISSION_SOFT_DENIED; + } + } + + if (next == null || next.getNext() == null) { + return PERMISSION_GRANTED; + } + + current = next; + } + } + + private static boolean checkPermission(@NonNull Context context, @NonNull String permission, + int uid, @NonNull Set<String> renouncedPermissions) { + final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1, + uid) == PackageManager.PERMISSION_GRANTED; + if (permissionGranted && renouncedPermissions.contains(permission) + && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS, + /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) { + return false; + } + return permissionGranted; + } + + private static int checkOp(@NonNull Context context, @NonNull int op, + @NonNull AttributionSource attributionSource, @Nullable String message, + boolean forDataDelivery, boolean startDataDelivery) { + if (op < 0 || attributionSource.getPackageName() == null) { + return PERMISSION_HARD_DENIED; + } + + AttributionSource current = attributionSource; + AttributionSource next = null; + + while (true) { + final boolean skipCurrentChecks = (next != null); + next = current.getNext(); + + // If the call is from a datasource we need to vet only the chain before it. This + // way we can avoid the datasource creating an attribution context for every call. + if (next != null && !current.isTrusted(context)) { + return PERMISSION_HARD_DENIED; + } + + // The access is for oneself if this is the single attribution source in the chain. + final boolean selfAccess = (next == null); + + final int opMode = performOpTransaction(context, op, current, message, + forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess, + /*fromDatasource*/ false); + + switch (opMode) { + case AppOpsManager.MODE_ERRORED: { + return PERMISSION_HARD_DENIED; + } + case AppOpsManager.MODE_IGNORED: { + return PERMISSION_SOFT_DENIED; + } + } + + if (next == null || next.getNext() == null) { + return PERMISSION_GRANTED; + } + + current = next; + } + } + + private static int performOpTransaction(@NonNull Context context, int op, + @NonNull AttributionSource attributionSource, @Nullable String message, + boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation, + boolean selfAccess, boolean singleReceiverFromDatasource) { + // We cannot perform app ops transactions without a package name. In all relevant + // places we pass the package name but just in case there is a bug somewhere we + // do a best effort to resolve the package from the UID (pick first without a loss + // of generality - they are in the same security sandbox). + final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + final AttributionSource accessorSource = (!singleReceiverFromDatasource) + ? attributionSource : attributionSource.getNext(); + if (!forDataDelivery) { + final String resolvedAccessorPackageName = resolvePackageName(context, accessorSource); + if (resolvedAccessorPackageName == null) { + return AppOpsManager.MODE_ERRORED; + } + final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op, + accessorSource.getUid(), resolvedAccessorPackageName); + final AttributionSource next = accessorSource.getNext(); + if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) { + final String resolvedNextPackageName = resolvePackageName(context, next); + if (resolvedNextPackageName == null) { + return AppOpsManager.MODE_ERRORED; + } + return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(), + resolvedNextPackageName); + } + return opMode; + } else if (startDataDelivery) { + final AttributionSource resolvedAttributionSource = resolveAttributionSource( + context, accessorSource); + if (resolvedAttributionSource.getPackageName() == null) { + return AppOpsManager.MODE_ERRORED; + } + if (selfAccess) { + // If the datasource is not in a trusted platform component then in would not + // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that + // an app is exposing runtime permission protected data but cannot blame others + // in a trusted way which would not properly show in permission usage UIs. + // As a fallback we note a proxy op that blames the app and the datasource. + try { + return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(), + resolvedAttributionSource.getPackageName(), + /*startIfModeDefault*/ false, + resolvedAttributionSource.getAttributionTag(), + message); + } catch (SecurityException e) { + Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with" + + " platform defined runtime permission " + + AppOpsManager.opToPermission(op) + " while not having " + + Manifest.permission.UPDATE_APP_OPS_STATS); + return appOpsManager.startProxyOpNoThrow(op, attributionSource, message, + skipProxyOperation); + } + } else { + return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message, + skipProxyOperation); + } + } else { + final AttributionSource resolvedAttributionSource = resolveAttributionSource( + context, accessorSource); + if (resolvedAttributionSource.getPackageName() == null) { + return AppOpsManager.MODE_ERRORED; + } + if (selfAccess) { + // If the datasource is not in a trusted platform component then in would not + // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that + // an app is exposing runtime permission protected data but cannot blame others + // in a trusted way which would not properly show in permission usage UIs. + // As a fallback we note a proxy op that blames the app and the datasource. + try { + return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(), + resolvedAttributionSource.getPackageName(), + resolvedAttributionSource.getAttributionTag(), + message); + } catch (SecurityException e) { + Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with" + + " platform defined runtime permission " + + AppOpsManager.opToPermission(op) + " while not having " + + Manifest.permission.UPDATE_APP_OPS_STATS); + return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message, + skipProxyOperation); + } + } else { + return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message, + skipProxyOperation); + } + } + } + + private static @Nullable String resolvePackageName(@NonNull Context context, + @NonNull AttributionSource attributionSource) { + if (attributionSource.getPackageName() != null) { + return attributionSource.getPackageName(); + } + final String[] packageNames = context.getPackageManager().getPackagesForUid( + attributionSource.getUid()); + if (packageNames != null) { + // This is best effort if the caller doesn't pass a package. The security + // sandbox is UID, therefore we pick an arbitrary package. + return packageNames[0]; + } + // Last resort to handle special UIDs like root, etc. + return AppOpsManager.resolvePackageName(attributionSource.getUid(), + attributionSource.getPackageName()); + } + + private static @NonNull AttributionSource resolveAttributionSource( + @NonNull Context context, @NonNull AttributionSource attributionSource) { + if (attributionSource.getPackageName() != null) { + return attributionSource; } - return sService; + return new AttributionSource(attributionSource.getUid(), + resolvePackageName(context, attributionSource), + attributionSource.getAttributionTag(), + attributionSource.getToken(), + attributionSource.getRenouncedPermissions(), + attributionSource.getNext()); } } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 64d5d9c9d858..17c90d64ce6a 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -20,7 +20,6 @@ import static android.os.Build.VERSION_CODES.S; import android.Manifest; import android.annotation.CheckResult; -import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -32,7 +31,6 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.AppGlobals; -import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.PropertyInvalidatedCache; import android.compat.annotation.ChangeId; @@ -65,8 +63,6 @@ import com.android.internal.R; import com.android.internal.annotations.Immutable; import com.android.internal.util.CollectionUtils; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 0147790559e3..2d1178a3f116 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -70,9 +70,7 @@ import android.app.admin.DevicePolicyManagerInternal; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.AttributionSource; -import android.content.AttributionSourceState; import android.content.Context; -import android.content.PermissionChecker; import android.content.pm.ApplicationInfo; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; @@ -106,7 +104,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; import android.permission.IOnPermissionsChangeListener; -import android.permission.IPermissionChecker; import android.permission.IPermissionManager; import android.permission.PermissionControllerManager; import android.permission.PermissionManager; @@ -167,7 +164,6 @@ import java.util.Objects; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -455,7 +451,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (permissionService == null) { permissionService = new PermissionManagerService(context, availableFeatures); ServiceManager.addService("permissionmgr", permissionService); - ServiceManager.addService("permission_checker", new PermissionCheckerService(context)); } return LocalServices.getService(PermissionManagerServiceInternal.class); } @@ -5419,419 +5414,4 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } } - - /** - * TODO: We need to consolidate these APIs either on PermissionManager or an extension - * object or a separate PermissionChecker service in context. The impartant part is to - * keep a single impl that is exposed to Java and native. We are not sure about the - * API shape so let is soak a bit. - */ - private static final class PermissionCheckerService extends IPermissionChecker.Stub { - // Cache for platform defined runtime permissions to avoid multi lookup (name -> info) - private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions - = new ConcurrentHashMap<>(); - - private final @NonNull Context mContext; - private final @NonNull AppOpsManager mAppOpsManager; - - PermissionCheckerService(@NonNull Context context) { - mContext = context; - mAppOpsManager = mContext.getSystemService(AppOpsManager.class); - } - - @Override - @PermissionChecker.PermissionResult - public int checkPermission(@NonNull String permission, - @NonNull AttributionSourceState attributionSourceState, @Nullable String message, - boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) { - Objects.requireNonNull(permission); - Objects.requireNonNull(attributionSourceState); - final AttributionSource attributionSource = new AttributionSource( - attributionSourceState); - final int result = checkPermission(mContext, permission, attributionSource, message, - forDataDelivery, startDataDelivery, fromDatasource); - // Finish any started op if some step in the attribution chain failed. - if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) { - finishDataDelivery(AppOpsManager.permissionToOp(permission), - attributionSource.asState()); - } - return result; - } - - @Override - public void finishDataDelivery(@NonNull String op, - @NonNull AttributionSourceState attributionSourceState) { - if (op == null || attributionSourceState.packageName == null) { - return; - } - mAppOpsManager.finishProxyOp(op, new AttributionSource(attributionSourceState)); - if (attributionSourceState.next != null) { - finishDataDelivery(op, attributionSourceState.next[0]); - } - } - - @Override - @PermissionChecker.PermissionResult - public int checkOp(int op, AttributionSourceState attributionSource, - String message, boolean forDataDelivery, boolean startDataDelivery) { - int result = checkOp(mContext, op, new AttributionSource(attributionSource), message, - forDataDelivery, startDataDelivery); - if (result != PermissionChecker.PERMISSION_GRANTED && startDataDelivery) { - // Finish any started op if some step in the attribution chain failed. - finishDataDelivery(AppOpsManager.opToName(op), attributionSource); - } - return result; - } - - @PermissionChecker.PermissionResult - private static int checkPermission(@NonNull Context context, @NonNull String permission, - @NonNull AttributionSource attributionSource, @Nullable String message, - boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) { - PermissionInfo permissionInfo = sPlatformPermissions.get(permission); - - if (permissionInfo == null) { - try { - permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0); - if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) { - // Double addition due to concurrency is fine - the backing - // store is concurrent. - sPlatformPermissions.put(permission, permissionInfo); - } - } catch (PackageManager.NameNotFoundException ignored) { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - } - - if (permissionInfo.isAppOp()) { - return checkAppOpPermission(context, permission, attributionSource, message, - forDataDelivery, fromDatasource); - } - if (permissionInfo.isRuntime()) { - return checkRuntimePermission(context, permission, attributionSource, message, - forDataDelivery, startDataDelivery, fromDatasource); - } - - if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(), - attributionSource.getRenouncedPermissions())) { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - - if (attributionSource.getNext() != null) { - return checkPermission(context, permission, - attributionSource.getNext(), message, forDataDelivery, - startDataDelivery, /*fromDatasource*/ false); - } - - return PermissionChecker.PERMISSION_GRANTED; - } - - @PermissionChecker.PermissionResult - private static int checkAppOpPermission(@NonNull Context context, - @NonNull String permission, @NonNull AttributionSource attributionSource, - @Nullable String message, boolean forDataDelivery, boolean fromDatasource) { - final int op = AppOpsManager.permissionToOpCode(permission); - if (op < 0) { - Slog.wtf(LOG_TAG, "Appop permission " + permission + " with no app op defined!"); - return PermissionChecker.PERMISSION_HARD_DENIED; - } - - AttributionSource current = attributionSource; - AttributionSource next = null; - - while (true) { - final boolean skipCurrentChecks = (fromDatasource || next != null); - - next = current.getNext(); - - // If the call is from a datasource we need to vet only the chain before it. This - // way we can avoid the datasource creating an attribution context for every call. - if (!(fromDatasource && current == attributionSource) - && next != null && !current.isTrusted(context)) { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - - // The access is for oneself if this is the single receiver of data - // after the data source or if this is the single attribution source - // in the chain if not from a datasource. - final boolean singleReceiverFromDatasource = (fromDatasource - && current == attributionSource && next != null && next.getNext() == null); - final boolean selfAccess = singleReceiverFromDatasource || next == null; - - final int opMode = performOpTransaction(context, op, current, message, - forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks, - selfAccess, singleReceiverFromDatasource); - - switch (opMode) { - case AppOpsManager.MODE_IGNORED: - case AppOpsManager.MODE_ERRORED: { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - case AppOpsManager.MODE_DEFAULT: { - if (!skipCurrentChecks && !checkPermission(context, permission, - attributionSource.getUid(), attributionSource - .getRenouncedPermissions())) { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - if (next != null && !checkPermission(context, permission, - next.getUid(), next.getRenouncedPermissions())) { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - } - } - - if (next == null || next.getNext() == null) { - return PermissionChecker.PERMISSION_GRANTED; - } - - current = next; - } - } - - private static int checkRuntimePermission(@NonNull Context context, - @NonNull String permission, @NonNull AttributionSource attributionSource, - @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, - boolean fromDatasource) { - // Now let's check the identity chain... - final int op = AppOpsManager.permissionToOpCode(permission); - - AttributionSource current = attributionSource; - AttributionSource next = null; - - while (true) { - final boolean skipCurrentChecks = (fromDatasource || next != null); - next = current.getNext(); - - // If the call is from a datasource we need to vet only the chain before it. This - // way we can avoid the datasource creating an attribution context for every call. - if (!(fromDatasource && current == attributionSource) - && next != null && !current.isTrusted(context)) { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - - // If we already checked the permission for this one, skip the work - if (!skipCurrentChecks && !checkPermission(context, permission, - current.getUid(), current.getRenouncedPermissions())) { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - - if (next != null && !checkPermission(context, permission, - next.getUid(), next.getRenouncedPermissions())) { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - - if (op < 0) { - // Bg location is one-off runtime modifier permission and has no app op - if (sPlatformPermissions.contains(permission) - && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) { - Slog.wtf(LOG_TAG, "Platform runtime permission " + permission - + " with no app op defined!"); - } - if (next == null) { - return PermissionChecker.PERMISSION_GRANTED; - } - current = next; - continue; - } - - // The access is for oneself if this is the single receiver of data - // after the data source or if this is the single attribution source - // in the chain if not from a datasource. - final boolean singleReceiverFromDatasource = (fromDatasource - && current == attributionSource && next != null && next.getNext() == null); - final boolean selfAccess = singleReceiverFromDatasource || next == null; - - final int opMode = performOpTransaction(context, op, current, message, - forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess, - singleReceiverFromDatasource); - - switch (opMode) { - case AppOpsManager.MODE_ERRORED: { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - case AppOpsManager.MODE_IGNORED: { - return PermissionChecker.PERMISSION_SOFT_DENIED; - } - } - - if (next == null || next.getNext() == null) { - return PermissionChecker.PERMISSION_GRANTED; - } - - current = next; - } - } - - private static boolean checkPermission(@NonNull Context context, @NonNull String permission, - int uid, @NonNull Set<String> renouncedPermissions) { - final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1, - uid) == PackageManager.PERMISSION_GRANTED; - if (permissionGranted && renouncedPermissions.contains(permission) - && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS, - /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) { - return false; - } - return permissionGranted; - } - - private static int checkOp(@NonNull Context context, @NonNull int op, - @NonNull AttributionSource attributionSource, @Nullable String message, - boolean forDataDelivery, boolean startDataDelivery) { - if (op < 0 || attributionSource.getPackageName() == null) { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - - AttributionSource current = attributionSource; - AttributionSource next = null; - - while (true) { - final boolean skipCurrentChecks = (next != null); - next = current.getNext(); - - // If the call is from a datasource we need to vet only the chain before it. This - // way we can avoid the datasource creating an attribution context for every call. - if (next != null && !current.isTrusted(context)) { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - - // The access is for oneself if this is the single attribution source in the chain. - final boolean selfAccess = (next == null); - - final int opMode = performOpTransaction(context, op, current, message, - forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess, - /*fromDatasource*/ false); - - switch (opMode) { - case AppOpsManager.MODE_ERRORED: { - return PermissionChecker.PERMISSION_HARD_DENIED; - } - case AppOpsManager.MODE_IGNORED: { - return PermissionChecker.PERMISSION_SOFT_DENIED; - } - } - - if (next == null || next.getNext() == null) { - return PermissionChecker.PERMISSION_GRANTED; - } - - current = next; - } - } - - private static int performOpTransaction(@NonNull Context context, int op, - @NonNull AttributionSource attributionSource, @Nullable String message, - boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation, - boolean selfAccess, boolean singleReceiverFromDatasource) { - // We cannot perform app ops transactions without a package name. In all relevant - // places we pass the package name but just in case there is a bug somewhere we - // do a best effort to resolve the package from the UID (pick first without a loss - // of generality - they are in the same security sandbox). - final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - final AttributionSource accessorSource = (!singleReceiverFromDatasource) - ? attributionSource : attributionSource.getNext(); - if (!forDataDelivery) { - final String resolvedAccessorPackageName = resolvePackageName(context, - accessorSource); - if (resolvedAccessorPackageName == null) { - return AppOpsManager.MODE_ERRORED; - } - final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op, - accessorSource.getUid(), resolvedAccessorPackageName); - final AttributionSource next = accessorSource.getNext(); - if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) { - final String resolvedNextPackageName = resolvePackageName(context, next); - if (resolvedNextPackageName == null) { - return AppOpsManager.MODE_ERRORED; - } - return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(), - resolvedNextPackageName); - } - return opMode; - } else if (startDataDelivery) { - final AttributionSource resolvedAttributionSource = resolveAttributionSource( - context, accessorSource); - if (resolvedAttributionSource.getPackageName() == null) { - return AppOpsManager.MODE_ERRORED; - } - if (selfAccess) { - // If the datasource is not in a trusted platform component then in would not - // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that - // an app is exposing runtime permission protected data but cannot blame others - // in a trusted way which would not properly show in permission usage UIs. - // As a fallback we note a proxy op that blames the app and the datasource. - try { - return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(), - resolvedAttributionSource.getPackageName(), - /*startIfModeDefault*/ false, - resolvedAttributionSource.getAttributionTag(), - message); - } catch (SecurityException e) { - Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with" - + " platform defined runtime permission " - + AppOpsManager.opToPermission(op) + " while not having " - + Manifest.permission.UPDATE_APP_OPS_STATS); - return appOpsManager.startProxyOpNoThrow(op, attributionSource, message, - skipProxyOperation); - } - } else { - return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message, - skipProxyOperation); - } - } else { - final AttributionSource resolvedAttributionSource = resolveAttributionSource( - context, accessorSource); - if (resolvedAttributionSource.getPackageName() == null) { - return AppOpsManager.MODE_ERRORED; - } - if (selfAccess) { - // If the datasource is not in a trusted platform component then in would not - // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that - // an app is exposing runtime permission protected data but cannot blame others - // in a trusted way which would not properly show in permission usage UIs. - // As a fallback we note a proxy op that blames the app and the datasource. - try { - return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(), - resolvedAttributionSource.getPackageName(), - resolvedAttributionSource.getAttributionTag(), - message); - } catch (SecurityException e) { - Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with" - + " platform defined runtime permission " - + AppOpsManager.opToPermission(op) + " while not having " - + Manifest.permission.UPDATE_APP_OPS_STATS); - return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message, - skipProxyOperation); - } - } else { - return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message, - skipProxyOperation); - } - } - } - - private static @Nullable String resolvePackageName(@NonNull Context context, - @NonNull AttributionSource attributionSource) { - if (attributionSource.getPackageName() != null) { - return attributionSource.getPackageName(); - } - final String[] packageNames = context.getPackageManager().getPackagesForUid( - attributionSource.getUid()); - if (packageNames != null) { - // This is best effort if the caller doesn't pass a package. The security - // sandbox is UID, therefore we pick an arbitrary package. - return packageNames[0]; - } - // Last resort to handle special UIDs like root, etc. - return AppOpsManager.resolvePackageName(attributionSource.getUid(), - attributionSource.getPackageName()); - } - - private static @NonNull AttributionSource resolveAttributionSource( - @NonNull Context context, @NonNull AttributionSource attributionSource) { - if (attributionSource.getPackageName() != null) { - return attributionSource; - } - return attributionSource.withPackageName(resolvePackageName(context, - attributionSource)); - } - } } |