diff --git a/Android.bp b/Android.bp index acf86a0cfa2d..0ffafc6dcef2 100644 --- a/Android.bp +++ b/Android.bp @@ -326,6 +326,7 @@ java_defaults { "tv_tuner_resource_manager_aidl_interface-java", "soundtrigger_middleware-aidl-java", "modules-utils-os", + "framework-permission-aidl-java", ], } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 196799345efd..d366e35dd11a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -219,6 +219,7 @@ package android.app { public class AppOpsManager { method @RequiresPermission("android.permission.MANAGE_APPOPS") public void addHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOps); method @RequiresPermission("android.permission.MANAGE_APPOPS") public void clearHistory(); + method public static void collectNotedOpSync(@NonNull android.app.SyncNotedAppOp); method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method public static int getNumOps(); method public boolean isOperationActive(int, int, String); @@ -360,6 +361,10 @@ package android.app { method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean); } + public final class SyncNotedAppOp implements android.os.Parcelable { + ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @NonNull String); + } + public class TaskInfo { method public boolean containsLaunchCookie(@NonNull android.os.IBinder); method @NonNull public android.content.res.Configuration getConfiguration(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 75a38c2380b9..52b38a20e4a1 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2783,14 +2783,24 @@ public class AppOpsManager { private static final ThreadLocal sBinderThreadCallingUid = new ThreadLocal<>(); /** - * If a thread is currently executing a two-way binder transaction, this stores the op-codes of - * the app-ops that were noted during this transaction. + * Optimization: we need to propagate to IPCs whether the current thread is collecting + * app ops but using only the thread local above is too slow as it requires a map lookup + * on every IPC. We add this static var that is lockless and stores an OR-ed mask of the + * thread id's currently collecting ops, thus reducing the map lookup to a simple bit + * operation except the extremely unlikely case when threads with overlapping id bits + * execute op collecting ops. + */ + private static volatile long sThreadsListeningForOpNotedInBinderTransaction = 0L; + + /** + * If a thread is currently executing a two-way binder transaction, this stores the + * ops that were noted blaming any app (the caller, the caller of the caller, etc). * * @see #getNotedOpCollectionMode * @see #collectNotedOpSync */ - private static final ThreadLocal> sAppOpsNotedInThisBinderTransaction = - new ThreadLocal<>(); + private static final ThreadLocal>> + sAppOpsNotedInThisBinderTransaction = new ThreadLocal<>(); /** Whether noting for an appop should be collected */ private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP]; @@ -8105,7 +8115,7 @@ public class AppOpsManager { SyncNotedAppOp syncOp = mService.noteOperation(op, uid, packageName, attributionTag, collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); - if (syncOp.getOpMode()== MODE_ALLOWED) { + if (syncOp.getOpMode() == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { collectNotedOpForSelf(syncOp); } else if (collectionMode == COLLECT_SYNC) { @@ -8872,69 +8882,10 @@ public class AppOpsManager { * @hide */ public static void startNotedAppOpsCollection(int callingUid) { + sThreadsListeningForOpNotedInBinderTransaction |= Thread.currentThread().getId(); sBinderThreadCallingUid.set(callingUid); } - /** - * State of a temporarily paused noted app-ops collection. - * - * @see #pauseNotedAppOpsCollection() - * - * @hide - */ - public static class PausedNotedAppOpsCollection { - final int mUid; - final @Nullable ArrayMap mCollectedNotedAppOps; - - PausedNotedAppOpsCollection(int uid, @Nullable ArrayMap collectedNotedAppOps) { - mUid = uid; - mCollectedNotedAppOps = collectedNotedAppOps; - } - } - - /** - * Temporarily suspend collection of noted app-ops when binder-thread calls into the other - * process. During such a call there might be call-backs coming back on the same thread which - * should not be accounted to the current collection. - * - * @return a state needed to resume the collection - * - * @hide - */ - public static @Nullable PausedNotedAppOpsCollection pauseNotedAppOpsCollection() { - Integer previousUid = sBinderThreadCallingUid.get(); - if (previousUid != null) { - ArrayMap previousCollectedNotedAppOps = - sAppOpsNotedInThisBinderTransaction.get(); - - sBinderThreadCallingUid.remove(); - sAppOpsNotedInThisBinderTransaction.remove(); - - return new PausedNotedAppOpsCollection(previousUid, previousCollectedNotedAppOps); - } - - return null; - } - - /** - * Resume a collection paused via {@link #pauseNotedAppOpsCollection}. - * - * @param prevCollection The state of the previous collection - * - * @hide - */ - public static void resumeNotedAppOpsCollection( - @Nullable PausedNotedAppOpsCollection prevCollection) { - if (prevCollection != null) { - sBinderThreadCallingUid.set(prevCollection.mUid); - - if (prevCollection.mCollectedNotedAppOps != null) { - sAppOpsNotedInThisBinderTransaction.set(prevCollection.mCollectedNotedAppOps); - } - } - } - /** * Finish collection of noted appops on this thread. * @@ -8946,6 +8897,7 @@ public class AppOpsManager { */ public static void finishNotedAppOpsCollection() { sBinderThreadCallingUid.remove(); + sThreadsListeningForOpNotedInBinderTransaction &= ~Thread.currentThread().getId(); sAppOpsNotedInThisBinderTransaction.remove(); } @@ -8970,28 +8922,52 @@ public class AppOpsManager { *

Delivered to caller via {@link #prefixParcelWithAppOpsIfNeeded} * * @param syncOp the op and attribution tag to note for + * + * @hide */ - private void collectNotedOpSync(@NonNull SyncNotedAppOp syncOp) { + @TestApi + public static void collectNotedOpSync(@NonNull SyncNotedAppOp syncOp) { + collectNotedOpSync(sOpStrToOp.get(syncOp.getOp()), syncOp.getAttributionTag(), + syncOp.getPackageName()); + } + + /** + * Collect a noted op when inside of a two-way binder call. + * + *

Delivered to caller via {@link #prefixParcelWithAppOpsIfNeeded} + * + * @param code the op code to note for + * @param attributionTag the attribution tag to note for + * @param packageName the package to note for + */ + private static void collectNotedOpSync(int code, @Nullable String attributionTag, + @NonNull String packageName) { // If this is inside of a two-way binder call: // We are inside of a two-way binder call. Delivered to caller via // {@link #prefixParcelWithAppOpsIfNeeded} - int op = sOpStrToOp.get(syncOp.getOp()); - ArrayMap appOpsNoted = sAppOpsNotedInThisBinderTransaction.get(); + ArrayMap> appOpsNoted = + sAppOpsNotedInThisBinderTransaction.get(); if (appOpsNoted == null) { appOpsNoted = new ArrayMap<>(1); sAppOpsNotedInThisBinderTransaction.set(appOpsNoted); } - long[] appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag()); - if (appOpsNotedForAttribution == null) { - appOpsNotedForAttribution = new long[2]; - appOpsNoted.put(syncOp.getAttributionTag(), appOpsNotedForAttribution); + ArrayMap packageAppOpsNotedForAttribution = appOpsNoted.get(packageName); + if (packageAppOpsNotedForAttribution == null) { + packageAppOpsNotedForAttribution = new ArrayMap<>(1); + appOpsNoted.put(packageName, packageAppOpsNotedForAttribution); } - if (op < 64) { - appOpsNotedForAttribution[0] |= 1L << op; + long[] appOpsNotedForAttribution = packageAppOpsNotedForAttribution.get(attributionTag); + if (appOpsNotedForAttribution == null) { + appOpsNotedForAttribution = new long[2]; + packageAppOpsNotedForAttribution.put(attributionTag, appOpsNotedForAttribution); + } + + if (code < 64) { + appOpsNotedForAttribution[0] |= 1L << code; } else { - appOpsNotedForAttribution[1] |= 1L << (op - 64); + appOpsNotedForAttribution[1] |= 1L << (code - 64); } } @@ -9045,9 +9021,7 @@ public class AppOpsManager { } } - Integer binderUid = sBinderThreadCallingUid.get(); - - if (binderUid != null && binderUid == uid) { + if (isListeningForOpNotedInBinderTransaction()) { return COLLECT_SYNC; } else { return COLLECT_ASYNC; @@ -9064,21 +9038,31 @@ public class AppOpsManager { * * @hide */ + // TODO (b/186872903) Refactor how sync noted ops are propagaged. public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) { - ArrayMap notedAppOps = sAppOpsNotedInThisBinderTransaction.get(); + final ArrayMap> notedAppOps = + sAppOpsNotedInThisBinderTransaction.get(); if (notedAppOps == null) { return; } p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER); - int numAttributionWithNotesAppOps = notedAppOps.size(); - p.writeInt(numAttributionWithNotesAppOps); + final int packageCount = notedAppOps.size(); + p.writeInt(packageCount); - for (int i = 0; i < numAttributionWithNotesAppOps; i++) { + for (int i = 0; i < packageCount; i++) { p.writeString(notedAppOps.keyAt(i)); - p.writeLong(notedAppOps.valueAt(i)[0]); - p.writeLong(notedAppOps.valueAt(i)[1]); + + final ArrayMap notedTagAppOps = notedAppOps.valueAt(i); + final int tagCount = notedTagAppOps.size(); + p.writeInt(tagCount); + + for (int j = 0; j < tagCount; j++) { + p.writeString(notedTagAppOps.keyAt(j)); + p.writeLong(notedTagAppOps.valueAt(j)[0]); + p.writeLong(notedTagAppOps.valueAt(j)[1]); + } } } @@ -9093,36 +9077,57 @@ public class AppOpsManager { * @hide */ public static void readAndLogNotedAppops(@NonNull Parcel p) { - int numAttributionsWithNotedAppOps = p.readInt(); + final int packageCount = p.readInt(); + if (packageCount <= 0) { + return; + } - for (int i = 0; i < numAttributionsWithNotedAppOps; i++) { - String attributionTag = p.readString(); - long[] rawNotedAppOps = new long[2]; - rawNotedAppOps[0] = p.readLong(); - rawNotedAppOps[1] = p.readLong(); + final String myPackageName = ActivityThread.currentPackageName(); + if (myPackageName == null) { + return; + } - if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) { - BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps); + synchronized (sLock) { + for (int i = 0; i < packageCount; i++) { + final String packageName = p.readString(); - synchronized (sLock) { + final int tagCount = p.readInt(); + for (int j = 0; j < tagCount; j++) { + final String attributionTag = p.readString(); + final long[] rawNotedAppOps = new long[2]; + rawNotedAppOps[0] = p.readLong(); + rawNotedAppOps[1] = p.readLong(); + + if (rawNotedAppOps[0] == 0 && rawNotedAppOps[1] == 0) { + continue; + } + + final BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps); for (int code = notedAppOps.nextSetBit(0); code != -1; code = notedAppOps.nextSetBit(code + 1)) { - if (sOnOpNotedCallback != null) { - sOnOpNotedCallback.onNoted(new SyncNotedAppOp(code, attributionTag)); - } else { - String message = getFormattedStackTrace(); - sUnforwardedOps.add( - new AsyncNotedAppOp(code, Process.myUid(), attributionTag, - message, System.currentTimeMillis())); - if (sUnforwardedOps.size() > MAX_UNFORWARDED_OPS) { - sUnforwardedOps.remove(0); + if (myPackageName.equals(packageName)) { + if (sOnOpNotedCallback != null) { + sOnOpNotedCallback.onNoted(new SyncNotedAppOp(code, + attributionTag, packageName)); + } else { + String message = getFormattedStackTrace(); + sUnforwardedOps.add(new AsyncNotedAppOp(code, Process.myUid(), + attributionTag, message, System.currentTimeMillis())); + if (sUnforwardedOps.size() > MAX_UNFORWARDED_OPS) { + sUnforwardedOps.remove(0); + } } + } else if (isListeningForOpNotedInBinderTransaction()) { + collectNotedOpSync(code, attributionTag, packageName); + } + } + for (int code = notedAppOps.nextSetBit(0); code != -1; + code = notedAppOps.nextSetBit(code + 1)) { + if (myPackageName.equals(packageName)) { + sMessageCollector.onNoted(new SyncNotedAppOp(code, + attributionTag, packageName)); } } - } - for (int code = notedAppOps.nextSetBit(0); code != -1; - code = notedAppOps.nextSetBit(code + 1)) { - sMessageCollector.onNoted(new SyncNotedAppOp(code, attributionTag)); } } } @@ -9229,7 +9234,17 @@ public class AppOpsManager { * @hide */ public static boolean isListeningForOpNoted() { - return sOnOpNotedCallback != null || isCollectingStackTraces(); + return sOnOpNotedCallback != null || isListeningForOpNotedInBinderTransaction() + || isCollectingStackTraces(); + } + + /** + * @return whether we are in a binder transaction and collecting appops. + */ + private static boolean isListeningForOpNotedInBinderTransaction() { + return (sThreadsListeningForOpNotedInBinderTransaction + & Thread.currentThread().getId()) != 0 + && sBinderThreadCallingUid.get() != null; } /** diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java index bc4e5436996d..32d889e81cb0 100644 --- a/core/java/android/app/SyncNotedAppOp.java +++ b/core/java/android/app/SyncNotedAppOp.java @@ -19,7 +19,9 @@ package android.app; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.os.Parcelable; +import android.os.Process; import com.android.internal.annotations.Immutable; import com.android.internal.util.DataClass; @@ -48,13 +50,19 @@ public final class SyncNotedAppOp implements Parcelable { private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mOpCode; /** attributionTag of synchronous appop noted */ private final @Nullable String mAttributionTag; + /** + * The package this op applies to + * @hide + */ + private final @NonNull String mPackageName; /** * Native code relies on parcel ordering, do not change * @hide */ + @TestApi public SyncNotedAppOp(int opMode, @IntRange(from = 0L) int opCode, - @Nullable String attributionTag) { + @Nullable String attributionTag, @NonNull String packageName) { this.mOpCode = opCode; com.android.internal.util.AnnotationValidations.validate( IntRange.class, null, mOpCode, @@ -62,6 +70,7 @@ public final class SyncNotedAppOp implements Parcelable { "to", AppOpsManager._NUM_OP - 1); this.mAttributionTag = attributionTag; this.mOpMode = opMode; + this.mPackageName = packageName; } /** @@ -73,7 +82,25 @@ public final class SyncNotedAppOp implements Parcelable { * attributionTag of synchronous appop noted */ public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String attributionTag) { - this(AppOpsManager.MODE_IGNORED, opCode, attributionTag); + this(AppOpsManager.MODE_IGNORED, opCode, attributionTag, ActivityThread + .currentPackageName()); + } + + /** + * Creates a new SyncNotedAppOp. + * + * @param opCode + * op code of synchronous appop noted + * @param attributionTag + * attributionTag of synchronous appop noted + * @param packageName + * The package this op applies to + * + * @hide + */ + public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String attributionTag, + @NonNull String packageName) { + this(AppOpsManager.MODE_IGNORED, opCode, attributionTag, packageName); } /** @@ -113,6 +140,16 @@ public final class SyncNotedAppOp implements Parcelable { return mAttributionTag; } + /** + * The package this op applies to + * + * @hide + */ + @DataClass.Generated.Member + public @NonNull String getPackageName() { + return mPackageName; + } + @Override @DataClass.Generated.Member public boolean equals(@Nullable Object o) { @@ -128,7 +165,8 @@ public final class SyncNotedAppOp implements Parcelable { return true && mOpMode == that.mOpMode && mOpCode == that.mOpCode - && java.util.Objects.equals(mAttributionTag, that.mAttributionTag); + && java.util.Objects.equals(mAttributionTag, that.mAttributionTag) + && java.util.Objects.equals(mPackageName, that.mPackageName); } @Override @@ -141,6 +179,7 @@ public final class SyncNotedAppOp implements Parcelable { _hash = 31 * _hash + mOpMode; _hash = 31 * _hash + mOpCode; _hash = 31 * _hash + java.util.Objects.hashCode(mAttributionTag); + _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName); return _hash; } @@ -156,6 +195,7 @@ public final class SyncNotedAppOp implements Parcelable { dest.writeInt(mOpMode); dest.writeInt(mOpCode); if (mAttributionTag != null) dest.writeString(mAttributionTag); + dest.writeString(mPackageName); } @Override @@ -173,6 +213,7 @@ public final class SyncNotedAppOp implements Parcelable { int opMode = in.readInt(); int opCode = in.readInt(); String attributionTag = (flg & 0x4) == 0 ? null : in.readString(); + String packageName = in.readString(); this.mOpMode = opMode; this.mOpCode = opCode; @@ -181,6 +222,9 @@ public final class SyncNotedAppOp implements Parcelable { "from", 0L, "to", AppOpsManager._NUM_OP - 1); this.mAttributionTag = attributionTag; + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); // onConstructed(); // You can define this method to get a callback } @@ -200,10 +244,10 @@ public final class SyncNotedAppOp implements Parcelable { }; @DataClass.Generated( - time = 1617317997768L, + time = 1619711733947L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java", - inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false)") + inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 2c155d5884ac..7ab731f15ad2 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -31,10 +31,9 @@ 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; @@ -70,10 +69,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 - 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. + * 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. *

* 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 @@ -85,80 +84,11 @@ 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 { - /** - * @hide - */ - static class RenouncedPermissionsParcelling implements Parcelling> { + private final @NonNull AttributionSourceState mAttributionSourceState; - @Override - public void parcel(Set 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 unparcel(Parcel source) { - final int size = source.readInt(); - if (size < 0) { - return null; - } - final ArraySet 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 mRenouncedPermissions = null; - - /** - * The next app to receive the permission protected data. - */ - private @Nullable AttributionSource mNext = null; + private @Nullable AttributionSource mNextCached; + private @Nullable Set mRenouncedPermissionsCached; /** @hide */ @TestApi @@ -171,8 +101,7 @@ public final class AttributionSource implements Parcelable { @TestApi public AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable AttributionSource next) { - this(uid, packageName, attributionTag, /*token*/ null, - /*renouncedPermissions*/ null, next); + this(uid, packageName, attributionTag, /*renouncedPermissions*/ null, next); } /** @hide */ @@ -180,8 +109,8 @@ public final class AttributionSource implements Parcelable { public AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable Set renouncedPermissions, @Nullable AttributionSource next) { - this(uid, packageName, attributionTag, /*token*/ null, - renouncedPermissions, next); + this(uid, packageName, attributionTag, /*token*/ null, (renouncedPermissions != null) + ? renouncedPermissions.toArray(new String[0]) : null, next); } /** @hide */ @@ -191,16 +120,49 @@ 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(mUid, mPackageName, mAttributionTag, mToken, - mRenouncedPermissions, next); + return new AttributionSource(getUid(), getPackageName(), getAttributionTag(), + getToken(), mAttributionSourceState.renouncedPermissions, next); } /** @hide */ public AttributionSource withToken(@Nullable IBinder token) { - return new AttributionSource(mUid, mPackageName, mAttributionTag, token, - mRenouncedPermissions, mNext); + 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; } /** @@ -213,10 +175,9 @@ public final class AttributionSource implements Parcelable { * from the caller. */ public void enforceCallingUid() { - final int callingUid = Binder.getCallingUid(); - if (callingUid != Process.SYSTEM_UID && callingUid != mUid) { - throw new SecurityException("Calling uid: " + callingUid - + " doesn't match source uid: " + mUid); + if (!checkCallingUid()) { + throw new SecurityException("Calling uid: " + Binder.getCallingUid() + + " doesn't match source uid: " + mAttributionSourceState.uid); } // No need to check package as app ops manager does it already. } @@ -231,7 +192,8 @@ public final class AttributionSource implements Parcelable { */ public boolean checkCallingUid() { final int callingUid = Binder.getCallingUid(); - if (callingUid != Process.SYSTEM_UID && callingUid != mUid) { + if (callingUid != Process.SYSTEM_UID + && callingUid != mAttributionSourceState.uid) { return false; } // No need to check package as app ops manager does it already. @@ -242,11 +204,12 @@ public final class AttributionSource implements Parcelable { public String toString() { if (Build.IS_DEBUGGABLE) { return "AttributionSource { " + - "uid = " + mUid + ", " + - "packageName = " + mPackageName + ", " + - "attributionTag = " + mAttributionTag + ", " + - "token = " + mToken + ", " + - "next = " + mNext + + "uid = " + mAttributionSourceState.uid + ", " + + "packageName = " + mAttributionSourceState.packageName + ", " + + "attributionTag = " + mAttributionSourceState.attributionTag + ", " + + "token = " + mAttributionSourceState.token + ", " + + "next = " + (mAttributionSourceState.next != null + ? mAttributionSourceState.next[0]: null) + " }"; } return super.toString(); @@ -258,8 +221,8 @@ public final class AttributionSource implements Parcelable { * @hide */ public int getNextUid() { - if (mNext != null) { - return mNext.getUid(); + if (mAttributionSourceState.next != null) { + return mAttributionSourceState.next[0].uid; } return Process.INVALID_UID; } @@ -270,8 +233,8 @@ public final class AttributionSource implements Parcelable { * @hide */ public @Nullable String getNextPackageName() { - if (mNext != null) { - return mNext.getPackageName(); + if (mAttributionSourceState.next != null) { + return mAttributionSourceState.next[0].packageName; } return null; } @@ -283,8 +246,8 @@ public final class AttributionSource implements Parcelable { * @hide */ public @Nullable String getNextAttributionTag() { - if (mNext != null) { - return mNext.getAttributionTag(); + if (mAttributionSourceState.next != null) { + return mAttributionSourceState.next[0].attributionTag; } return null; } @@ -297,8 +260,9 @@ public final class AttributionSource implements Parcelable { * @return Whether this is a trusted source. */ public boolean isTrusted(@NonNull Context context) { - return mToken != null && context.getSystemService(PermissionManager.class) - .isRegisteredAttributionSource(this); + return mAttributionSourceState.token != null + && context.getSystemService(PermissionManager.class) + .isRegisteredAttributionSource(this); } /** @@ -310,71 +274,36 @@ public final class AttributionSource implements Parcelable { @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) @NonNull public Set getRenouncedPermissions() { - 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 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 + if (mRenouncedPermissionsCached == null) { + if (mAttributionSourceState.renouncedPermissions != null) { + mRenouncedPermissionsCached = new ArraySet<>( + mAttributionSourceState.renouncedPermissions); + } else { + mRenouncedPermissionsCached = Collections.emptySet(); + } + } + return mRenouncedPermissionsCached; } /** * The UID that is accessing the permission protected data. */ public int getUid() { - return mUid; + return mAttributionSourceState.uid; } /** * The package that is accessing the permission protected data. */ public @Nullable String getPackageName() { - return mPackageName; + return mAttributionSourceState.packageName; } /** * The attribution tag of the app accessing the permission protected data. */ public @Nullable String getAttributionTag() { - return mAttributionTag; + return mAttributionSourceState.attributionTag; } /** @@ -383,113 +312,56 @@ public final class AttributionSource implements Parcelable { * @hide */ public @Nullable IBinder getToken() { - return mToken; + return mAttributionSourceState.token; } /** * The next app to receive the permission protected data. */ public @Nullable AttributionSource getNext() { - return mNext; + if (mNextCached == null && mAttributionSourceState.next != null) { + mNextCached = new AttributionSource(mAttributionSourceState.next[0]); + } + return mNextCached; } @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; - //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); + 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()); } @Override public int hashCode() { - // You can override field hashCode logic by defining methods like: - // int fieldNameHashCode() { ... } - int _hash = 1; - _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); + _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()); return _hash; } - static Parcelling> 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) { - // 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); + mAttributionSourceState.writeToParcel(dest, 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 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 CREATOR = new Parcelable.Creator() { @Override @@ -506,15 +378,9 @@ public final class AttributionSource implements Parcelable { /** * A builder for {@link AttributionSource} */ - @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 mRenouncedPermissions; - private @Nullable AttributionSource mNext; + public static final class Builder { + private @NonNull final AttributionSourceState mAttributionSourceState = + new AttributionSourceState(); private long mBuilderFieldsSet = 0L; @@ -524,9 +390,8 @@ public final class AttributionSource implements Parcelable { * @param uid * The UID that is accessing the permission protected data. */ - public Builder( - int uid) { - mUid = uid; + public Builder(int uid) { + mAttributionSourceState.uid = uid; } /** @@ -535,7 +400,7 @@ public final class AttributionSource implements Parcelable { public @NonNull Builder setPackageName(@Nullable String value) { checkNotUsed(); mBuilderFieldsSet |= 0x2; - mPackageName = value; + mAttributionSourceState.packageName = value; return this; } @@ -545,7 +410,7 @@ public final class AttributionSource implements Parcelable { public @NonNull Builder setAttributionTag(@Nullable String value) { checkNotUsed(); mBuilderFieldsSet |= 0x4; - mAttributionTag = value; + mAttributionSourceState.attributionTag = value; return this; } @@ -578,7 +443,8 @@ public final class AttributionSource implements Parcelable { public @NonNull Builder setRenouncedPermissions(@Nullable Set value) { checkNotUsed(); mBuilderFieldsSet |= 0x10; - mRenouncedPermissions = value; + mAttributionSourceState.renouncedPermissions = (value != null) + ? value.toArray(new String[0]) : null; return this; } @@ -588,7 +454,8 @@ public final class AttributionSource implements Parcelable { public @NonNull Builder setNext(@Nullable AttributionSource value) { checkNotUsed(); mBuilderFieldsSet |= 0x20; - mNext = value; + mAttributionSourceState.next = (value != null) ? new AttributionSourceState[] + {value.mAttributionSourceState} : null; return this; } @@ -598,28 +465,21 @@ public final class AttributionSource implements Parcelable { mBuilderFieldsSet |= 0x40; // Mark builder used if ((mBuilderFieldsSet & 0x2) == 0) { - mPackageName = null; + mAttributionSourceState.packageName = null; } if ((mBuilderFieldsSet & 0x4) == 0) { - mAttributionTag = null; + mAttributionSourceState.attributionTag = null; } if ((mBuilderFieldsSet & 0x8) == 0) { - mToken = null; + mAttributionSourceState.token = null; } if ((mBuilderFieldsSet & 0x10) == 0) { - mRenouncedPermissions = null; + mAttributionSourceState.renouncedPermissions = null; } if ((mBuilderFieldsSet & 0x20) == 0) { - mNext = null; + mAttributionSourceState.next = null; } - AttributionSource o = new AttributionSource( - mUid, - mPackageName, - mAttributionTag, - mToken, - mRenouncedPermissions, - mNext); - return o; + return new AttributionSource(mAttributionSourceState); } private void checkNotUsed() { @@ -629,9 +489,4 @@ 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 5089f30585b4..66e088359459 100644 --- a/core/java/android/content/PermissionChecker.java +++ b/core/java/android/content/PermissionChecker.java @@ -16,21 +16,19 @@ 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.util.Slog; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.permission.IPermissionChecker; 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 @@ -72,34 +70,44 @@ import java.util.concurrent.ConcurrentHashMap; * @hide */ public final class PermissionChecker { - private static final String LOG_TAG = PermissionChecker.class.getName(); + /** + * The permission is granted. + * + * @hide + */ + public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED; - 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: + /** + * The permission is denied. Applicable only to runtime and app op permissions. + * + *

Returned when: *

    - *
  • For non app op permissions, returned when the permission is denied.
  • - *
  • For app op permissions, returned when the app op is denied or app op is - * {@link AppOpsManager#MODE_DEFAULT} and permission is denied.
  • + *
  • the runtime permission is granted, but the corresponding app op is denied + * for runtime permissions.
  • + *
  • the app ops is ignored for app op permissions.
  • *
* + * @hide */ - public static final int PERMISSION_HARD_DENIED = AppOpsManager.MODE_ERRORED; + public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED; + + /** + * The permission is denied. + * + *

Returned when: + *

    + *
  • the permission is denied for non app op permissions.
  • + *
  • the app op is denied or app op is {@link AppOpsManager#MODE_DEFAULT} + * and permission is denied.
  • + *
+ * + * @hide + */ + public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED; /** 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 sPlatformPermissions - = new ConcurrentHashMap<>(); - /** @hide */ @IntDef({PERMISSION_GRANTED, PERMISSION_SOFT_DENIED, @@ -107,6 +115,8 @@ public final class PermissionChecker { @Retention(RetentionPolicy.SOURCE) public @interface PermissionResult {} + private static volatile IPermissionChecker sService; + private PermissionChecker() { /* do nothing */ } @@ -232,7 +242,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, pid, attributionSource, + return checkPermissionForDataDeliveryCommon(context, permission, attributionSource, message, false /*startDataDelivery*/, /*fromDatasource*/ true); } @@ -307,21 +317,23 @@ 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, pid, attributionSource, + return checkPermissionForDataDeliveryCommon(context, permission, attributionSource, message, startDataDelivery, /*fromDatasource*/ false); } private static int checkPermissionForDataDeliveryCommon(@NonNull Context context, - @NonNull String permission, int pid, @NonNull AttributionSource attributionSource, + @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean startDataDelivery, boolean fromDatasource) { // If the check failed in the middle of the chain, finish any started op. - final int result = checkPermissionCommon(context, permission, attributionSource, - message, true /*forDataDelivery*/, startDataDelivery, fromDatasource); - if (startDataDelivery && result != PERMISSION_GRANTED) { - finishDataDelivery(context, AppOpsManager.permissionToOp(permission), - attributionSource); + try { + final int result = getPermissionCheckerService().checkPermission(permission, + attributionSource.asState(), message, true /*forDataDelivery*/, + startDataDelivery, fromDatasource); + return result; + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } - return result; + return PERMISSION_HARD_DENIED; } /** @@ -356,9 +368,14 @@ public final class PermissionChecker { public static int checkPermissionAndStartDataDelivery(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message) { - return checkPermissionCommon(context, permission, attributionSource, - message, true /*forDataDelivery*/, /*startDataDelivery*/ true, - /*fromDatasource*/ false); + try { + return getPermissionCheckerService().checkPermission(permission, + attributionSource.asState(), message, true /*forDataDelivery*/, + /*startDataDelivery*/ true, /*fromDatasource*/ false); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return PERMISSION_HARD_DENIED; } /** @@ -390,13 +407,14 @@ public final class PermissionChecker { public static int startOpForDataDelivery(@NonNull Context context, @NonNull String opName, @NonNull AttributionSource attributionSource, @Nullable String message) { - 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); + try { + return getPermissionCheckerService().checkOp( + AppOpsManager.strOpToOp(opName), attributionSource.asState(), message, + true /*forDataDelivery*/, true /*startDataDelivery*/); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } - return result; + return PERMISSION_HARD_DENIED; } /** @@ -412,15 +430,10 @@ public final class PermissionChecker { */ public static void finishDataDelivery(@NonNull Context context, @NonNull String op, @NonNull AttributionSource attributionSource) { - 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()); + try { + getPermissionCheckerService().finishDataDelivery(op, attributionSource.asState()); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } } @@ -456,8 +469,14 @@ public final class PermissionChecker { public static int checkOpForPreflight(@NonNull Context context, @NonNull String opName, @NonNull AttributionSource attributionSource, @Nullable String message) { - return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource, - message, false /*forDataDelivery*/, false /*startDataDelivery*/); + try { + return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName), + attributionSource.asState(), message, false /*forDataDelivery*/, + false /*startDataDelivery*/); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return PERMISSION_HARD_DENIED; } /** @@ -489,8 +508,14 @@ public final class PermissionChecker { public static int checkOpForDataDelivery(@NonNull Context context, @NonNull String opName, @NonNull AttributionSource attributionSource, @Nullable String message) { - return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource, - message, true /*forDataDelivery*/, false /*startDataDelivery*/); + try { + return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName), + attributionSource.asState(), message, true /*forDataDelivery*/, + false /*startDataDelivery*/); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return PERMISSION_HARD_DENIED; } /** @@ -561,9 +586,14 @@ public final class PermissionChecker { @PermissionResult public static int checkPermissionForPreflight(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource) { - return checkPermissionCommon(context, permission, attributionSource, - null /*message*/, false /*forDataDelivery*/, /*startDataDelivery*/ false, - /*fromDatasource*/ false); + try { + return getPermissionCheckerService().checkPermission(permission, + attributionSource.asState(), null /*message*/, false /*forDataDelivery*/, + /*startDataDelivery*/ false, /*fromDatasource*/ false); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return PERMISSION_HARD_DENIED; } /** @@ -798,356 +828,12 @@ public final class PermissionChecker { Binder.getCallingUid(), packageName); } - @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; - } + 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); } - - 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 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()); + return sService; } } diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index d026e959905c..2a9b703583a6 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -560,9 +560,6 @@ public final class BinderProxy implements IBinder { } } - final AppOpsManager.PausedNotedAppOpsCollection prevCollection = - AppOpsManager.pauseNotedAppOpsCollection(); - if ((flags & FLAG_ONEWAY) == 0 && AppOpsManager.isListeningForOpNoted()) { flags |= FLAG_COLLECT_NOTED_APP_OPS; } @@ -571,8 +568,6 @@ public final class BinderProxy implements IBinder { boolean replyOwnsNative = (reply == null) ? false : reply.ownsNativeParcelObject(); return transactNative(code, data, reply, replyOwnsNative, flags); } finally { - AppOpsManager.resumeNotedAppOpsCollection(prevCollection); - if (transactListener != null) { transactListener.onTransactEnded(session); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 7eaf18fc971b..c73593ff009c 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -3085,7 +3085,8 @@ public class AppOpsService extends IAppOpsService.Stub { String resolveProxyPackageName = AppOpsManager.resolvePackageName(proxyUid, proxyPackageName); if (resolveProxyPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, + proxiedAttributionTag, proxiedPackageName); } final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid; @@ -3101,14 +3102,16 @@ public class AppOpsService extends IAppOpsService.Stub { resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage); if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) { - return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag); + return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag, + proxiedPackageName); } } String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid, proxiedPackageName); if (resolveProxiedPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, + proxiedPackageName); } final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED @@ -3135,7 +3138,8 @@ public class AppOpsService extends IAppOpsService.Stub { String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); if (resolvedPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, + packageName); } return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag, Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF, @@ -3152,7 +3156,8 @@ public class AppOpsService extends IAppOpsService.Stub { bypass = verifyAndGetBypass(uid, packageName, attributionTag); } catch (SecurityException e) { Slog.e(TAG, "noteOperation", e); - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, + packageName); } synchronized (this) { @@ -3163,7 +3168,8 @@ public class AppOpsService extends IAppOpsService.Stub { AppOpsManager.MODE_IGNORED); if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid + " package " + packageName); - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, + packageName); } final Op op = getOpLocked(ops, code, uid, true); final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); @@ -3179,7 +3185,8 @@ public class AppOpsService extends IAppOpsService.Stub { attributedOp.rejected(uidState.state, flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, AppOpsManager.MODE_IGNORED); - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, + packageName); } // If there is a non-default per UID policy (we set UID op mode only if // non-default) it takes over, otherwise use the per package policy. @@ -3192,7 +3199,7 @@ public class AppOpsService extends IAppOpsService.Stub { attributedOp.rejected(uidState.state, flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, uidMode); - return new SyncNotedAppOp(uidMode, code, attributionTag); + return new SyncNotedAppOp(uidMode, code, attributionTag, packageName); } } else { final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) @@ -3205,7 +3212,7 @@ public class AppOpsService extends IAppOpsService.Stub { attributedOp.rejected(uidState.state, flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, mode); - return new SyncNotedAppOp(mode, code, attributionTag); + return new SyncNotedAppOp(mode, code, attributionTag, packageName); } } if (DEBUG) { @@ -3224,7 +3231,8 @@ public class AppOpsService extends IAppOpsService.Stub { shouldCollectMessage); } - return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag, + packageName); } } @@ -3528,7 +3536,8 @@ public class AppOpsService extends IAppOpsService.Stub { String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); if (resolvedPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, + packageName); } // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution @@ -3539,7 +3548,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (code == OP_RECORD_AUDIO_HOTWORD) { int result = checkOperation(OP_RECORD_AUDIO, uid, packageName); if (result != AppOpsManager.MODE_ALLOWED) { - return new SyncNotedAppOp(result, code, attributionTag); + return new SyncNotedAppOp(result, code, attributionTag, packageName); } } return startOperationUnchecked(clientId, code, uid, packageName, attributionTag, @@ -3578,7 +3587,8 @@ public class AppOpsService extends IAppOpsService.Stub { String resolvedProxyPackageName = AppOpsManager.resolvePackageName(proxyUid, proxyPackageName); if (resolvedProxyPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, + proxiedPackageName); } final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid; @@ -3589,7 +3599,8 @@ public class AppOpsService extends IAppOpsService.Stub { String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid, proxiedPackageName); if (resolvedProxiedPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, + proxiedPackageName); } final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED @@ -3637,7 +3648,8 @@ public class AppOpsService extends IAppOpsService.Stub { bypass = verifyAndGetBypass(uid, packageName, attributionTag); } catch (SecurityException e) { Slog.e(TAG, "startOperation", e); - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, + packageName); } synchronized (this) { @@ -3649,7 +3661,8 @@ public class AppOpsService extends IAppOpsService.Stub { } if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid + " package " + packageName); - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, + packageName); } final Op op = getOpLocked(ops, code, uid, true); if (isOpRestrictedLocked(uid, code, packageName, bypass)) { @@ -3657,7 +3670,8 @@ public class AppOpsService extends IAppOpsService.Stub { scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, AppOpsManager.MODE_IGNORED); } - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, + packageName); } final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); @@ -3678,7 +3692,7 @@ public class AppOpsService extends IAppOpsService.Stub { scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, uidMode); } - return new SyncNotedAppOp(uidMode, code, attributionTag); + return new SyncNotedAppOp(uidMode, code, attributionTag, packageName); } } else { final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) @@ -3694,7 +3708,7 @@ public class AppOpsService extends IAppOpsService.Stub { scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, mode); } - return new SyncNotedAppOp(mode, code, attributionTag); + return new SyncNotedAppOp(mode, code, attributionTag, packageName); } } if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid @@ -3716,7 +3730,8 @@ public class AppOpsService extends IAppOpsService.Stub { message, shouldCollectMessage); } - return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag); + return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag, + packageName); } @Override 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 884bbea2eb28..b3fa98e810cd 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -70,7 +70,9 @@ 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; @@ -104,6 +106,7 @@ 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; @@ -164,6 +167,7 @@ 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; @@ -451,6 +455,7 @@ 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); } @@ -5448,4 +5453,419 @@ 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 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 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)); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java index 41237c8ae51f..12e0d8bc5d62 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -168,7 +168,7 @@ public class ActivityManagerServiceTest { private void mockNoteOperation() { SyncNotedAppOp allowed = new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, - AppOpsManager.OP_GET_USAGE_STATS, null); + AppOpsManager.OP_GET_USAGE_STATS, null, mContext.getPackageName()); when(mAppOpsService.noteOperation(eq(AppOpsManager.OP_GET_USAGE_STATS), eq(Process.myUid()), nullable(String.class), nullable(String.class), any(Boolean.class), nullable(String.class), any(Boolean.class))).thenReturn(allowed);