From 4bf102ae26f74e3c5c4214eb244df023ee1ad4d7 Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Thu, 22 Apr 2021 20:09:49 +0000 Subject: [PATCH] Prepare AttributionSource to expose to native Separate the internal state of AttributionSource from the class to make it a simple AIDL we can translate automatically to native - keeping Java and native parts in sync. This would allow writing a thin native lib for checking attribution source permissions which would be used to teach camera and audio about attributions. Deinfe an AIDL interface for passing around an attribution source and opr performing permission checker oprations allowing native and Java permission checks on attribution chains to be handled. The Java side permission checker functions are in a dedicated permisison checker service on top of which sits the PermissionChecker. We expose similar PermissionChecker native APIs sitting on top of the same remote interface. The nice thing is that we have native and Java permisison checkers in sync sharing remoting code and being close in shape. For now the PermissionChecker in Java is divorced from the PermissionManager but in T we will consider how to unify them, either by an extension object on the PermmissionManager or APIs on the PermissionManager, or another approach, and then migrate clients off the PermissionChecker APIs. Sync app ops were not tracked across multiple binder calls which prevents moving the permission checks in the system server as this adds one more hop. Now sync ops are propagated backed the call stack and only the ops for the package are dispatched to it and the rest are propagated back to the caller, recursively. bug: 158792096 Test: atest CtsPermission5TestCases atest CtsAppOps2TestCases atest CtsPermissionTestCases atest CtsPermission2TestCases atest CtsPermission3TestCases atest CtsPermission4TestCases atest CtsPermission5TestCases Change-Id: Ia5cbd2eb20a2da172a5960afdddd7e467f4bcb0d --- Android.bp | 1 + core/api/test-current.txt | 5 + core/java/android/app/AppOpsManager.java | 229 ++++---- core/java/android/app/SyncNotedAppOp.java | 54 +- .../android/content/AttributionSource.java | 385 +++++-------- .../android/content/PermissionChecker.java | 504 ++++-------------- core/java/android/os/BinderProxy.java | 5 - .../android/server/appop/AppOpsService.java | 55 +- .../permission/PermissionManagerService.java | 420 +++++++++++++++ .../server/am/ActivityManagerServiceTest.java | 2 +- 10 files changed, 848 insertions(+), 812 deletions(-) diff --git a/Android.bp b/Android.bp index 685c69df6823..8db5589d2ef6 100644 --- a/Android.bp +++ b/Android.bp @@ -325,6 +325,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 35767b3acb8c..8fd9e1eb4d73 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 efeef1b07c0e..36d161dfe7b4 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);