Merge "Revert "Prepare AttributionSource to expose to native"" into sc-dev

This commit is contained in:
Svetoslav Ganov
2021-04-27 02:12:30 +00:00
committed by Android (Google) Code Review
5 changed files with 674 additions and 640 deletions

View File

@@ -325,7 +325,6 @@ java_defaults {
"tv_tuner_resource_manager_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
"modules-utils-os",
"framework-permission-aidl-java",
],
}

View File

@@ -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>> {
private @Nullable AttributionSource mNextCached;
private @Nullable Set<String> mRenouncedPermissionsCached;
@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;
/**
* 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
}

View File

@@ -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:
private static final String PLATFORM_PACKAGE_NAME = "android";
/** 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 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>
* <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_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
/**
* The permission is denied.
*
* <p>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>
* </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;
}
}
return sService;
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 new AttributionSource(attributionSource.getUid(),
resolvePackageName(context, attributionSource),
attributionSource.getAttributionTag(),
attributionSource.getToken(),
attributionSource.getRenouncedPermissions(),
attributionSource.getNext());
}
}

View File

@@ -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;

View File

@@ -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));
}
}
}