diff options
| author | Svet Ganov <svetoslavganov@google.com> | 2021-02-24 04:09:05 +0000 |
|---|---|---|
| committer | Svet Ganov <svetoslavganov@google.com> | 2021-03-29 16:49:33 +0000 |
| commit | 8d2ed506045d5d6cf25ef7ef77a30da910733657 (patch) | |
| tree | 6bbe71d982f29433262bb5ecb1bcf628a3c1a279 /core/java/android | |
| parent | e3585e106fac76bada696186ac2ba147bc40c2b8 (diff) | |
Runtime permission attribution improvements
When an app is proxying access to runtime permission protected
data it needs to check whether the calling app has a permission
to the data it is about to proxy which leaves a trace in app ops
that the requesting app perofmed a data access. However, then the
app doing the work needs to get the protected data itself from the
OS which access gets attributed only to itself. As a result there
are two data accesses in app ops where only the first one is a
proxy one that app A got access to Foo through app B - that is the
one we want to show in the permission tracking UIs - and one
for the data access - that is the one we would want to blame on
the calling app, and in fact, these two accesses should be one -
that app A accessed Foo though B. This limitation requires fragile
one off workarounds where both accesses use the same attribution
tag and sys UI has hardcoded rules to dedupe. Since this is not
documented we cannot expect that the ecosystem would reliably
do this workaround in apps that that the workaround in the OS
would be respected by every OEM.
This change adds a mechaism to resolve this issue. It allows for
an app to create an attribution context for another app and then
any private data access thorugh this context would result in a
single app op blame that A accessed Foo though B, i.e. we no longer
have double accounting. Also this can be nested through apps, e.g.
app A asks app B which asks app C for contacts. In this case app
B creates an attribution context for app A and calls into app C
which creates an attribution context for app B. When app C gets
contacts the entire attribution chain would get a porper, single
blame: that C accessed the data, that B got the data from C, and
that A got the data form B. Furthermore, this mechanism ensures
that apps cannot forget to check permissions for the caller
before proxying private data. In our example B and C don't need
to check the permisisons for A and B, respectively, since the
permisisons for the entire attribution chain are checked before
data delivery. Attribution chains are not forgeable preventing
a bad actor to create an arbitrary one - each attribution is
created by the app it refers to and points to a chain of
attributions created by their corresponding apps.
This change also fixes a bug where all content provider accesses
were double counted in app ops due to double noting. While at
this it also fixes that apps can now access their own last ops.
There was a bug where one could not pass null getting the attributed
ops from a historical package ops while this is a valid use case
since if there is no attribution everything is mapped to the null
tag. There were some app op APIs not being piped thorough the app
ops delegate and by extension through the app ops policy. Also
now that we have nice way to express the permission chain in a
call we no longer need the special casing in activity manager to
handle content provider accesses through the OS. Fixed a bug
where we don't properly handle the android.os.shell calls with
an invlaid tag which was failing while the shell can do any tag.
Finally, to ensure the mechanims is validated and works end-to-end
we are adding support for a voice recognizer to blame the client
app for the mic access. The recognition service can create a blaming
context when opening the mic and if the mic is open, which would
do all permission checks, we would not do so again. Since changes
to PermissionChercker for handling attribution sources were made
the CL also hooks up renounced permissoins in the request permission
flow and in the permission checks.
bug:158792096
bug:180647319
Test:atest CtsPermissionsTestCases
atest CtsPermissions2TestCases
atest CtsPermissions3TestCases
atest CtsPermissions4TestCases
atest CtsPermissions5TestCases
atest CtsAppOpsTestCases
atest CtsAppOps2TestCases
Change-Id: Ib04585515d3dc3956966005ae9d94955b2f3ee08
Diffstat (limited to 'core/java/android')
24 files changed, 2281 insertions, 693 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 47843cc113a2..0f38b5fdb5c3 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5246,13 +5246,40 @@ public class Activity extends ContextThemeWrapper if (requestCode < 0) { throw new IllegalArgumentException("requestCode should be >= 0"); } + if (mHasCurrentPermissionsRequest) { Log.w(TAG, "Can request only one set of permissions at a time"); // Dispatch the callback with empty arrays which means a cancellation. onRequestPermissionsResult(requestCode, new String[0], new int[0]); return; } - Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions); + + List<String> filteredPermissions = null; + + if (!getAttributionSource().getRenouncedPermissions().isEmpty()) { + final int permissionCount = permissions.length; + for (int i = 0; i < permissionCount; i++) { + if (getAttributionSource().getRenouncedPermissions().contains(permissions[i])) { + if (filteredPermissions == null) { + filteredPermissions = new ArrayList<>(i); + for (int j = 0; j < i; j++) { + filteredPermissions.add(permissions[i]); + } + } + } else if (filteredPermissions != null) { + filteredPermissions.add(permissions[i]); + } + } + } + + final Intent intent; + if (filteredPermissions == null) { + intent = getPackageManager().buildRequestPermissionsIntent(permissions); + } else { + intent = getPackageManager().buildRequestPermissionsIntent( + filteredPermissions.toArray(new String[0])); + } + startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null); mHasCurrentPermissionsRequest = true; } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 7e4af1ad7952..f76e1c0e50fb 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -50,6 +50,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; +import android.content.AttributionSource; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; @@ -5955,7 +5956,7 @@ public class AppOpsManager { * * @return The historical ops for the attribution. */ - public @Nullable AttributedHistoricalOps getAttributedOps(@NonNull String attributionTag) { + public @Nullable AttributedHistoricalOps getAttributedOps(@Nullable String attributionTag) { if (mAttributedHistoricalOps == null) { return null; } @@ -6480,7 +6481,7 @@ public class AppOpsManager { * Gets number of discrete historical app ops. * * @return The number historical app ops. - * @see #getOpAt(int) + * @see #getDiscreteAccessAt(int) */ public @IntRange(from = 0) int getDiscreteAccessCount() { if (mDiscreteAccesses == null) { @@ -6494,7 +6495,7 @@ public class AppOpsManager { * * @param index The index to lookup. * @return The op at the given index. - * @see #getOpCount() + * @see #getDiscreteAccessCount() */ public @NonNull AttributedOpEntry getDiscreteAccessAt(@IntRange(from = 0) int index) { if (mDiscreteAccesses == null) { @@ -7979,7 +7980,7 @@ public class AppOpsManager { try { collectNoteOpCallsForValidation(op); int collectionMode = getNotedOpCollectionMode(uid, packageName, op); - boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID ? true : false; + boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID; if (collectionMode == COLLECT_ASYNC) { if (message == null) { // Set stack trace as default message @@ -8033,14 +8034,9 @@ public class AppOpsManager { */ public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) { - int mode = noteProxyOpNoThrow(op, proxiedPackageName, proxiedUid, proxiedAttributionTag, - message); - if (mode == MODE_ERRORED) { - throw new SecurityException("Proxy package " + mContext.getOpPackageName() - + " from uid " + Process.myUid() + " or calling package " + proxiedPackageName - + " from uid " + proxiedUid + " not allowed to perform " + sOpNames[op]); - } - return mode; + return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(), + new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)), + message, /*skipProxyOperation*/ false); } /** @@ -8069,6 +8065,36 @@ public class AppOpsManager { } /** + * Make note of an application performing an operation on behalf of another application(s). + * + * @param op The operation to note. One of the OPSTR_* constants. + * @param attributionSource The permission identity for which to note. + * @param message A message describing the reason the op was noted + * @param skipProxyOperation Whether to skip the proxy note. + * + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED} + * if it is not allowed and should be silently ignored (without causing the app to crash). + * + * @throws SecurityException If the any proxying operations in the permission identityf + * chain fails. + * + * @hide + */ + public int noteProxyOp(@NonNull int op, @NonNull AttributionSource attributionSource, + @Nullable String message, boolean skipProxyOperation) { + final int mode = noteProxyOpNoThrow(op, attributionSource, message, skipProxyOperation); + if (mode == MODE_ERRORED) { + throw new SecurityException("Proxy package " + + attributionSource.getPackageName() + " from uid " + + attributionSource.getUid() + " or calling package " + + attributionSource.getNextPackageName() + " from uid " + + attributionSource.getNextUid() + " not allowed to perform " + + sOpNames[op]); + } + return mode; + } + + /** * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String, String)} instead */ @Deprecated @@ -8093,24 +8119,36 @@ public class AppOpsManager { */ public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName, int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) { - return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid, - proxiedAttributionTag, message); + return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource( + mContext.getAttributionSource(), new AttributionSource(proxiedUid, + proxiedPackageName, proxiedAttributionTag)), message, + /*skipProxyOperation*/ false); } /** - * @see #noteProxyOpNoThrow(String, String, int, String, String) + * Make note of an application performing an operation on behalf of another application(s). + * + * @param op The operation to note. One of the OPSTR_* constants. + * @param attributionSource The permission identity for which to note. + * @param message A message describing the reason the op was noted + * @param skipProxyOperation Whether to note op for the proxy + * + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED} + * if it is not allowed and should be silently ignored (without causing the app to crash). * * @hide */ @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck") - public int noteProxyOpNoThrow(int op, @Nullable String proxiedPackageName, int proxiedUid, - @Nullable String proxiedAttributionTag, @Nullable String message) { + public int noteProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource, + @Nullable String message, boolean skipProxyOperation) { int myUid = Process.myUid(); try { collectNoteOpCallsForValidation(op); - int collectionMode = getNotedOpCollectionMode(proxiedUid, proxiedPackageName, op); - boolean shouldCollectMessage = myUid == Process.SYSTEM_UID ? true : false; + int collectionMode = getNotedOpCollectionMode( + attributionSource.getNextUid(), + attributionSource.getNextAttributionTag(), op); + boolean shouldCollectMessage = (myUid == Process.SYSTEM_UID); if (collectionMode == COLLECT_ASYNC) { if (message == null) { // Set stack trace as default message @@ -8119,20 +8157,19 @@ public class AppOpsManager { } } - int mode = mService.noteProxyOperation(op, proxiedUid, proxiedPackageName, - proxiedAttributionTag, myUid, mContext.getOpPackageName(), - mContext.getAttributionTag(), collectionMode == COLLECT_ASYNC, message, - shouldCollectMessage); + int mode = mService.noteProxyOperation(op, attributionSource, + collectionMode == COLLECT_ASYNC, message, + shouldCollectMessage, skipProxyOperation); if (mode == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { - collectNotedOpForSelf(op, proxiedAttributionTag); + collectNotedOpForSelf(op, attributionSource.getNextAttributionTag()); } else if (collectionMode == COLLECT_SYNC // Only collect app-ops when the proxy is trusted && (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1, myUid) == PackageManager.PERMISSION_GRANTED || - Binder.getCallingUid() == proxiedUid)) { - collectNotedOpSync(op, proxiedAttributionTag); + Binder.getCallingUid() == attributionSource.getNextUid())) { + collectNotedOpSync(op, attributionSource.getNextAttributionTag()); } } @@ -8424,7 +8461,7 @@ public class AppOpsManager { try { collectNoteOpCallsForValidation(op); int collectionMode = getNotedOpCollectionMode(uid, packageName, op); - boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID ? true : false; + boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID; if (collectionMode == COLLECT_ASYNC) { if (message == null) { // Set stack trace as default message @@ -8450,6 +8487,7 @@ public class AppOpsManager { throw e.rethrowFromSystemServer(); } } + /** * Report that an application has started executing a long-running operation on behalf of * another application when handling an IPC. This function will verify that the calling uid and @@ -8470,19 +8508,45 @@ public class AppOpsManager { */ public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag, @Nullable String message) { - final int mode = startProxyOpNoThrow(op, proxiedUid, proxiedPackageName, - proxiedAttributionTag, message); + return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(), + new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)), + message, /*skipProxyOperation*/ false); + } + + /** + * Report that an application has started executing a long-running operation on behalf of + * another application for the attribution chain specified by the {@link AttributionSource}}. + * + * @param op The op to note + * @param attributionSource The permission identity for which to check + * @param message A message describing the reason the op was noted + * @param skipProxyOperation Whether to skip the proxy start. + * + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED} + * if it is not allowed and should be silently ignored (without causing the app to crash). + * + * @throws SecurityException If the any proxying operations in the permission identity + * chain fails. + * + * @hide + */ + public int startProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource, + @Nullable String message, boolean skipProxyOperation) { + final int mode = startProxyOpNoThrow(AppOpsManager.strOpToOp(op), attributionSource, + message, skipProxyOperation); if (mode == MODE_ERRORED) { - throw new SecurityException("Proxy package " + mContext.getOpPackageName() - + " from uid " + Process.myUid() + " or calling package " + proxiedPackageName - + " from uid " + proxiedUid + " not allowed to perform " - + sOpNames[strOpToOp(op)]); + throw new SecurityException("Proxy package " + + attributionSource.getPackageName() + " from uid " + + attributionSource.getUid() + " or calling package " + + attributionSource.getNextPackageName() + " from uid " + + attributionSource.getNextUid() + " not allowed to perform " + + op); } return mode; } /** - *Like {@link #startProxyOp(String, int, String, String, String)} but instead + * Like {@link #startProxyOp(String, int, String, String, String)} but instead * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}. * * @see #startProxyOp(String, int, String, String, String) @@ -8490,11 +8554,28 @@ public class AppOpsManager { public int startProxyOpNoThrow(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag, @Nullable String message) { - try { - int opInt = strOpToOp(op); + return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource( + mContext.getAttributionSource(), new AttributionSource(proxiedUid, + proxiedPackageName, proxiedAttributionTag)), message, + /*skipProxyOperation*/ false); + } - collectNoteOpCallsForValidation(opInt); - int collectionMode = getNotedOpCollectionMode(proxiedUid, proxiedPackageName, opInt); + /** + * Like {@link #startProxyOp(String, AttributionSource, String)} but instead + * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED} and + * the checks is for the attribution chain specified by the {@link AttributionSource}. + * + * @see #startProxyOp(String, AttributionSource, String) + * + * @hide + */ + public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource, + @Nullable String message, boolean skipProxyOperation) { + try { + collectNoteOpCallsForValidation(op); + int collectionMode = getNotedOpCollectionMode( + attributionSource.getNextUid(), + attributionSource.getNextPackageName(), op); boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID; if (collectionMode == COLLECT_ASYNC) { if (message == null) { @@ -8504,24 +8585,23 @@ public class AppOpsManager { } } - int mode = mService.startProxyOperation(getClientId(), opInt, proxiedUid, - proxiedPackageName, proxiedAttributionTag, Process.myUid(), - mContext.getOpPackageName(), mContext.getAttributionTag(), false, - collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); + int mode = mService.startProxyOperation(getClientId(), op, + attributionSource, false, collectionMode == COLLECT_ASYNC, message, + shouldCollectMessage, skipProxyOperation); if (mode == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { - collectNotedOpForSelf(opInt, proxiedAttributionTag); + collectNotedOpForSelf(op, + attributionSource.getNextAttributionTag()); } else if (collectionMode == COLLECT_SYNC // Only collect app-ops when the proxy is trusted && (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1, Process.myUid()) == PackageManager.PERMISSION_GRANTED - || Binder.getCallingUid() == proxiedUid)) { - collectNotedOpSync(opInt, proxiedAttributionTag); + || Binder.getCallingUid() == attributionSource.getNextUid())) { + collectNotedOpSync(op, attributionSource.getNextAttributionTag()); } } - return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -8580,22 +8660,37 @@ public class AppOpsManager { } /** - * Report that an application is no longer performing an operation that had previously + * Report that an application is no longer performing an operation that had previously * been started with {@link #startProxyOp(String, int, String, String, String)}. There is no * validation of input or result; the parameters supplied here must be the exact same ones * previously passed in when starting the operation. + * * @param op The operation which was started - * @param proxiedUid The uid the op was started on behalf of - * @param proxiedPackageName The package the op was started on behalf of - * @param proxiedAttributionTag The proxied {@link Context#createAttributionContext - * attribution tag} or {@code null} for default attribution + * @param proxiedUid The proxied appp's UID + * @param proxiedPackageName The proxied appp's package name + * @param proxiedAttributionTag The proxied appp's attribution tag or + * {@code null} for default attribution */ public void finishProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) { + finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(), + new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag))); + } + + /** + * Report that an application is no longer performing an operation that had previously + * been started with {@link #startProxyOp(String, AttributionSource, String)}. There is no + * validation of input or result; the parameters supplied here must be the exact same ones + * previously passed in when starting the operation. + * + * @param op The operation which was started + * @param attributionSource The permission identity for which to finish + * + * @hide + */ + public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource) { try { - mService.finishProxyOperation(getClientId(), strOpToOp(op), proxiedUid, - proxiedPackageName, proxiedAttributionTag, Process.myUid(), - mContext.getOpPackageName(), mContext.getAttributionTag()); + mService.finishProxyOperation(getClientId(), strOpToOp(op), attributionSource); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -8616,6 +8711,46 @@ public class AppOpsManager { } /** + * Get whether you are currently proxying to another package. That applies only + * for long running operations like {@link #OP_RECORD_AUDIO}. + * + * @param op The op. + * @param proxyAttributionTag Your attribution tag to query for. + * @param proxiedUid The proxied UID to query for. + * @param proxiedPackageName The proxied package to query for. + * @return Whether you are currently proxying to this target. + * + * @hide + */ + public boolean isProxying(int op, @NonNull String proxyAttributionTag, int proxiedUid, + @NonNull String proxiedPackageName) { + try { + return mService.isProxying(op, mContext.getOpPackageName(), + mContext.getAttributionTag(), proxiedUid, proxiedPackageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Clears the op state (last accesses + op modes) for a package but not + * the historical state. + * + * @param packageName The package to reset. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void resetPackageOpsNoHistory(@NonNull String packageName) { + try { + mService.resetPackageOpsNoHistory(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Start collection of noted appops on this thread. * * <p>Called at the beginning of a two way binder transaction. @@ -8771,7 +8906,7 @@ public class AppOpsManager { packageName = "android"; } - // check it the appops needs to be collected and cache result + // check if the appops needs to be collected and cache result if (sAppOpsToNote[op] == SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED) { boolean shouldCollectNotes; try { diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java index 5e032f00a3a0..a3d0cf2e972f 100644 --- a/core/java/android/app/AppOpsManagerInternal.java +++ b/core/java/android/app/AppOpsManagerInternal.java @@ -18,12 +18,17 @@ package android.app; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.AttributionSource; +import android.os.IBinder; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.app.IAppOpsCallback; import com.android.internal.util.function.HeptFunction; +import com.android.internal.util.function.HexFunction; +import com.android.internal.util.function.OctFunction; import com.android.internal.util.function.QuadFunction; +import com.android.internal.util.function.TriFunction; /** * App ops service local interface. @@ -76,6 +81,55 @@ public abstract class AppOpsManagerInternal { @Nullable String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean, Integer> superImpl); + + /** + * Allows overriding note proxy operation behavior. + * + * @param code The op code to note. + * @param attributionSource The permission identity of the caller. + * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected + * @param message The message in the async noted op + * @param shouldCollectMessage whether to collect messages + * @param skipProxyOperation Whether to skip the proxy portion of the operation + * @param superImpl The super implementation. + * @return The app op note result. + */ + int noteProxyOperation(int code, @NonNull AttributionSource attributionSource, + boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage, boolean skipProxyOperation, + @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean, + Boolean, Integer> superImpl); + + /** + * Allows overriding start proxy operation behavior. + * + * @param code The op code to start. + * @param attributionSource The permission identity of the caller. + * @param startIfModeDefault Whether to start the op of the mode is default. + * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected + * @param message The message in the async noted op + * @param shouldCollectMessage whether to collect messages + * @param skipProxyOperation Whether to skip the proxy portion of the operation + * @param superImpl The super implementation. + * @return The app op note result. + */ + int startProxyOperation(IBinder token, int code, + @NonNull AttributionSource attributionSource, boolean startIfModeDefault, + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, + boolean skipProxyOperation, @NonNull OctFunction<IBinder, Integer, + AttributionSource, Boolean, Boolean, String, Boolean, Boolean, + Integer> superImpl); + + /** + * Allows overriding finish proxy op. + * + * @param clientId Client state token. + * @param code The op code to finish. + * @param attributionSource The permission identity of the caller. + */ + void finishProxyOperation(IBinder clientId, int code, + @NonNull AttributionSource attributionSource, + @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl); } /** diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 03e95fc3b6b9..f8165e9a7cf6 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -60,6 +60,7 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.content.AttributionSource; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -224,8 +225,8 @@ class ContextImpl extends Context { private final String mBasePackageName; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final String mOpPackageName; - private final @NonNull ContextParams mParams; + private @NonNull AttributionSource mAttributionSource; private final @NonNull ResourcesManager mResourcesManager; @UnsupportedAppUsage @@ -467,13 +468,13 @@ class ContextImpl extends Context { /** @hide */ @Override public String getOpPackageName() { - return mOpPackageName != null ? mOpPackageName : getBasePackageName(); + return mAttributionSource.getPackageName(); } /** @hide */ @Override public @Nullable String getAttributionTag() { - return mParams.getAttributionTag(); + return mAttributionSource.getAttributionTag(); } @Override @@ -482,6 +483,11 @@ class ContextImpl extends Context { } @Override + public @NonNull AttributionSource getAttributionSource() { + return mAttributionSource; + } + + @Override public ApplicationInfo getApplicationInfo() { if (mPackageInfo != null) { return mPackageInfo.getApplicationInfo(); @@ -2074,13 +2080,7 @@ class ContextImpl extends Context { Log.v(TAG, "Treating renounced permission " + permission + " as denied"); return PERMISSION_DENIED; } - - try { - return ActivityManager.getService().checkPermissionWithToken( - permission, pid, uid, callerToken); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return checkPermission(permission, pid, uid); } @Override @@ -2415,8 +2415,10 @@ class ContextImpl extends Context { LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE); if (pi != null) { - ContextImpl c = new ContextImpl(this, mMainThread, pi, ContextParams.EMPTY, null, - mToken, new UserHandle(UserHandle.getUserId(application.uid)), + ContextImpl c = new ContextImpl(this, mMainThread, pi, ContextParams.EMPTY, + mAttributionSource.getAttributionTag(), + mAttributionSource.getNext(), + null, mToken, new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null); final int displayId = getDisplayId(); @@ -2446,15 +2448,19 @@ class ContextImpl extends Context { if (packageName.equals("system") || packageName.equals("android")) { // The system resources are loaded in every application, so we can safely copy // the context without reloading Resources. - return new ContextImpl(this, mMainThread, mPackageInfo, mParams, null, - mToken, user, flags, null, null); + return new ContextImpl(this, mMainThread, mPackageInfo, mParams, + mAttributionSource.getAttributionTag(), + mAttributionSource.getNext(), + null, mToken, user, flags, null, null); } LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier()); if (pi != null) { - ContextImpl c = new ContextImpl(this, mMainThread, pi, mParams, null, - mToken, user, flags, null, null); + ContextImpl c = new ContextImpl(this, mMainThread, pi, mParams, + mAttributionSource.getAttributionTag(), + mAttributionSource.getNext(), + null, mToken, user, flags, null, null); final int displayId = getDisplayId(); final Integer overrideDisplayId = mForceDisplayOverrideInResources @@ -2491,8 +2497,10 @@ class ContextImpl extends Context { final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName); final String[] paths = mPackageInfo.getSplitPaths(splitName); - final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, - mParams, splitName, mToken, mUser, mFlags, classLoader, null); + final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams, + mAttributionSource.getAttributionTag(), + mAttributionSource.getNext(), + splitName, mToken, mUser, mFlags, classLoader, null); context.setResources(ResourcesManager.getInstance().getResources( mToken, @@ -2526,6 +2534,8 @@ class ContextImpl extends Context { } ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams, + mAttributionSource.getAttributionTag(), + mAttributionSource.getNext(), mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = getDisplayId(); @@ -2544,6 +2554,8 @@ class ContextImpl extends Context { } ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams, + mAttributionSource.getAttributionTag(), + mAttributionSource.getNext(), mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = display.getDisplayId(); @@ -2650,6 +2662,8 @@ class ContextImpl extends Context { @UiContext ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) { ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams, + mAttributionSource.getAttributionTag(), + mAttributionSource.getNext(), mSplitName, token, mUser, mFlags, mClassLoader, null); // Window contexts receive configurations directly from the server and as such do not // need to override their display in ResourcesManager. @@ -2695,9 +2709,10 @@ class ContextImpl extends Context { @NonNull @Override - public Context createContext(@NonNull ContextParams params) { - return new ContextImpl(this, mMainThread, mPackageInfo, params, mSplitName, - mToken, mUser, mFlags, mClassLoader, null); + public Context createContext(@NonNull ContextParams contextParams) { + return new ContextImpl(this, mMainThread, mPackageInfo, contextParams, + contextParams.getAttributionTag(), contextParams.getNextAttributionSource(), + mSplitName, mToken, mUser, mFlags, mClassLoader, null); } @Override @@ -2710,16 +2725,20 @@ class ContextImpl extends Context { public Context createDeviceProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) | Context.CONTEXT_DEVICE_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mPackageInfo, mParams, mSplitName, - mToken, mUser, flags, mClassLoader, null); + return new ContextImpl(this, mMainThread, mPackageInfo, mParams, + mAttributionSource.getAttributionTag(), + mAttributionSource.getNext(), + mSplitName, mToken, mUser, flags, mClassLoader, null); } @Override public Context createCredentialProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE) | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mPackageInfo, mParams, mSplitName, - mToken, mUser, flags, mClassLoader, null); + return new ContextImpl(this, mMainThread, mPackageInfo, mParams, + mAttributionSource.getAttributionTag(), + mAttributionSource.getNext(), + mSplitName, mToken, mUser, flags, mClassLoader, null); } @Override @@ -2893,7 +2912,7 @@ class ContextImpl extends Context { static ContextImpl createSystemContext(ActivityThread mainThread) { LoadedApk packageInfo = new LoadedApk(mainThread); ContextImpl context = new ContextImpl(null, mainThread, packageInfo, - ContextParams.EMPTY, null, null, null, 0, null, null); + ContextParams.EMPTY, null, null, null, null, null, 0, null, null); context.setResources(packageInfo.getResources()); context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), context.mResourcesManager.getDisplayMetrics()); @@ -2911,7 +2930,7 @@ class ContextImpl extends Context { static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) { final LoadedApk packageInfo = systemContext.mPackageInfo; ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, - ContextParams.EMPTY, null, null, null, 0, null, null); + ContextParams.EMPTY, null, null, null, null, null, 0, null, null); context.setResources(createResources(null, packageInfo, null, displayId, null, packageInfo.getCompatibilityInfo(), null)); context.updateDisplay(displayId); @@ -2936,7 +2955,7 @@ class ContextImpl extends Context { String opPackageName) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); ContextImpl context = new ContextImpl(null, mainThread, packageInfo, - ContextParams.EMPTY, null, null, null, 0, null, opPackageName); + ContextParams.EMPTY, null, null, null, null, null, 0, null, opPackageName); context.setResources(packageInfo.getResources()); context.mContextType = isSystemOrSystemUI(context) ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI : CONTEXT_TYPE_NON_UI; @@ -2966,7 +2985,7 @@ class ContextImpl extends Context { } ContextImpl context = new ContextImpl(null, mainThread, packageInfo, ContextParams.EMPTY, - activityInfo.splitName, activityToken, null, 0, classLoader, null); + null, null, activityInfo.splitName, activityToken, null, 0, classLoader, null); context.mContextType = CONTEXT_TYPE_ACTIVITY; // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY. @@ -2999,6 +3018,7 @@ class ContextImpl extends Context { private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread, @NonNull LoadedApk packageInfo, @NonNull ContextParams params, + @Nullable String attributionTag, @Nullable AttributionSource nextAttributionSource, @Nullable String splitName, @Nullable IBinder token, @Nullable UserHandle user, int flags, @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName) { mOuterContext = this; @@ -3054,9 +3074,27 @@ class ContextImpl extends Context { mOpPackageName = overrideOpPackageName != null ? overrideOpPackageName : opPackageName; mParams = Objects.requireNonNull(params); + initializeAttributionSource(attributionTag, nextAttributionSource); mContentResolver = new ApplicationContentResolver(this, mainThread); } + private void initializeAttributionSource(@Nullable String attributionTag, + @Nullable AttributionSource nextAttributionSource) { + mAttributionSource = new AttributionSource(Process.myUid(), mOpPackageName, + attributionTag, nextAttributionSource); + // If we want to access protected data on behalf of another app we need to + // tell the OS that we opt in to participate in the attribution chain. + if (nextAttributionSource != null) { + // If an app happened to stub the internal OS for testing the registration method + // can return null. In this case we keep the current untrusted attribution source. + final AttributionSource attributionSource = getSystemService(PermissionManager.class) + .registerAttributionSource(mAttributionSource); + if (attributionSource != null) { + mAttributionSource = attributionSource; + } + } + } + void setResources(Resources r) { if (r instanceof CompatResources) { ((CompatResources) r).setContext(this); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 4c2433c04771..81e5e1d96294 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -475,8 +475,6 @@ interface IActivityManager { @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean isTopOfTask(in IBinder token); void bootAnimationComplete(); - int checkPermissionWithToken(in String permission, int pid, int uid, - in IBinder callerToken); @UnsupportedAppUsage void registerTaskStackListener(in ITaskStackListener listener); void unregisterTaskStackListener(in ITaskStackListener listener); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index a3d19ca6425c..0be7b732b4bd 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -745,7 +745,6 @@ public final class BluetoothAdapter { * Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance. */ BluetoothAdapter(IBluetoothManager managerService) { - if (managerService == null) { throw new IllegalArgumentException("bluetooth manager service is null"); } diff --git a/core/java/android/content/AttributionSource.aidl b/core/java/android/content/AttributionSource.aidl new file mode 100644 index 000000000000..10d5c274ae91 --- /dev/null +++ b/core/java/android/content/AttributionSource.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +parcelable AttributionSource; diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java new file mode 100644 index 000000000000..053bfc1a3253 --- /dev/null +++ b/core/java/android/content/AttributionSource.java @@ -0,0 +1,612 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.app.AppGlobals; +import android.os.Binder; +import android.os.Build; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +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.Objects; +import java.util.Set; + +/** + * This class represents a source to which access to permission protected data should be + * attributed. Attribution sources can be chained to represent cases where the protected + * data would flow through several applications. For example, app A may ask app B for + * contacts and in turn app B may ask app C for contacts. In this case, the attribution + * chain would be A -> B -> C and the data flow would be C -> B -> A. There are two + * main benefits of using the attribution source mechanism: avoid doing explicit permission + * checks on behalf of the calling app if you are accessing private data on their behalf + * to send back; avoid double data access blaming which happens as you check the calling + * app's permissions and when you access the data behind these permissions (for runtime + * permissions). Also if not explicitly blaming the caller the data access would be + * counted towards your app vs to the previous app where yours was just a proxy. + * <p> + * Every {@link Context} has an attribution source and you can get it via {@link + * Context#getAttributionSource()} representing itself, which is a chain of one. You + * can attribute work to another app, or more precisely to a chain of apps, through + * which the data you would be accessing would flow, via {@link Context#createContext( + * ContextParams)} plus specifying an attribution source for the next app to receive + * the protected data you are accessing via {@link AttributionSource.Builder#setNext( + * AttributionSource)}. Creating this attribution chain ensures that the datasource would + * check whether every app in the attribution chain has permission to access the data + * before releasing it. The datasource will also record appropriately that this data was + * accessed by the apps in the sequence if the data is behind a sensitive permission + * (e.g. dangerous). Again, this is useful if you are accessing the data on behalf of another + * app, for example a speech recognizer using the mic so it can provide recognition to + * a calling app. + * <p> + * You can create an attribution chain of you and any other app without any verification + * as this is something already available via the {@link android.app.AppOpsManager} APIs. + * 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. + * <p> + * You cannot forge an attribution chain without the participation of every app in the + * attribution chain (aside of the special case mentioned above). To create an attribution + * source that is trusted you need to create an attribution context that points to an + * attribution source that was explicitly created by the app that it refers to, recursively. + * <p> + * Since creating an attribution context leads to all permissions for apps in the attribution + * chain being checked, you need to expect getting a security exception when accessing + * 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 +// @DataClass(genEqualsHashCode = true, genConstructor = false, genBuilder = true) +public final class AttributionSource implements Parcelable { + /** + * @hide + */ + static class RenouncedPermissionsParcelling implements Parcelling<Set<String>> { + + @Override + public void parcel(Set<String> item, Parcel dest, int parcelFlags) { + if (item == null) { + dest.writeInt(-1); + } else { + dest.writeInt(item.size()); + for (String permission : item) { + dest.writeString8(permission); + } + } + } + + @Override + public Set<String> unparcel(Parcel source) { + final int size = source.readInt(); + if (size < 0) { + return null; + } + final ArraySet<String> result = new ArraySet<>(size); + for (int i = 0; i < size; i++) { + result.add(source.readString8()); + } + return result; + } + } + + /** + * The UID that is accessing the permission protected data. + */ + private final int mUid; + + /** + * The package that is accessing the permission protected data. + */ + private @Nullable String mPackageName = null; + + /** + * 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 + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) + @DataClass.ParcelWith(RenouncedPermissionsParcelling.class) + private @Nullable Set<String> mRenouncedPermissions = null; + + /** + * The next app to receive the permission protected data. + */ + private @Nullable AttributionSource mNext = null; + + /** @hide */ + @TestApi + public AttributionSource(int uid, @Nullable String packageName, + @Nullable String attributionTag) { + this(uid, packageName, attributionTag, /*next*/ null); + } + + /** @hide */ + @TestApi + public AttributionSource(int uid, @Nullable String packageName, + @Nullable String attributionTag, @Nullable AttributionSource next) { + this(uid, packageName, attributionTag, /*token*/ null, + /*renouncedPermissions*/ null, next); + } + + /** @hide */ + public AttributionSource(@NonNull AttributionSource current, + @Nullable AttributionSource next) { + this(current.getUid(), current.getPackageName(), current.getAttributionTag(), + /*token*/ null, /*renouncedPermissions*/ null, next); + } + + /** @hide */ + public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) { + return new AttributionSource(mUid, mPackageName, mAttributionTag, mToken, + mRenouncedPermissions, next); + } + + /** @hide */ + public AttributionSource withToken(@Nullable IBinder token) { + return new AttributionSource(mUid, mPackageName, mAttributionTag, token, + mRenouncedPermissions, mNext); + } + + /** + * If you are handling an IPC and you don't trust the caller you need to validate + * whether the attribution source is one for the calling app to prevent the caller + * to pass you a source from another app without including themselves in the + * attribution chain. + * + * @throws SecurityException if the attribution source cannot be trusted to be + * 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); + } + // No need to check package as app ops manager does it already. + } + + /** + * If you are handling an IPC and you don't trust the caller you need to validate + * whether the attribution source is one for the calling app to prevent the caller + * to pass you a source from another app without including themselves in the + * attribution chain. + *f + * @return if the attribution source cannot be trusted to be from the caller. + */ + public boolean checkCallingUid() { + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID && callingUid != mUid) { + return false; + } + // No need to check package as app ops manager does it already. + return true; + } + + @Override + public String toString() { + if (Build.IS_DEBUGGABLE) { + return "AttributionSource { " + + "uid = " + mUid + ", " + + "packageName = " + mPackageName + ", " + + "attributionTag = " + mAttributionTag + ", " + + "token = " + mToken + ", " + + "next = " + mNext + + " }"; + } + return super.toString(); + } + + /** + * @return The next UID that would receive the permission protected data. + * + * @hide + */ + public int getNextUid() { + if (mNext != null) { + return mNext.getUid(); + } + return Process.INVALID_UID; + } + + /** + * @return The next package that would receive the permission protected data. + * + * @hide + */ + public @Nullable String getNextPackageName() { + if (mNext != null) { + return mNext.getPackageName(); + } + return null; + } + + /** + * @return The nexxt package's attribution tag that would receive + * the permission protected data. + * + * @hide + */ + public @Nullable String getNextAttributionTag() { + if (mNext != null) { + return mNext.getAttributionTag(); + } + return null; + } + + /** + * Checks whether this attribution source can be trusted. That is whether + * the app it refers to created it and provided to the attribution chain. + * + * @param context Context handle. + * @return Whether this is a trusted source. + */ + public boolean isTrusted(@NonNull Context context) { + return mToken != null && context.getSystemService(PermissionManager.class) + .isRegisteredAttributionSource(this); + } + + /** + * Permissions that should be considered revoked regardless if granted. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) + @NonNull + public Set<String> 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<String> renouncedPermissions, + @Nullable AttributionSource next) { + this.mUid = uid; + this.mPackageName = packageName; + this.mAttributionTag = attributionTag; + this.mToken = token; + this.mRenouncedPermissions = renouncedPermissions; + com.android.internal.util.AnnotationValidations.validate( + SystemApi.class, null, mRenouncedPermissions); + com.android.internal.util.AnnotationValidations.validate( + RequiresPermission.class, null, mRenouncedPermissions, + "value", android.Manifest.permission.RENOUNCE_PERMISSIONS); + this.mNext = next; + + // onConstructed(); // You can define this method to get a callback + } + + /** + * The UID that is accessing the permission protected data. + */ + public int getUid() { + return mUid; + } + + /** + * The package that is accessing the permission protected data. + */ + public @Nullable String getPackageName() { + return mPackageName; + } + + /** + * The attribution tag of the app accessing the permission protected data. + */ + public @Nullable String getAttributionTag() { + return mAttributionTag; + } + + /** + * Unique token for that source. + * + * @hide + */ + public @Nullable IBinder getToken() { + return mToken; + } + + /** + * The next app to receive the permission protected data. + */ + public @Nullable AttributionSource getNext() { + return mNext; + } + + @Override + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(AttributionSource other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + AttributionSource that = (AttributionSource) o; + //noinspection PointlessBooleanExpression + return true + && mUid == that.mUid + && Objects.equals(mPackageName, that.mPackageName) + && Objects.equals(mAttributionTag, that.mAttributionTag) + && Objects.equals(mToken, that.mToken) + && Objects.equals(mRenouncedPermissions, that.mRenouncedPermissions) + && Objects.equals(mNext, that.mNext); + } + + @Override + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + mUid; + _hash = 31 * _hash + Objects.hashCode(mPackageName); + _hash = 31 * _hash + Objects.hashCode(mAttributionTag); + _hash = 31 * _hash + Objects.hashCode(mToken); + _hash = 31 * _hash + Objects.hashCode(mRenouncedPermissions); + _hash = 31 * _hash + Objects.hashCode(mNext); + return _hash; + } + + static Parcelling<Set<String>> sParcellingForRenouncedPermissions = + Parcelling.Cache.get( + RenouncedPermissionsParcelling.class); + static { + if (sParcellingForRenouncedPermissions == null) { + sParcellingForRenouncedPermissions = Parcelling.Cache.put( + new RenouncedPermissionsParcelling()); + } + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mPackageName != null) flg |= 0x2; + if (mAttributionTag != null) flg |= 0x4; + if (mToken != null) flg |= 0x8; + if (mRenouncedPermissions != null) flg |= 0x10; + if (mNext != null) flg |= 0x20; + dest.writeByte(flg); + dest.writeInt(mUid); + if (mPackageName != null) dest.writeString(mPackageName); + if (mAttributionTag != null) dest.writeString(mAttributionTag); + if (mToken != null) dest.writeStrongBinder(mToken); + sParcellingForRenouncedPermissions.parcel(mRenouncedPermissions, dest, flags); + if (mNext != null) dest.writeTypedObject(mNext, flags); + } + + @Override + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + /* package-private */ AttributionSource(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + int uid = in.readInt(); + String packageName = (flg & 0x2) == 0 ? null : in.readString(); + String attributionTag = (flg & 0x4) == 0 ? null : in.readString(); + IBinder token = (flg & 0x8) == 0 ? null : in.readStrongBinder(); + Set<String> renouncedPermissions = sParcellingForRenouncedPermissions.unparcel(in); + AttributionSource next = (flg & 0x20) == 0 ? null : (AttributionSource) in.readTypedObject(AttributionSource.CREATOR); + + this.mUid = uid; + this.mPackageName = packageName; + this.mAttributionTag = attributionTag; + this.mToken = token; + this.mRenouncedPermissions = renouncedPermissions; + com.android.internal.util.AnnotationValidations.validate( + SystemApi.class, null, mRenouncedPermissions); + com.android.internal.util.AnnotationValidations.validate( + RequiresPermission.class, null, mRenouncedPermissions, + "value", android.Manifest.permission.RENOUNCE_PERMISSIONS); + this.mNext = next; + + // onConstructed(); // You can define this method to get a callback + } + + public static final @NonNull Parcelable.Creator<AttributionSource> CREATOR + = new Parcelable.Creator<AttributionSource>() { + @Override + public AttributionSource[] newArray(int size) { + return new AttributionSource[size]; + } + + @Override + public AttributionSource createFromParcel(@NonNull Parcel in) { + return new AttributionSource(in); + } + }; + + /** + * 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 @SystemApi @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) @Nullable Set<String> mRenouncedPermissions; + private @Nullable AttributionSource mNext; + + private long mBuilderFieldsSet = 0L; + + /** + * Creates a new Builder. + * + * @param uid + * The UID that is accessing the permission protected data. + */ + public Builder( + int uid) { + mUid = uid; + } + + /** + * The package that is accessing the permission protected data. + */ + public @NonNull Builder setPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mPackageName = value; + return this; + } + + /** + * The attribution tag of the app accessing the permission protected data. + */ + public @NonNull Builder setAttributionTag(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mAttributionTag = value; + return this; + } + + /** + * Permissions that should be considered revoked regardless if granted. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) + public @NonNull Builder setRenouncedPermissions(@NonNull Set<String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mRenouncedPermissions = value; + return this; + } + + /** + * The next app to receive the permission protected data. + */ + public @NonNull Builder setNext(@NonNull AttributionSource value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mNext = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AttributionSource build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x40; // Mark builder used + + if ((mBuilderFieldsSet & 0x2) == 0) { + mPackageName = null; + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mAttributionTag = null; + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mToken = null; + } + if ((mBuilderFieldsSet & 0x10) == 0) { + mRenouncedPermissions = null; + } + if ((mBuilderFieldsSet & 0x20) == 0) { + mNext = null; + } + AttributionSource o = new AttributionSource( + mUid, + mPackageName, + mAttributionTag, + mToken, + mRenouncedPermissions, + mNext); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x40) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 73b4f62a23e4..82842039c310 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -18,11 +18,6 @@ package android.content; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; -import static android.app.AppOpsManager.MODE_ALLOWED; -import static android.app.AppOpsManager.MODE_DEFAULT; -import static android.app.AppOpsManager.MODE_ERRORED; -import static android.app.AppOpsManager.MODE_IGNORED; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Trace.TRACE_TAG_DATABASE; import android.annotation.NonNull; @@ -45,7 +40,6 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; -import android.os.IBinder; import android.os.ICancellationSignal; import android.os.ParcelFileDescriptor; import android.os.ParcelableException; @@ -57,7 +51,6 @@ import android.os.UserHandle; import android.os.storage.StorageManager; import android.text.TextUtils; import android.util.Log; -import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; @@ -141,7 +134,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall private boolean mNoPerms; private boolean mSingleUser; - private ThreadLocal<Pair<String, String>> mCallingPackage; + private ThreadLocal<AttributionSource> mCallingAttributionSource; private Transport mTransport = new Transport(); @@ -231,13 +224,13 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @Override - public Cursor query(String callingPkg, @Nullable String attributionTag, Uri uri, + public Cursor query(@NonNull AttributionSource attributionSource, Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) { uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); - if (enforceReadPermission(callingPkg, attributionTag, uri, null) - != AppOpsManager.MODE_ALLOWED) { + if (enforceReadPermission(attributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { // The caller has no access to the data, so return an empty cursor with // the columns in the requested order. The caller may ask for an invalid // column and we would not catch that but this is not a problem in practice. @@ -253,8 +246,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall // we have to execute the query as if allowed to get a cursor with the // columns. We then use the column names to return an empty cursor. Cursor cursor; - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { cursor = mInterface.query( uri, projection, queryArgs, @@ -262,7 +255,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); } if (cursor == null) { return null; @@ -272,8 +265,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall return new MatrixCursor(cursor.getColumnNames(), 0); } Trace.traceBegin(TRACE_TAG_DATABASE, "query"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return mInterface.query( uri, projection, queryArgs, @@ -281,7 +274,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @@ -314,60 +307,59 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @Override - public Uri insert(String callingPkg, @Nullable String attributionTag, Uri uri, + public Uri insert(@NonNull AttributionSource attributionSource, Uri uri, ContentValues initialValues, Bundle extras) { uri = validateIncomingUri(uri); int userId = getUserIdFromUri(uri); uri = maybeGetUriWithoutUserId(uri); - if (enforceWritePermission(callingPkg, attributionTag, uri, null) - != AppOpsManager.MODE_ALLOWED) { - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + if (enforceWritePermission(attributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return rejectInsert(uri, initialValues); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); } } Trace.traceBegin(TRACE_TAG_DATABASE, "insert"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return maybeAddUserId(mInterface.insert(uri, initialValues, extras), userId); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @Override - public int bulkInsert(String callingPkg, @Nullable String attributionTag, Uri uri, + public int bulkInsert(@NonNull AttributionSource attributionSource, Uri uri, ContentValues[] initialValues) { uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); - if (enforceWritePermission(callingPkg, attributionTag, uri, null) - != AppOpsManager.MODE_ALLOWED) { + if (enforceWritePermission(attributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { return 0; } Trace.traceBegin(TRACE_TAG_DATABASE, "bulkInsert"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return mInterface.bulkInsert(uri, initialValues); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @Override - public ContentProviderResult[] applyBatch(String callingPkg, - @Nullable String attributionTag, String authority, - ArrayList<ContentProviderOperation> operations) + public ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource, + String authority, ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { validateIncomingAuthority(authority); int numOperations = operations.size(); @@ -383,22 +375,24 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall operation = new ContentProviderOperation(operation, uri); operations.set(i, operation); } + final AttributionSource accessAttributionSource = + attributionSource; if (operation.isReadOperation()) { - if (enforceReadPermission(callingPkg, attributionTag, uri, null) - != AppOpsManager.MODE_ALLOWED) { + if (enforceReadPermission(accessAttributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { throw new OperationApplicationException("App op not allowed", 0); } } if (operation.isWriteOperation()) { - if (enforceWritePermission(callingPkg, attributionTag, uri, null) - != AppOpsManager.MODE_ALLOWED) { + if (enforceWritePermission(accessAttributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { throw new OperationApplicationException("App op not allowed", 0); } } } Trace.traceBegin(TRACE_TAG_DATABASE, "applyBatch"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { ContentProviderResult[] results = mInterface.applyBatch(authority, operations); @@ -414,111 +408,111 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @Override - public int delete(String callingPkg, @Nullable String attributionTag, Uri uri, + public int delete(@NonNull AttributionSource attributionSource, Uri uri, Bundle extras) { uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); - if (enforceWritePermission(callingPkg, attributionTag, uri, null) - != AppOpsManager.MODE_ALLOWED) { + if (enforceWritePermission(attributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { return 0; } Trace.traceBegin(TRACE_TAG_DATABASE, "delete"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return mInterface.delete(uri, extras); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @Override - public int update(String callingPkg, @Nullable String attributionTag, Uri uri, + public int update(@NonNull AttributionSource attributionSource, Uri uri, ContentValues values, Bundle extras) { uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); - if (enforceWritePermission(callingPkg, attributionTag, uri, null) - != AppOpsManager.MODE_ALLOWED) { + if (enforceWritePermission(attributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { return 0; } Trace.traceBegin(TRACE_TAG_DATABASE, "update"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return mInterface.update(uri, values, extras); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @Override - public ParcelFileDescriptor openFile(String callingPkg, @Nullable String attributionTag, - Uri uri, String mode, ICancellationSignal cancellationSignal, IBinder callerToken) + public ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource, + Uri uri, String mode, ICancellationSignal cancellationSignal) throws FileNotFoundException { uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); - enforceFilePermission(callingPkg, attributionTag, uri, mode, callerToken); + enforceFilePermission(attributionSource, uri, mode); Trace.traceBegin(TRACE_TAG_DATABASE, "openFile"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return mInterface.openFile( uri, mode, CancellationSignal.fromTransport(cancellationSignal)); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @Override - public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String attributionTag, + public AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource, Uri uri, String mode, ICancellationSignal cancellationSignal) throws FileNotFoundException { uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); - enforceFilePermission(callingPkg, attributionTag, uri, mode, null); + enforceFilePermission(attributionSource, uri, mode); Trace.traceBegin(TRACE_TAG_DATABASE, "openAssetFile"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return mInterface.openAssetFile( uri, mode, CancellationSignal.fromTransport(cancellationSignal)); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @Override - public Bundle call(String callingPkg, @Nullable String attributionTag, String authority, + public Bundle call(@NonNull AttributionSource attributionSource, String authority, String method, @Nullable String arg, @Nullable Bundle extras) { validateIncomingAuthority(authority); Bundle.setDefusable(extras, true); Trace.traceBegin(TRACE_TAG_DATABASE, "call"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return mInterface.call(authority, method, arg, extras); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @@ -539,23 +533,23 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @Override - public AssetFileDescriptor openTypedAssetFile(String callingPkg, - @Nullable String attributionTag, Uri uri, String mimeType, Bundle opts, - ICancellationSignal cancellationSignal) throws FileNotFoundException { + public AssetFileDescriptor openTypedAssetFile( + @NonNull AttributionSource attributionSource, Uri uri, String mimeType, + Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException { Bundle.setDefusable(opts, true); uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); - enforceFilePermission(callingPkg, attributionTag, uri, "r", null); + enforceFilePermission(attributionSource, uri, "r"); Trace.traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return mInterface.openTypedAssetFile( uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal)); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @@ -566,34 +560,34 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @Override - public Uri canonicalize(String callingPkg, @Nullable String attributionTag, Uri uri) { + public Uri canonicalize(@NonNull AttributionSource attributionSource, Uri uri) { uri = validateIncomingUri(uri); int userId = getUserIdFromUri(uri); uri = getUriWithoutUserId(uri); - if (enforceReadPermission(callingPkg, attributionTag, uri, null) - != AppOpsManager.MODE_ALLOWED) { + if (enforceReadPermission(attributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { return null; } Trace.traceBegin(TRACE_TAG_DATABASE, "canonicalize"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return maybeAddUserId(mInterface.canonicalize(uri), userId); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @Override - public void canonicalizeAsync(String callingPkg, @Nullable String attributionTag, Uri uri, + public void canonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri, RemoteCallback callback) { final Bundle result = new Bundle(); try { result.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT, - canonicalize(callingPkg, attributionTag, uri)); + canonicalize(attributionSource, uri)); } catch (Exception e) { result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR, new ParcelableException(e)); @@ -602,34 +596,34 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @Override - public Uri uncanonicalize(String callingPkg, String attributionTag, Uri uri) { + public Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri uri) { uri = validateIncomingUri(uri); int userId = getUserIdFromUri(uri); uri = getUriWithoutUserId(uri); - if (enforceReadPermission(callingPkg, attributionTag, uri, null) - != AppOpsManager.MODE_ALLOWED) { + if (enforceReadPermission(attributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { return null; } Trace.traceBegin(TRACE_TAG_DATABASE, "uncanonicalize"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return maybeAddUserId(mInterface.uncanonicalize(uri), userId); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @Override - public void uncanonicalizeAsync(String callingPkg, @Nullable String attributionTag, Uri uri, + public void uncanonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri, RemoteCallback callback) { final Bundle result = new Bundle(); try { result.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT, - uncanonicalize(callingPkg, attributionTag, uri)); + uncanonicalize(attributionSource, uri)); } catch (Exception e) { result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR, new ParcelableException(e)); @@ -638,92 +632,95 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @Override - public boolean refresh(String callingPkg, String attributionTag, Uri uri, Bundle extras, - ICancellationSignal cancellationSignal) throws RemoteException { + public boolean refresh(@NonNull AttributionSource attributionSource, Uri uri, + Bundle extras, ICancellationSignal cancellationSignal) throws RemoteException { uri = validateIncomingUri(uri); uri = getUriWithoutUserId(uri); - if (enforceReadPermission(callingPkg, attributionTag, uri, null) - != AppOpsManager.MODE_ALLOWED) { + if (enforceReadPermission(attributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { return false; } Trace.traceBegin(TRACE_TAG_DATABASE, "refresh"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return mInterface.refresh(uri, extras, CancellationSignal.fromTransport(cancellationSignal)); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } @Override - public int checkUriPermission(String callingPkg, @Nullable String attributionTag, Uri uri, + public int checkUriPermission(@NonNull AttributionSource attributionSource, Uri uri, int uid, int modeFlags) { uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); Trace.traceBegin(TRACE_TAG_DATABASE, "checkUriPermission"); - final Pair<String, String> original = setCallingPackage( - new Pair<>(callingPkg, attributionTag)); + final AttributionSource original = setCallingAttributionSource( + attributionSource); try { return mInterface.checkUriPermission(uri, uid, modeFlags); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { - setCallingPackage(original); + setCallingAttributionSource(original); Trace.traceEnd(TRACE_TAG_DATABASE); } } - private void enforceFilePermission(String callingPkg, @Nullable String attributionTag, - Uri uri, String mode, IBinder callerToken) + @PermissionChecker.PermissionResult + private void enforceFilePermission(@NonNull AttributionSource attributionSource, + Uri uri, String mode) throws FileNotFoundException, SecurityException { if (mode != null && mode.indexOf('w') != -1) { - if (enforceWritePermission(callingPkg, attributionTag, uri, callerToken) - != AppOpsManager.MODE_ALLOWED) { + if (enforceWritePermission(attributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { throw new FileNotFoundException("App op not allowed"); } } else { - if (enforceReadPermission(callingPkg, attributionTag, uri, callerToken) - != AppOpsManager.MODE_ALLOWED) { + if (enforceReadPermission(attributionSource, uri) + != PermissionChecker.PERMISSION_GRANTED) { throw new FileNotFoundException("App op not allowed"); } } } - private int enforceReadPermission(String callingPkg, @Nullable String attributionTag, - Uri uri, IBinder callerToken) + @PermissionChecker.PermissionResult + private int enforceReadPermission(@NonNull AttributionSource attributionSource, Uri uri) throws SecurityException { - final int mode = enforceReadPermissionInner(uri, callingPkg, attributionTag, - callerToken); - if (mode != MODE_ALLOWED) { - return mode; + final int result = enforceReadPermissionInner(uri, attributionSource); + if (result != PermissionChecker.PERMISSION_GRANTED) { + return result; } - - return noteProxyOp(callingPkg, attributionTag, mReadOp); + // Only check the read op if it differs from the one for the permission + // we already checked above to avoid double attribution for every access. + if (mTransport.mReadOp != AppOpsManager.OP_NONE + && mTransport.mReadOp != AppOpsManager.permissionToOpCode(mReadPermission)) { + return PermissionChecker.checkOpForDataDelivery(getContext(), + AppOpsManager.opToPublicName(mTransport.mReadOp), + attributionSource, /*message*/ null); + } + return PermissionChecker.PERMISSION_GRANTED; } - private int enforceWritePermission(String callingPkg, String attributionTag, Uri uri, - IBinder callerToken) + @PermissionChecker.PermissionResult + private int enforceWritePermission(@NonNull AttributionSource attributionSource, Uri uri) throws SecurityException { - final int mode = enforceWritePermissionInner(uri, callingPkg, attributionTag, - callerToken); - if (mode != MODE_ALLOWED) { - return mode; + final int result = enforceWritePermissionInner(uri, attributionSource); + if (result != PermissionChecker.PERMISSION_GRANTED) { + return result; } - - return noteProxyOp(callingPkg, attributionTag, mWriteOp); - } - - private int noteProxyOp(String callingPkg, String attributionTag, int op) { - if (op != AppOpsManager.OP_NONE) { - int mode = mAppOpsManager.noteProxyOp(op, callingPkg, Binder.getCallingUid(), - attributionTag, null); - return mode == MODE_DEFAULT ? MODE_IGNORED : mode; + // Only check the write op if it differs from the one for the permission + // we already checked above to avoid double attribution for every access. + if (mTransport.mWriteOp != AppOpsManager.OP_NONE + && mTransport.mWriteOp != AppOpsManager.permissionToOpCode(mWritePermission)) { + return PermissionChecker.checkOpForDataDelivery(getContext(), + AppOpsManager.opToPublicName(mTransport.mWriteOp), + attributionSource, /*message*/ null); } - - return AppOpsManager.MODE_ALLOWED; + return PermissionChecker.PERMISSION_GRANTED; } } @@ -731,49 +728,53 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall if (UserHandle.getUserId(uid) == context.getUserId() || mSingleUser) { return true; } - return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) == PERMISSION_GRANTED + return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) + == PackageManager.PERMISSION_GRANTED || context.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid) - == PERMISSION_GRANTED; + == PackageManager.PERMISSION_GRANTED; } /** * Verify that calling app holds both the given permission and any app-op * associated with that permission. */ - private int checkPermissionAndAppOp(String permission, String callingPkg, - @Nullable String attributionTag, IBinder callerToken) { - if (getContext().checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid(), - callerToken) != PERMISSION_GRANTED) { - return MODE_ERRORED; + @PermissionChecker.PermissionResult + private int checkPermission(String permission, + @NonNull AttributionSource attributionSource) { + if (Binder.getCallingPid() == Process.myPid()) { + return PermissionChecker.PERMISSION_GRANTED; } - - return mTransport.noteProxyOp(callingPkg, attributionTag, - AppOpsManager.permissionToOpCode(permission)); + if (!attributionSource.checkCallingUid()) { + return PermissionChecker.PERMISSION_HARD_DENIED; + } + return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(getContext(), + permission, -1, new AttributionSource(getContext().getAttributionSource(), + attributionSource), /*message*/ null); } /** {@hide} */ - protected int enforceReadPermissionInner(Uri uri, String callingPkg, - @Nullable String attributionTag, IBinder callerToken) throws SecurityException { + @PermissionChecker.PermissionResult + protected int enforceReadPermissionInner(Uri uri, + @NonNull AttributionSource attributionSource) throws SecurityException { final Context context = getContext(); final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); String missingPerm = null; - int strongestMode = MODE_ALLOWED; + int strongestResult = PermissionChecker.PERMISSION_GRANTED; if (UserHandle.isSameApp(uid, mMyUid)) { - return MODE_ALLOWED; + return PermissionChecker.PERMISSION_GRANTED; } if (mExported && checkUser(pid, uid, context)) { final String componentPerm = getReadPermission(); if (componentPerm != null) { - final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, attributionTag, - callerToken); - if (mode == MODE_ALLOWED) { - return MODE_ALLOWED; + final int result = checkPermission(componentPerm, attributionSource); + if (result == PermissionChecker.PERMISSION_GRANTED) { + return PermissionChecker.PERMISSION_GRANTED; } else { missingPerm = componentPerm; - strongestMode = Math.max(strongestMode, mode); + strongestResult = Math.max(strongestResult, result); } } @@ -787,16 +788,15 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall for (PathPermission pp : pps) { final String pathPerm = pp.getReadPermission(); if (pathPerm != null && pp.match(path)) { - final int mode = checkPermissionAndAppOp(pathPerm, callingPkg, - attributionTag, callerToken); - if (mode == MODE_ALLOWED) { - return MODE_ALLOWED; + final int result = checkPermission(pathPerm, attributionSource); + if (result == PermissionChecker.PERMISSION_GRANTED) { + return PermissionChecker.PERMISSION_GRANTED; } else { // any denied <path-permission> means we lose // default <provider> access. allowDefaultRead = false; missingPerm = pathPerm; - strongestMode = Math.max(strongestMode, mode); + strongestResult = Math.max(strongestResult, result); } } } @@ -804,22 +804,22 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall // if we passed <path-permission> checks above, and no default // <provider> permission, then allow access. - if (allowDefaultRead) return MODE_ALLOWED; + if (allowDefaultRead) return PermissionChecker.PERMISSION_GRANTED; } // last chance, check against any uri grants final int callingUserId = UserHandle.getUserId(uid); final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid)) ? maybeAddUserId(uri, callingUserId) : uri; - if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION, - callerToken) == PERMISSION_GRANTED) { - return MODE_ALLOWED; + if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) + == PackageManager.PERMISSION_GRANTED) { + return PermissionChecker.PERMISSION_GRANTED; } // If the worst denial we found above was ignored, then pass that // ignored through; otherwise we assume it should be a real error below. - if (strongestMode == MODE_IGNORED) { - return MODE_IGNORED; + if (strongestResult == PermissionChecker.PERMISSION_SOFT_DENIED) { + return PermissionChecker.PERMISSION_SOFT_DENIED; } final String suffix; @@ -836,28 +836,28 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } /** {@hide} */ - protected int enforceWritePermissionInner(Uri uri, String callingPkg, - @Nullable String attributionTag, IBinder callerToken) throws SecurityException { + @PermissionChecker.PermissionResult + protected int enforceWritePermissionInner(Uri uri, + @NonNull AttributionSource attributionSource) throws SecurityException { final Context context = getContext(); final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); String missingPerm = null; - int strongestMode = MODE_ALLOWED; + int strongestResult = PermissionChecker.PERMISSION_GRANTED; if (UserHandle.isSameApp(uid, mMyUid)) { - return MODE_ALLOWED; + return PermissionChecker.PERMISSION_GRANTED; } if (mExported && checkUser(pid, uid, context)) { final String componentPerm = getWritePermission(); if (componentPerm != null) { - final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, - attributionTag, callerToken); - if (mode == MODE_ALLOWED) { - return MODE_ALLOWED; + final int mode = checkPermission(componentPerm, attributionSource); + if (mode == PermissionChecker.PERMISSION_GRANTED) { + return PermissionChecker.PERMISSION_GRANTED; } else { missingPerm = componentPerm; - strongestMode = Math.max(strongestMode, mode); + strongestResult = Math.max(strongestResult, mode); } } @@ -871,16 +871,15 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall for (PathPermission pp : pps) { final String pathPerm = pp.getWritePermission(); if (pathPerm != null && pp.match(path)) { - final int mode = checkPermissionAndAppOp(pathPerm, callingPkg, - attributionTag, callerToken); - if (mode == MODE_ALLOWED) { - return MODE_ALLOWED; + final int mode = checkPermission(pathPerm, attributionSource); + if (mode == PermissionChecker.PERMISSION_GRANTED) { + return PermissionChecker.PERMISSION_GRANTED; } else { // any denied <path-permission> means we lose // default <provider> access. allowDefaultWrite = false; missingPerm = pathPerm; - strongestMode = Math.max(strongestMode, mode); + strongestResult = Math.max(strongestResult, mode); } } } @@ -888,19 +887,19 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall // if we passed <path-permission> checks above, and no default // <provider> permission, then allow access. - if (allowDefaultWrite) return MODE_ALLOWED; + if (allowDefaultWrite) return PermissionChecker.PERMISSION_GRANTED; } // last chance, check against any uri grants - if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, - callerToken) == PERMISSION_GRANTED) { - return MODE_ALLOWED; + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + == PackageManager.PERMISSION_GRANTED) { + return PermissionChecker.PERMISSION_GRANTED; } // If the worst denial we found above was ignored, then pass that // ignored through; otherwise we assume it should be a real error below. - if (strongestMode == MODE_IGNORED) { - return MODE_IGNORED; + if (strongestResult == PermissionChecker.PERMISSION_SOFT_DENIED) { + return PermissionChecker.PERMISSION_SOFT_DENIED; } final String failReason = mExported @@ -941,9 +940,10 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * Set the calling package/feature, returning the current value (or {@code null}) * which can be used later to restore the previous state. */ - private Pair<String, String> setCallingPackage(Pair<String, String> callingPackage) { - final Pair<String, String> original = mCallingPackage.get(); - mCallingPackage.set(callingPackage); + private @Nullable AttributionSource setCallingAttributionSource( + @Nullable AttributionSource attributionSource) { + final AttributionSource original = mCallingAttributionSource.get(); + mCallingAttributionSource.set(attributionSource); onCallingPackageChanged(); return original; } @@ -963,13 +963,30 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * calling UID. */ public final @Nullable String getCallingPackage() { - final Pair<String, String> pkg = mCallingPackage.get(); - if (pkg != null) { - mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg.first); - return pkg.first; - } + final AttributionSource callingAttributionSource = getCallingAttributionSource(); + return (callingAttributionSource != null) + ? callingAttributionSource.getPackageName() : null; + } - return null; + /** + * Gets the attribution source of the calling app. If you want to attribute + * the data access to the calling app you can create an attribution context + * via {@link android.content.Context#createContext(ContextParams)} and passing + * this identity to {@link ContextParams.Builder#setNextAttributionSource( + * AttributionSource)}. + * + * @return The identity of the caller for permission purposes. + * + * @see ContextParams.Builder#setNextAttributionSource(AttributionSource) + * @see AttributionSource + */ + public final @Nullable AttributionSource getCallingAttributionSource() { + final AttributionSource attributionSource = mCallingAttributionSource.get(); + if (attributionSource != null) { + mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), + attributionSource.getPackageName()); + } + return attributionSource; } /** @@ -983,11 +1000,10 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * @see #getCallingPackage */ public final @Nullable String getCallingAttributionTag() { - final Pair<String, String> pkg = mCallingPackage.get(); - if (pkg != null) { - return pkg.second; + final AttributionSource attributionSource = mCallingAttributionSource.get(); + if (attributionSource != null) { + return attributionSource.getAttributionTag(); } - return null; } @@ -1012,11 +1028,10 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * @see Context#grantUriPermission(String, Uri, int) */ public final @Nullable String getCallingPackageUnchecked() { - final Pair<String, String> pkg = mCallingPackage.get(); - if (pkg != null) { - return pkg.first; + final AttributionSource attributionSource = mCallingAttributionSource.get(); + if (attributionSource != null) { + return attributionSource.getPackageName(); } - return null; } @@ -1038,12 +1053,12 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall /** {@hide} */ public final long binderToken; /** {@hide} */ - public final Pair<String, String> callingPackage; + public final @Nullable AttributionSource callingAttributionSource; /** {@hide} */ - public CallingIdentity(long binderToken, Pair<String, String> callingPackage) { + public CallingIdentity(long binderToken, @Nullable AttributionSource attributionSource) { this.binderToken = binderToken; - this.callingPackage = callingPackage; + this.callingAttributionSource = attributionSource; } } @@ -1059,7 +1074,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall */ @SuppressWarnings("AndroidFrameworkBinderIdentity") public final @NonNull CallingIdentity clearCallingIdentity() { - return new CallingIdentity(Binder.clearCallingIdentity(), setCallingPackage(null)); + return new CallingIdentity(Binder.clearCallingIdentity(), + setCallingAttributionSource(null)); } /** @@ -1071,7 +1087,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall */ public final void restoreCallingIdentity(@NonNull CallingIdentity identity) { Binder.restoreCallingIdentity(identity.binderToken); - mCallingPackage.set(identity.callingPackage); + mCallingAttributionSource.set(identity.callingAttributionSource); } /** @@ -2374,7 +2390,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall private void attachInfo(Context context, ProviderInfo info, boolean testing) { mNoPerms = testing; - mCallingPackage = new ThreadLocal<>(); + mCallingAttributionSource = new ThreadLocal<>(); /* * Only allow it to be set once, so after the content service gives diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 5af7861e1a20..518e7534d512 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -79,7 +79,8 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { private final IContentProvider mContentProvider; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final String mPackageName; - private final @Nullable String mAttributionTag; + private final @NonNull AttributionSource mAttributionSource; + private final String mAuthority; private final boolean mStable; @@ -103,7 +104,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { mContentResolver = contentResolver; mContentProvider = contentProvider; mPackageName = contentResolver.mPackageName; - mAttributionTag = contentResolver.mAttributionTag; + mAttributionSource = contentResolver.getAttributionSource(); mAuthority = authority; mStable = stable; @@ -193,7 +194,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { cancellationSignal.setRemote(remoteCancellationSignal); } final Cursor cursor = mContentProvider.query( - mPackageName, mAttributionTag, uri, projection, queryArgs, + mAttributionSource, uri, projection, queryArgs, remoteCancellationSignal); if (cursor == null) { return null; @@ -254,7 +255,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { beforeRemote(); try { - return mContentProvider.canonicalize(mPackageName, mAttributionTag, url); + return mContentProvider.canonicalize(mAttributionSource, url); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -272,7 +273,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { beforeRemote(); try { - return mContentProvider.uncanonicalize(mPackageName, mAttributionTag, url); + return mContentProvider.uncanonicalize(mAttributionSource, url); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -297,7 +298,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { remoteCancellationSignal = mContentProvider.createCancellationSignal(); cancellationSignal.setRemote(remoteCancellationSignal); } - return mContentProvider.refresh(mPackageName, mAttributionTag, url, extras, + return mContentProvider.refresh(mAttributionSource, url, extras, remoteCancellationSignal); } catch (DeadObjectException e) { if (!mStable) { @@ -317,7 +318,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { beforeRemote(); try { - return mContentProvider.checkUriPermission(mPackageName, mAttributionTag, uri, uid, + return mContentProvider.checkUriPermission(mAttributionSource, uri, uid, modeFlags); } catch (DeadObjectException e) { if (!mStable) { @@ -343,7 +344,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { beforeRemote(); try { - return mContentProvider.insert(mPackageName, mAttributionTag, url, initialValues, + return mContentProvider.insert(mAttributionSource, url, initialValues, extras); } catch (DeadObjectException e) { if (!mStable) { @@ -364,7 +365,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { beforeRemote(); try { - return mContentProvider.bulkInsert(mPackageName, mAttributionTag, url, initialValues); + return mContentProvider.bulkInsert(mAttributionSource, url, initialValues); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -388,7 +389,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { beforeRemote(); try { - return mContentProvider.delete(mPackageName, mAttributionTag, url, extras); + return mContentProvider.delete(mAttributionSource, url, extras); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -413,7 +414,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { beforeRemote(); try { - return mContentProvider.update(mPackageName, mAttributionTag, url, values, extras); + return mContentProvider.update(mAttributionSource, url, values, extras); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -457,8 +458,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { remoteSignal = mContentProvider.createCancellationSignal(); signal.setRemote(remoteSignal); } - return mContentProvider.openFile(mPackageName, mAttributionTag, url, mode, - remoteSignal, null); + return mContentProvider.openFile(mAttributionSource, url, mode, remoteSignal); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -502,7 +502,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { remoteSignal = mContentProvider.createCancellationSignal(); signal.setRemote(remoteSignal); } - return mContentProvider.openAssetFile(mPackageName, mAttributionTag, url, mode, + return mContentProvider.openAssetFile(mAttributionSource, url, mode, remoteSignal); } catch (DeadObjectException e) { if (!mStable) { @@ -544,7 +544,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { signal.setRemote(remoteSignal); } return mContentProvider.openTypedAssetFile( - mPackageName, mAttributionTag, uri, mimeTypeFilter, opts, remoteSignal); + mAttributionSource, uri, mimeTypeFilter, opts, remoteSignal); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -571,7 +571,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { beforeRemote(); try { - return mContentProvider.applyBatch(mPackageName, mAttributionTag, authority, + return mContentProvider.applyBatch(mAttributionSource, authority, operations); } catch (DeadObjectException e) { if (!mStable) { @@ -598,7 +598,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { beforeRemote(); try { - return mContentProvider.call(mPackageName, mAttributionTag, authority, method, arg, + return mContentProvider.call(mAttributionSource, authority, method, arg, extras); } catch (DeadObjectException e) { if (!mStable) { diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index 7d121d56c86d..47c966990861 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -16,6 +16,7 @@ package android.content; +import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetFileDescriptor; @@ -83,8 +84,8 @@ abstract public class ContentProviderNative extends Binder implements IContentPr { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String callingFeatureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); // String[] projection @@ -103,7 +104,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface( data.readStrongBinder()); - Cursor cursor = query(callingPkg, callingFeatureId, url, projection, queryArgs, + Cursor cursor = query(attributionSource, url, projection, queryArgs, cancellationSignal); if (cursor != null) { CursorToBulkCursorAdaptor adaptor = null; @@ -158,13 +159,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case INSERT_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); ContentValues values = ContentValues.CREATOR.createFromParcel(data); Bundle extras = data.readBundle(); - Uri out = insert(callingPkg, featureId, url, values, extras); + Uri out = insert(attributionSource, url, values, extras); reply.writeNoException(); Uri.writeToParcel(reply, out); return true; @@ -173,12 +174,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case BULK_INSERT_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); ContentValues[] values = data.createTypedArray(ContentValues.CREATOR); - int count = bulkInsert(callingPkg, featureId, url, values); + int count = bulkInsert(attributionSource, url, values); reply.writeNoException(); reply.writeInt(count); return true; @@ -187,8 +188,8 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case APPLY_BATCH_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); String authority = data.readString(); final int numOperations = data.readInt(); final ArrayList<ContentProviderOperation> operations = @@ -196,7 +197,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr for (int i = 0; i < numOperations; i++) { operations.add(i, ContentProviderOperation.CREATOR.createFromParcel(data)); } - final ContentProviderResult[] results = applyBatch(callingPkg, featureId, + final ContentProviderResult[] results = applyBatch(attributionSource, authority, operations); reply.writeNoException(); reply.writeTypedArray(results, 0); @@ -206,12 +207,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case DELETE_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); Bundle extras = data.readBundle(); - int count = delete(callingPkg, featureId, url, extras); + int count = delete(attributionSource, url, extras); reply.writeNoException(); reply.writeInt(count); @@ -221,13 +222,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case UPDATE_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); ContentValues values = ContentValues.CREATOR.createFromParcel(data); Bundle extras = data.readBundle(); - int count = update(callingPkg, featureId, url, values, extras); + int count = update(attributionSource, url, values, extras); reply.writeNoException(); reply.writeInt(count); @@ -237,16 +238,15 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case OPEN_FILE_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); String mode = data.readString(); ICancellationSignal signal = ICancellationSignal.Stub.asInterface( data.readStrongBinder()); - IBinder callerToken = data.readStrongBinder(); ParcelFileDescriptor fd; - fd = openFile(callingPkg, featureId, url, mode, signal, callerToken); + fd = openFile(attributionSource, url, mode, signal); reply.writeNoException(); if (fd != null) { reply.writeInt(1); @@ -261,15 +261,15 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case OPEN_ASSET_FILE_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); String mode = data.readString(); ICancellationSignal signal = ICancellationSignal.Stub.asInterface( data.readStrongBinder()); AssetFileDescriptor fd; - fd = openAssetFile(callingPkg, featureId, url, mode, signal); + fd = openAssetFile(attributionSource, url, mode, signal); reply.writeNoException(); if (fd != null) { reply.writeInt(1); @@ -285,14 +285,14 @@ abstract public class ContentProviderNative extends Binder implements IContentPr { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); String authority = data.readString(); String method = data.readString(); String stringArg = data.readString(); Bundle extras = data.readBundle(); - Bundle responseBundle = call(callingPkg, featureId, authority, method, + Bundle responseBundle = call(attributionSource, authority, method, stringArg, extras); reply.writeNoException(); @@ -315,8 +315,8 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case OPEN_TYPED_ASSET_FILE_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); String mimeType = data.readString(); Bundle opts = data.readBundle(); @@ -324,7 +324,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr data.readStrongBinder()); AssetFileDescriptor fd; - fd = openTypedAssetFile(callingPkg, featureId, url, mimeType, opts, signal); + fd = openTypedAssetFile(attributionSource, url, mimeType, opts, signal); reply.writeNoException(); if (fd != null) { reply.writeInt(1); @@ -349,11 +349,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case CANONICALIZE_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); - Uri out = canonicalize(callingPkg, featureId, url); + Uri out = canonicalize(attributionSource, url); reply.writeNoException(); Uri.writeToParcel(reply, out); return true; @@ -361,22 +361,22 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case CANONICALIZE_ASYNC_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri uri = Uri.CREATOR.createFromParcel(data); RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data); - canonicalizeAsync(callingPkg, featureId, uri, callback); + canonicalizeAsync(attributionSource, uri, callback); return true; } case UNCANONICALIZE_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); - Uri out = uncanonicalize(callingPkg, featureId, url); + Uri out = uncanonicalize(attributionSource, url); reply.writeNoException(); Uri.writeToParcel(reply, out); return true; @@ -384,24 +384,24 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case UNCANONICALIZE_ASYNC_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri uri = Uri.CREATOR.createFromParcel(data); RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data); - uncanonicalizeAsync(callingPkg, featureId, uri, callback); + uncanonicalizeAsync(attributionSource, uri, callback); return true; } case REFRESH_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri url = Uri.CREATOR.createFromParcel(data); Bundle extras = data.readBundle(); ICancellationSignal signal = ICancellationSignal.Stub.asInterface( data.readStrongBinder()); - boolean out = refresh(callingPkg, featureId, url, extras, signal); + boolean out = refresh(attributionSource, url, extras, signal); reply.writeNoException(); reply.writeInt(out ? 0 : -1); return true; @@ -409,13 +409,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr case CHECK_URI_PERMISSION_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); - String callingPkg = data.readString(); - String featureId = data.readString(); + AttributionSource attributionSource = AttributionSource.CREATOR + .createFromParcel(data); Uri uri = Uri.CREATOR.createFromParcel(data); int uid = data.readInt(); int modeFlags = data.readInt(); - int out = checkUriPermission(callingPkg, featureId, uri, uid, modeFlags); + int out = checkUriPermission(attributionSource, uri, uid, modeFlags); reply.writeNoException(); reply.writeInt(out); return true; @@ -451,7 +451,7 @@ final class ContentProviderProxy implements IContentProvider } @Override - public Cursor query(String callingPkg, @Nullable String featureId, Uri url, + public Cursor query(@NonNull AttributionSource attributionSource, Uri url, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) throws RemoteException { @@ -461,8 +461,7 @@ final class ContentProviderProxy implements IContentProvider try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); int length = 0; if (projection != null) { @@ -540,7 +539,7 @@ final class ContentProviderProxy implements IContentProvider } @Override - public Uri insert(String callingPkg, @Nullable String featureId, Uri url, + public Uri insert(@NonNull AttributionSource attributionSource, Uri url, ContentValues values, Bundle extras) throws RemoteException { Parcel data = Parcel.obtain(); @@ -548,8 +547,7 @@ final class ContentProviderProxy implements IContentProvider try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); values.writeToParcel(data, 0); data.writeBundle(extras); @@ -566,15 +564,14 @@ final class ContentProviderProxy implements IContentProvider } @Override - public int bulkInsert(String callingPkg, @Nullable String featureId, Uri url, + public int bulkInsert(@NonNull AttributionSource attributionSource, Uri url, ContentValues[] values) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); data.writeTypedArray(values, 0); @@ -590,15 +587,14 @@ final class ContentProviderProxy implements IContentProvider } @Override - public ContentProviderResult[] applyBatch(String callingPkg, @Nullable String featureId, + public ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource, String authority, ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); data.writeString(authority); data.writeInt(operations.size()); for (ContentProviderOperation operation : operations) { @@ -617,15 +613,14 @@ final class ContentProviderProxy implements IContentProvider } @Override - public int delete(String callingPkg, @Nullable String featureId, Uri url, Bundle extras) + public int delete(@NonNull AttributionSource attributionSource, Uri url, Bundle extras) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); data.writeBundle(extras); @@ -641,15 +636,14 @@ final class ContentProviderProxy implements IContentProvider } @Override - public int update(String callingPkg, @Nullable String featureId, Uri url, + public int update(@NonNull AttributionSource attributionSource, Uri url, ContentValues values, Bundle extras) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); values.writeToParcel(data, 0); data.writeBundle(extras); @@ -666,20 +660,18 @@ final class ContentProviderProxy implements IContentProvider } @Override - public ParcelFileDescriptor openFile(String callingPkg, @Nullable String featureId, Uri url, - String mode, ICancellationSignal signal, IBinder token) + public ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource, Uri url, + String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); data.writeString(mode); data.writeStrongBinder(signal != null ? signal.asBinder() : null); - data.writeStrongBinder(token); mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0); @@ -695,7 +687,7 @@ final class ContentProviderProxy implements IContentProvider } @Override - public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String featureId, + public AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); @@ -703,8 +695,7 @@ final class ContentProviderProxy implements IContentProvider try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); data.writeString(mode); data.writeStrongBinder(signal != null ? signal.asBinder() : null); @@ -723,15 +714,14 @@ final class ContentProviderProxy implements IContentProvider } @Override - public Bundle call(String callingPkg, @Nullable String featureId, String authority, + public Bundle call(@NonNull AttributionSource attributionSource, String authority, String method, String request, Bundle extras) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); data.writeString(authority); data.writeString(method); data.writeString(request); @@ -771,7 +761,7 @@ final class ContentProviderProxy implements IContentProvider } @Override - public AssetFileDescriptor openTypedAssetFile(String callingPkg, @Nullable String featureId, + public AssetFileDescriptor openTypedAssetFile(@NonNull AttributionSource attributionSource, Uri url, String mimeType, Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); @@ -779,8 +769,7 @@ final class ContentProviderProxy implements IContentProvider try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); data.writeString(mimeType); data.writeBundle(opts); @@ -820,15 +809,14 @@ final class ContentProviderProxy implements IContentProvider } @Override - public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri url) + public Uri canonicalize(@NonNull AttributionSource attributionSource, Uri url) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); mRemote.transact(IContentProvider.CANONICALIZE_TRANSACTION, data, reply, 0); @@ -843,14 +831,13 @@ final class ContentProviderProxy implements IContentProvider } @Override - /* oneway */ public void canonicalizeAsync(String callingPkg, @Nullable String featureId, + /* oneway */ public void canonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri, RemoteCallback callback) throws RemoteException { Parcel data = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); uri.writeToParcel(data, 0); callback.writeToParcel(data, 0); @@ -862,15 +849,14 @@ final class ContentProviderProxy implements IContentProvider } @Override - public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri url) + public Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri url) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); mRemote.transact(IContentProvider.UNCANONICALIZE_TRANSACTION, data, reply, 0); @@ -885,14 +871,13 @@ final class ContentProviderProxy implements IContentProvider } @Override - /* oneway */ public void uncanonicalizeAsync(String callingPkg, @Nullable String featureId, + /* oneway */ public void uncanonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri, RemoteCallback callback) throws RemoteException { Parcel data = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); uri.writeToParcel(data, 0); callback.writeToParcel(data, 0); @@ -904,15 +889,14 @@ final class ContentProviderProxy implements IContentProvider } @Override - public boolean refresh(String callingPkg, @Nullable String featureId, Uri url, Bundle extras, + public boolean refresh(@NonNull AttributionSource attributionSource, Uri url, Bundle extras, ICancellationSignal signal) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); data.writeBundle(extras); data.writeStrongBinder(signal != null ? signal.asBinder() : null); @@ -929,15 +913,14 @@ final class ContentProviderProxy implements IContentProvider } @Override - public int checkUriPermission(String callingPkg, @Nullable String featureId, Uri url, int uid, + public int checkUriPermission(@NonNull AttributionSource attributionSource, Uri url, int uid, int modeFlags) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); - data.writeString(callingPkg); - data.writeString(featureId); + attributionSource.writeToParcel(data, 0); url.writeToParcel(data, 0); data.writeInt(uid); data.writeInt(modeFlags); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 8ea417f900db..14b2a65c4da6 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -816,7 +816,6 @@ public abstract class ContentResolver implements ContentInterface { public ContentResolver(@Nullable Context context, @Nullable ContentInterface wrapped) { mContext = context != null ? context : ActivityThread.currentApplication(); mPackageName = mContext.getOpPackageName(); - mAttributionTag = mContext.getAttributionTag(); mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; mWrapped = wrapped; } @@ -1217,7 +1216,7 @@ public abstract class ContentResolver implements ContentInterface { cancellationSignal.setRemote(remoteCancellationSignal); } try { - qCursor = unstableProvider.query(mPackageName, mAttributionTag, uri, projection, + qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection, queryArgs, remoteCancellationSignal); } catch (DeadObjectException e) { // The remote process has died... but we only hold an unstable @@ -1228,7 +1227,7 @@ public abstract class ContentResolver implements ContentInterface { if (stableProvider == null) { return null; } - qCursor = stableProvider.query(mPackageName, mAttributionTag, uri, projection, + qCursor = stableProvider.query(mContext.getAttributionSource(), uri, projection, queryArgs, remoteCancellationSignal); } if (qCursor == null) { @@ -1320,7 +1319,7 @@ public abstract class ContentResolver implements ContentInterface { try { final UriResultListener resultListener = new UriResultListener(); - provider.canonicalizeAsync(mPackageName, mAttributionTag, url, + provider.canonicalizeAsync(mContext.getAttributionSource(), url, new RemoteCallback(resultListener)); resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS); if (resultListener.exception != null) { @@ -1371,7 +1370,7 @@ public abstract class ContentResolver implements ContentInterface { try { final UriResultListener resultListener = new UriResultListener(); - provider.uncanonicalizeAsync(mPackageName, mAttributionTag, url, + provider.uncanonicalizeAsync(mContext.getAttributionSource(), url, new RemoteCallback(resultListener)); resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS); if (resultListener.exception != null) { @@ -1429,7 +1428,7 @@ public abstract class ContentResolver implements ContentInterface { remoteCancellationSignal = provider.createCancellationSignal(); cancellationSignal.setRemote(remoteCancellationSignal); } - return provider.refresh(mPackageName, mAttributionTag, url, extras, + return provider.refresh(mContext.getAttributionSource(), url, extras, remoteCancellationSignal); } catch (RemoteException e) { // Arbitrary and not worth documenting, as Activity @@ -1858,7 +1857,7 @@ public abstract class ContentResolver implements ContentInterface { try { fd = unstableProvider.openAssetFile( - mPackageName, mAttributionTag, uri, mode, + mContext.getAttributionSource(), uri, mode, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause @@ -1873,8 +1872,8 @@ public abstract class ContentResolver implements ContentInterface { if (stableProvider == null) { throw new FileNotFoundException("No content provider: " + uri); } - fd = stableProvider.openAssetFile( - mPackageName, mAttributionTag, uri, mode, remoteCancellationSignal); + fd = stableProvider.openAssetFile(mContext.getAttributionSource(), + uri, mode, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause return null; @@ -2025,7 +2024,7 @@ public abstract class ContentResolver implements ContentInterface { try { fd = unstableProvider.openTypedAssetFile( - mPackageName, mAttributionTag, uri, mimeType, opts, + mContext.getAttributionSource(), uri, mimeType, opts, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause @@ -2041,7 +2040,7 @@ public abstract class ContentResolver implements ContentInterface { throw new FileNotFoundException("No content provider: " + uri); } fd = stableProvider.openTypedAssetFile( - mPackageName, mAttributionTag, uri, mimeType, opts, + mContext.getAttributionSource(), uri, mimeType, opts, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause @@ -2190,7 +2189,7 @@ public abstract class ContentResolver implements ContentInterface { } try { long startTime = SystemClock.uptimeMillis(); - Uri createdRow = provider.insert(mPackageName, mAttributionTag, url, values, extras); + Uri createdRow = provider.insert(mContext.getAttributionSource(), url, values, extras); long durationMillis = SystemClock.uptimeMillis() - startTime; maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */); return createdRow; @@ -2271,7 +2270,7 @@ public abstract class ContentResolver implements ContentInterface { } try { long startTime = SystemClock.uptimeMillis(); - int rowsCreated = provider.bulkInsert(mPackageName, mAttributionTag, url, values); + int rowsCreated = provider.bulkInsert(mContext.getAttributionSource(), url, values); long durationMillis = SystemClock.uptimeMillis() - startTime; maybeLogUpdateToEventLog(durationMillis, url, "bulkinsert", null /* where */); return rowsCreated; @@ -2330,7 +2329,7 @@ public abstract class ContentResolver implements ContentInterface { } try { long startTime = SystemClock.uptimeMillis(); - int rowsDeleted = provider.delete(mPackageName, mAttributionTag, url, extras); + int rowsDeleted = provider.delete(mContext.getAttributionSource(), url, extras); long durationMillis = SystemClock.uptimeMillis() - startTime; maybeLogUpdateToEventLog(durationMillis, url, "delete", null); return rowsDeleted; @@ -2397,7 +2396,8 @@ public abstract class ContentResolver implements ContentInterface { } try { long startTime = SystemClock.uptimeMillis(); - int rowsUpdated = provider.update(mPackageName, mAttributionTag, uri, values, extras); + int rowsUpdated = provider.update(mContext.getAttributionSource(), + uri, values, extras); long durationMillis = SystemClock.uptimeMillis() - startTime; maybeLogUpdateToEventLog(durationMillis, uri, "update", null); return rowsUpdated; @@ -2446,8 +2446,8 @@ public abstract class ContentResolver implements ContentInterface { throw new IllegalArgumentException("Unknown authority " + authority); } try { - final Bundle res = provider.call(mPackageName, mAttributionTag, authority, method, arg, - extras); + final Bundle res = provider.call(mContext.getAttributionSource(), + authority, method, arg, extras); Bundle.setDefusable(res, true); return res; } catch (RemoteException e) { @@ -3866,12 +3866,17 @@ public abstract class ContentResolver implements ContentInterface { /** @hide */ @UnsupportedAppUsage public String getPackageName() { - return mPackageName; + return mContext.getOpPackageName(); } /** @hide */ public @Nullable String getAttributionTag() { - return mAttributionTag; + return mContext.getAttributionTag(); + } + + /** @hide */ + public @NonNull AttributionSource getAttributionSource() { + return mContext.getAttributionSource(); } @UnsupportedAppUsage @@ -3881,7 +3886,6 @@ public abstract class ContentResolver implements ContentInterface { @UnsupportedAppUsage final String mPackageName; - final @Nullable String mAttributionTag; final int mTargetSdkVersion; final ContentInterface mWrapped; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 6ff296c9799f..8531d341ee9b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -889,6 +889,15 @@ public abstract class Context { return null; } + /** + * @return The identity of this context for permission purposes. + * + * @see AttributionSource + */ + public @NonNull AttributionSource getAttributionSource() { + return null; + } + // TODO moltmann: Remove /** * @removed @@ -6465,8 +6474,10 @@ public abstract class Context { * @removed */ @Deprecated - public @NonNull Context createFeatureContext(@Nullable String featureId) { - return createAttributionContext(featureId); + public @NonNull Context createFeatureContext(@Nullable String attributionTag) { + return createContext(new ContextParams.Builder() + .setAttributionTag(attributionTag) + .build()); } /** diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java index fad905bfac13..2b2db8fca2ca 100644 --- a/core/java/android/content/ContextParams.java +++ b/core/java/android/content/ContextParams.java @@ -19,7 +19,6 @@ package android.content; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; import android.annotation.SystemApi; import java.util.Collections; @@ -38,36 +37,26 @@ import java.util.Set; * is an arbitrary string your app specifies for the purposes of tracking permission * accesses from a given portion of your app; against another package and optionally * its attribution tag if you are accessing the data on behalf of another app and - * you will be passing that data to this app. Both attributions are not mutually - * exclusive. - * - * <p>For example if you have a feature "foo" in your app which accesses - * permissions on behalf of app "foo.bar.baz" with feature "bar" you need to - * create a context like this: - * - * <pre class="prettyprint"> - * context.createContext(new ContextParams.Builder() - * .setAttributionTag("foo") - * .setReceiverPackage("foo.bar.baz", "bar") - * .build()) - * </pre> + * you will be passing that data to this app, recursively. Both attributions are + * not mutually exclusive. * * @see Context#createContext(ContextParams) + * @see AttributionSource */ public final class ContextParams { - private final String mAttributionTag; - private final String mReceiverPackage; - private final String mReceiverAttributionTag; - private final Set<String> mRenouncedPermissions; + private final @Nullable String mAttributionTag; + private final @Nullable AttributionSource mNext; + private final @NonNull Set<String> mRenouncedPermissions; /** {@hide} */ public static final ContextParams EMPTY = new ContextParams.Builder().build(); - private ContextParams(@NonNull ContextParams.Builder builder) { - mAttributionTag = builder.mAttributionTag; - mReceiverPackage = builder.mReceiverPackage; - mReceiverAttributionTag = builder.mReceiverAttributionTag; - mRenouncedPermissions = builder.mRenouncedPermissions; + private ContextParams(@Nullable String attributionTag, + @Nullable AttributionSource next, + @NonNull Set<String> renouncedPermissions) { + mAttributionTag = attributionTag; + mNext = next; + mRenouncedPermissions = renouncedPermissions; } /** @@ -79,45 +68,35 @@ public final class ContextParams { } /** - * @return The receiving package. - */ - @Nullable - public String getReceiverPackage() { - return mReceiverPackage; - } - - /** - * @return The receiving package's attribution tag. - */ - @Nullable - public String getReceiverAttributionTag() { - return mReceiverAttributionTag; - } - - /** * @return The set of permissions to treat as renounced. * @hide */ @SystemApi - @SuppressLint("NullableCollection") @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) - public @Nullable Set<String> getRenouncedPermissions() { + public @NonNull Set<String> getRenouncedPermissions() { return mRenouncedPermissions; } /** @hide */ public boolean isRenouncedPermission(@NonNull String permission) { - return mRenouncedPermissions != null && mRenouncedPermissions.contains(permission); + return mRenouncedPermissions.contains(permission); + } + + /** + * @return The receiving attribution source. + */ + @Nullable + public AttributionSource getNextAttributionSource() { + return mNext; } /** * Builder for creating a {@link ContextParams}. */ public static final class Builder { - private String mAttributionTag; - private String mReceiverPackage; - private String mReceiverAttributionTag; - private Set<String> mRenouncedPermissions; + private @Nullable String mAttributionTag; + private @NonNull Set<String> mRenouncedPermissions = Collections.emptySet(); + private @Nullable AttributionSource mNext; /** * Create a new builder. @@ -145,9 +124,8 @@ public final class ContextParams { public Builder(@NonNull ContextParams params) { Objects.requireNonNull(params); mAttributionTag = params.mAttributionTag; - mReceiverPackage = params.mReceiverPackage; - mReceiverAttributionTag = params.mReceiverAttributionTag; mRenouncedPermissions = params.mRenouncedPermissions; + mNext = params.mNext; } /** @@ -163,18 +141,16 @@ public final class ContextParams { } /** - * Sets the package and its optional attribution tag that would be receiving - * the permission protected data. + * Sets the attribution source for the app on whose behalf you are doing the work. * - * @param packageName The package name receiving the permission protected data. - * @param attributionTag An attribution tag of the receiving package. + * @param next The permission identity of the receiving app. * @return This builder. + * + * @see AttributionSource */ @NonNull - public Builder setReceiverPackage(@Nullable String packageName, - @Nullable String attributionTag) { - mReceiverPackage = packageName; - mReceiverAttributionTag = attributionTag; + public Builder setNextAttributionSource(@NonNull AttributionSource next) { + mNext = Objects.requireNonNull(next); return this; } @@ -194,19 +170,16 @@ public final class ContextParams { * permissions are supported by this mechanism. * * @param renouncedPermissions The set of permissions to treat as - * renounced. + * renounced, which is as if not granted. * @return This builder. * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public @NonNull Builder setRenouncedPermissions( - @Nullable Set<String> renouncedPermissions) { - if (renouncedPermissions != null) { - mRenouncedPermissions = Collections.unmodifiableSet(renouncedPermissions); - } else { - mRenouncedPermissions = null; - } + @NonNull Set<String> renouncedPermissions) { + mRenouncedPermissions = Collections.unmodifiableSet( + Objects.requireNonNull(renouncedPermissions)); return this; } @@ -217,7 +190,8 @@ public final class ContextParams { */ @NonNull public ContextParams build() { - return new ContextParams(this); + return new ContextParams(mAttributionTag, mNext, + mRenouncedPermissions); } } } diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 609f417a8008..de0d65fec1fb 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -1060,6 +1060,12 @@ public class ContextWrapper extends Context { return mBase.createAttributionContext(attributionTag); } + @NonNull + @Override + public AttributionSource getAttributionSource() { + return mBase.getAttributionSource(); + } + @Override public boolean isRestricted() { return mBase.isRestricted(); diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 9210b132c75a..e0315a3e171b 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -16,11 +16,13 @@ package android.content; +import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; @@ -38,11 +40,11 @@ import java.util.ArrayList; * @hide */ public interface IContentProvider extends IInterface { - public Cursor query(String callingPkg, @Nullable String attributionTag, Uri url, + Cursor query(@NonNull AttributionSource attributionSource, Uri url, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) throws RemoteException; - public String getType(Uri url) throws RemoteException; + String getType(Uri url) throws RemoteException; /** * A oneway version of getType. The functionality is exactly the same, except that the @@ -55,54 +57,56 @@ public interface IContentProvider extends IInterface { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link " + "ContentProviderClient#insert(android.net.Uri, android.content.ContentValues)} " + "instead") - public default Uri insert(String callingPkg, Uri url, ContentValues initialValues) + default Uri insert(String callingPkg, Uri url, ContentValues initialValues) throws RemoteException { - return insert(callingPkg, null, url, initialValues, null); + return insert(new AttributionSource(Binder.getCallingUid(), callingPkg, null), + url, initialValues, null); } - public Uri insert(String callingPkg, String attributionTag, Uri url, + Uri insert(@NonNull AttributionSource attributionSource, Uri url, ContentValues initialValues, Bundle extras) throws RemoteException; @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link " + "ContentProviderClient#bulkInsert(android.net.Uri, android.content.ContentValues[])" + "} instead") - public default int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues) + default int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues) throws RemoteException { - return bulkInsert(callingPkg, null, url, initialValues); + return bulkInsert(new AttributionSource(Binder.getCallingUid(), callingPkg, null), + url, initialValues); } - public int bulkInsert(String callingPkg, String attributionTag, Uri url, + int bulkInsert(@NonNull AttributionSource attributionSource, Uri url, ContentValues[] initialValues) throws RemoteException; @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link " + "ContentProviderClient#delete(android.net.Uri, java.lang.String, java.lang" + ".String[])} instead") - public default int delete(String callingPkg, Uri url, String selection, String[] selectionArgs) + default int delete(String callingPkg, Uri url, String selection, String[] selectionArgs) throws RemoteException { - return delete(callingPkg, null, url, - ContentResolver.createSqlQueryBundle(selection, selectionArgs)); + return delete(new AttributionSource(Binder.getCallingUid(), callingPkg, null), + url, ContentResolver.createSqlQueryBundle(selection, selectionArgs)); } - public int delete(String callingPkg, String attributionTag, Uri url, Bundle extras) + int delete(@NonNull AttributionSource attributionSource, Uri url, Bundle extras) throws RemoteException; @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link " + "ContentProviderClient#update(android.net.Uri, android.content.ContentValues, java" + ".lang.String, java.lang.String[])} instead") - public default int update(String callingPkg, Uri url, ContentValues values, String selection, + default int update(String callingPkg, Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException { - return update(callingPkg, null, url, values, - ContentResolver.createSqlQueryBundle(selection, selectionArgs)); + return update(new AttributionSource(Binder.getCallingUid(), callingPkg, null), + url, values, ContentResolver.createSqlQueryBundle(selection, selectionArgs)); } - public int update(String callingPkg, String attributionTag, Uri url, ContentValues values, + int update(@NonNull AttributionSource attributionSource, Uri url, ContentValues values, Bundle extras) throws RemoteException; - public ParcelFileDescriptor openFile(String callingPkg, @Nullable String attributionTag, - Uri url, String mode, ICancellationSignal signal, IBinder callerToken) + ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource, + Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException; - public AssetFileDescriptor openAssetFile(String callingPkg, @Nullable String attributionTag, + AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException; - public ContentProviderResult[] applyBatch(String callingPkg, @Nullable String attributionTag, + ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource, String authority, ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException; @@ -112,18 +116,19 @@ public interface IContentProvider extends IInterface { + "instead") public default Bundle call(String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras) throws RemoteException { - return call(callingPkg, null, "unknown", method, arg, extras); + return call(new AttributionSource(Binder.getCallingUid(), callingPkg, null), + "unknown", method, arg, extras); } - public Bundle call(String callingPkg, @Nullable String attributionTag, String authority, + Bundle call(@NonNull AttributionSource attributionSource, String authority, String method, @Nullable String arg, @Nullable Bundle extras) throws RemoteException; - public int checkUriPermission(String callingPkg, @Nullable String attributionTag, Uri uri, + int checkUriPermission(@NonNull AttributionSource attributionSource, Uri uri, int uid, int modeFlags) throws RemoteException; - public ICancellationSignal createCancellationSignal() throws RemoteException; + ICancellationSignal createCancellationSignal() throws RemoteException; - public Uri canonicalize(String callingPkg, @Nullable String attributionTag, Uri uri) + Uri canonicalize(@NonNull AttributionSource attributionSource, Uri uri) throws RemoteException; /** @@ -131,10 +136,10 @@ public interface IContentProvider extends IInterface { * call returns immediately, and the resulting type is returned when available via * a binder callback. */ - void canonicalizeAsync(String callingPkg, @Nullable String attributionTag, Uri uri, + void canonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri, RemoteCallback callback) throws RemoteException; - public Uri uncanonicalize(String callingPkg, @Nullable String attributionTag, Uri uri) + Uri uncanonicalize(@NonNull AttributionSource attributionSource, Uri uri) throws RemoteException; /** @@ -142,18 +147,17 @@ public interface IContentProvider extends IInterface { * call returns immediately, and the resulting type is returned when available via * a binder callback. */ - void uncanonicalizeAsync(String callingPkg, @Nullable String attributionTag, Uri uri, + void uncanonicalizeAsync(@NonNull AttributionSource attributionSource, Uri uri, RemoteCallback callback) throws RemoteException; - public boolean refresh(String callingPkg, @Nullable String attributionTag, Uri url, + public boolean refresh(@NonNull AttributionSource attributionSource, Uri url, @Nullable Bundle extras, ICancellationSignal cancellationSignal) throws RemoteException; // Data interchange. public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException; - public AssetFileDescriptor openTypedAssetFile(String callingPkg, - @Nullable String attributionTag, Uri url, String mimeType, Bundle opts, - ICancellationSignal signal) + public AssetFileDescriptor openTypedAssetFile(@NonNull AttributionSource attributionSource, + Uri url, String mimeType, Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException; /* IPC constants */ diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java index 159db92c79c9..08eac5aff655 100644 --- a/core/java/android/content/PermissionChecker.java +++ b/core/java/android/content/PermissionChecker.java @@ -27,6 +27,8 @@ import android.os.Process; 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 @@ -68,8 +70,14 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public final class PermissionChecker { + private static final String PLATFORM_PACKAGE_NAME = "android"; + /** The permission is granted. */ - public static final int PERMISSION_GRANTED = PackageManager.PERMISSION_GRANTED; + public static final int PERMISSION_GRANTED = AppOpsManager.MODE_ALLOWED; + + /** Only for runtime permissions, its returned when the runtime permission + * is granted, but the corresponding app op is denied. */ + public static final int PERMISSION_SOFT_DENIED = AppOpsManager.MODE_IGNORED; /** Returned when: * <ul> @@ -79,15 +87,14 @@ public final class PermissionChecker { * </ul> * */ - public static final int PERMISSION_HARD_DENIED = PackageManager.PERMISSION_DENIED; - - /** 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 = PackageManager.PERMISSION_DENIED - 1; + public static final int PERMISSION_HARD_DENIED = AppOpsManager.MODE_ERRORED; /** Constant when the PID for which we check permissions is unknown. */ public static final int PID_UNKNOWN = -1; + private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions + = new ConcurrentHashMap<>(); + /** @hide */ @IntDef({PERMISSION_GRANTED, PERMISSION_SOFT_DENIED, @@ -131,6 +138,50 @@ public final class PermissionChecker { * @return The permission check result which is either {@link #PERMISSION_GRANTED} * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * @param message A message describing the reason the permission was checked + * @param startDataDelivery Whether this is the start of data delivery. + * + * @see #checkPermissionForPreflight(Context, String, int, int, String) + */ + @PermissionResult + public static int checkPermissionForDataDelivery(@NonNull Context context, + @NonNull String permission, int pid, int uid, @Nullable String packageName, + @Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) { + return checkPermissionForDataDelivery(context, permission, pid, new AttributionSource(uid, + packageName, attributionTag), message, startDataDelivery); + } + + /** + * Checks whether a given package in a UID and PID has a given permission + * and whether the app op that corresponds to this permission is allowed. + * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * point where you will deliver the permission protected data to clients. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkPermissionForPreflight(Context, String, int, int, String)} + * to determine if the app has or may have location permission (if app has only foreground + * location the grant state depends on the app's fg/gb state) and this check will not + * leave a trace that permission protected data was delivered. When you are about to + * deliver the location data to a registered listener you should use this method which + * will evaluate the permission access based on the current fg/bg state of the app and + * leave a record that the data was accessed. + * + * <p>For more details how to determine the {@code packageName}, {@code attributionTag}, and + * {@code message}, please check the description in + * {@link AppOpsManager#noteOp(String, int, String, String, String)} + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID + * is not known. + * @param uid The uid for which to check. + * @param packageName The package name for which to check. If null the + * the first package for the calling UID will be used. + * @param attributionTag attribution tag + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. + * @param message A message describing the reason the permission was checked * * @see #checkPermissionForPreflight(Context, String, int, int, String) */ @@ -138,8 +189,303 @@ public final class PermissionChecker { public static int checkPermissionForDataDelivery(@NonNull Context context, @NonNull String permission, int pid, int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String message) { - return checkPermissionCommon(context, permission, pid, uid, packageName, attributionTag, - message, true /*forDataDelivery*/); + return checkPermissionForDataDelivery(context, permission, pid, uid, + packageName, attributionTag, message, false /*startDataDelivery*/); + } + + /** + * Checks whether a given data access chain described by the given {@link AttributionSource} + * has a given permission and whether the app op that corresponds to this permission + * is allowed. Call this method if you are the datasource which would not blame you for + * access to the data since you are the data. + * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * point where you will deliver the permission protected data to clients. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkPermissionForPreflight(Context, String, int, int, String)} + * to determine if the app has or may have location permission (if app has only foreground + * location the grant state depends on the app's fg/gb state) and this check will not + * leave a trace that permission protected data was delivered. When you are about to + * deliver the location data to a registered listener you should use this method which + * will evaluate the permission access based on the current fg/bg state of the app and + * leave a record that the data was accessed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID + * is not known. + * @param attributionSource the permission identity + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. + * @param message A message describing the reason the permission was checked + * + * @see #checkPermissionForPreflight(Context, String, AttributionSource) + */ + @PermissionResult + public static int checkPermissionForDataDeliveryFromDataSource(@NonNull Context context, + @NonNull String permission, int pid, @NonNull AttributionSource attributionSource, + @Nullable String message) { + return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource, + message, false /*startDataDelivery*/, /*fromDatasource*/ true); + } + + /** + * Checks whether a given data access chain described by the given {@link AttributionSource} + * has a given permission and whether the app op that corresponds to this permission + * is allowed. + * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * point where you will deliver the permission protected data to clients. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkPermissionForPreflight(Context, String, AttributionSource)} + * to determine if the app has or may have location permission (if app has only foreground + * location the grant state depends on the app's fg/gb state) and this check will not + * leave a trace that permission protected data was delivered. When you are about to + * deliver the location data to a registered listener you should use this method which + * will evaluate the permission access based on the current fg/bg state of the app and + * leave a record that the data was accessed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID + * is not known. + * @param attributionSource the permission identity + * @param message A message describing the reason the permission was checked + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. + * + * @see #checkPermissionForPreflight(Context, String, AttributionSource) + */ + @PermissionResult + public static int checkPermissionForDataDelivery(@NonNull Context context, + @NonNull String permission, int pid, @NonNull AttributionSource attributionSource, + @Nullable String message) { + return checkPermissionForDataDelivery(context, permission, pid, attributionSource, + message, false /*startDataDelivery*/); + } + + /** + * Checks whether a given data access chain described by the given {@link AttributionSource} + * has a given permission and whether the app op that corresponds to this permission + * is allowed. + * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * point where you will deliver the permission protected data to clients. + * + * <p>For example, if an app registers a data listener it should have the required + * permission but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkPermissionForPreflight(Context, String, + * AttributionSource)} + * to determine if the app has or may have permission and this check will not + * leave a trace that permission protected data was delivered. When you are about to + * deliver the data to a registered listener you should use this method which + * will evaluate the permission access based on the current fg/bg state of the app and + * leave a record that the data was accessed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID + * is not known. + * @param attributionSource The identity for which to check the permission. + * @param message A message describing the reason the permission was checked + * @param startDataDelivery Whether this is the start of data delivery. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. + * + * @see #checkPermissionForPreflight(Context, String, AttributionSource) + */ + @PermissionResult + 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, + message, startDataDelivery, /*fromDatasource*/ false); + } + + private static int checkPermissionForDataDeliveryCommon(@NonNull Context context, + @NonNull String permission, int pid, @NonNull AttributionSource attributionSource, + @Nullable String message, boolean startDataDelivery, boolean fromDatasource) { + // If the check failed in the middle of the chain, finish any started op. + final int result = checkPermissionCommon(context, permission, attributionSource, + message, true /*forDataDelivery*/, startDataDelivery, fromDatasource); + if (startDataDelivery && result != PERMISSION_GRANTED) { + finishDataDelivery(context, AppOpsManager.permissionToOp(permission), + attributionSource); + } + return result; + } + + /** + * Checks whether a given data access chain described by the given {@link AttributionSource} + * has a given permission and whether the app op that corresponds to this permission + * is allowed. The app ops area also marked as started. This is useful for long running + * permissions like camera. + * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * point where you will deliver the permission protected data to clients. + * + * <p>For example, if an app registers a data listener it should have the required + * permission but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkPermissionForPreflight(Context, String, + * AttributionSource)} + * to determine if the app has or may have permission and this check will not + * leave a trace that permission protected data was delivered. When you are about to + * deliver the data to a registered listener you should use this method which + * will evaluate the permission access based on the current fg/bg state of the app and + * leave a record that the data was accessed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @param attributionSource The identity for which to check the permission. + * @param message A message describing the reason the permission was checked + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. + * + * @see #checkPermissionForPreflight(Context, String, AttributionSource) + */ + @PermissionResult + 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); + } + + /** + * Checks whether a given data access chain described by the given {@link + * AttributionSource} has a given app op allowed and marks the op as started. + * + * <strong>NOTE:</strong> Use this method only for app op checks at the + * point where you will deliver the protected data to clients. + * + * <p>For example, if an app registers a data listener it should have the data + * op but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkOpForPreflight(Context, String, AttributionSource, String)} + * to determine if the app has or may have op access and this check will not + * leave a trace that op protected data was delivered. When you are about to + * deliver the data to a registered listener you should use this method which + * will evaluate the op access based on the current fg/bg state of the app and + * leave a record that the data was accessed. + * + * @param context Context for accessing resources. + * @param opName THe op to start. + * @param attributionSource The identity for which to check the permission. + * @param message A message describing the reason the permission was checked + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. + * + * @see #finishDataDelivery(Context, String, AttributionSource) + */ + @PermissionResult + 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); + } + return result; + } + + /** + * Finishes an ongoing op for data access chain described by the given {@link + * AttributionSource}. + * + * @param context Context for accessing resources. + * @param op The op to finish. + * @param attributionSource The identity for which finish op. + * + * @see #startOpForDataDelivery(Context, String, AttributionSource, String) + * @see #checkPermissionAndStartDataDelivery(Context, String, AttributionSource, String) + */ + 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()); + } + } + + /** + * Checks whether a given data access chain described by the given {@link + * AttributionSource} has a given app op allowed. + * + * <strong>NOTE:</strong> Use this method only for op checks at the + * preflight point where you will not deliver the protected data + * to clients but schedule a data delivery, apps register listeners, + * etc. + * + * <p>For example, if an app registers a data listener it should have the op + * but no data is actually sent to the app at the moment of registration + * and you should use this method to determine if the app has or may have data + * access and this check will not leave a trace that protected data + * was delivered. When you are about to deliver the data to a registered + * listener you should use {@link #checkOpForDataDelivery(Context, String, + * AttributionSource, String)} which will evaluate the op access based + * on the current fg/bg state of the app and leave a record that the data was + * accessed. + * + * @param context Context for accessing resources. + * @param opName The op to check. + * @param attributionSource The identity for which to check the permission. + * @param message A message describing the reason the permission was checked + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. + * + * @see #checkOpForDataDelivery(Context, String, AttributionSource, String) + */ + @PermissionResult + 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*/); + } + + /** + * Checks whether a given data access chain described by the given {@link AttributionSource} + * has an allowed app op. + * + * <strong>NOTE:</strong> Use this method only for op checks at the + * point where you will deliver the permission protected data to clients. + * + * <p>For example, if an app registers a data listener it should have the data + * permission but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkOpForPreflight(Context, String, AttributionSource, String)} + * to determine if the app has or may have data access and this check will not + * leave a trace that op protected data was delivered. When you are about to + * deliver the data to a registered listener you should use this method which + * will evaluate the op access based on the current fg/bg state of the app and + * leave a record that the data was accessed. + * + * @param context Context for accessing resources. + * @param opName The op to check. + * @param attributionSource The identity for which to check the op. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. + * @param message A message describing the reason the permission was checked + * + * @see #checkOpForPreflight(Context, String, AttributionSource, String) + */ + @PermissionResult + 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*/); } /** @@ -158,8 +504,8 @@ public final class PermissionChecker { * fg/gb state) and this check will not leave a trace that permission protected data * was delivered. When you are about to deliver the location data to a registered * listener you should use {@link #checkPermissionForDataDelivery(Context, String, - * int, int, String, String)} which will evaluate the permission access based on the current - * fg/bg state of the app and leave a record that the data was accessed. + * int, int, String, String, String)} which will evaluate the permission access based + * on the currentfg/bg state of the app and leave a record that the data was accessed. * * @param context Context for accessing resources. * @param permission The permission to check. @@ -170,13 +516,49 @@ public final class PermissionChecker { * @return The permission check result which is either {@link #PERMISSION_GRANTED} * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * - * @see #checkPermissionForDataDelivery(Context, String, int, int, String, String) + * @see #checkPermissionForDataDelivery(Context, String, int, int, String, String, String) */ @PermissionResult public static int checkPermissionForPreflight(@NonNull Context context, @NonNull String permission, int pid, int uid, @Nullable String packageName) { - return checkPermissionCommon(context, permission, pid, uid, packageName, - null /*attributionTag*/, null /*message*/, false /*forDataDelivery*/); + return checkPermissionForPreflight(context, permission, new AttributionSource( + uid, packageName, null /*attributionTag*/)); + } + + /** + * Checks whether a given data access chain described by the given {@link AttributionSource} + * has a given permission and whether the app op that corresponds to this permission + * is allowed. + * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * preflight point where you will not deliver the permission protected data + * to clients but schedule permission data delivery, apps register listeners, + * etc. + * + * <p>For example, if an app registers a data listener it should have the required + * permission but no data is actually sent to the app at the moment of registration + * and you should use this method to determine if the app has or may have the + * permission and this check will not leave a trace that permission protected data + * was delivered. When you are about to deliver the protected data to a registered + * listener you should use {@link #checkPermissionForDataDelivery(Context, String, + * int, AttributionSource, String, boolean)} which will evaluate the permission access based + * on the current fg/bg state of the app and leave a record that the data was accessed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @param attributionSource The identity for which to check the permission. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. + * + * @see #checkPermissionForDataDelivery(Context, String, int, AttributionSource, + * String, boolean) + */ + @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); } /** @@ -211,7 +593,8 @@ public final class PermissionChecker { public static int checkSelfPermissionForDataDelivery(@NonNull Context context, @NonNull String permission, @Nullable String message) { return checkPermissionForDataDelivery(context, permission, Process.myPid(), - Process.myUid(), context.getPackageName(), context.getAttributionTag(), message); + Process.myUid(), context.getPackageName(), context.getAttributionTag(), message, + /*startDataDelivery*/ false); } /** @@ -289,7 +672,8 @@ public final class PermissionChecker { return PERMISSION_HARD_DENIED; } return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(), - Binder.getCallingUid(), callingPackageName, callingAttributionTag, message); + Binder.getCallingUid(), callingPackageName, callingAttributionTag, message, + /*startDataDelivery*/ false); } /** @@ -308,8 +692,8 @@ public final class PermissionChecker { * fg/gb state) and this check will not leave a trace that permission protected data * was delivered. When you are about to deliver the location data to a registered * listener you should use {@link #checkCallingOrSelfPermissionForDataDelivery(Context, - * String, String)} which will evaluate the permission access based on the current fg/bg state - * of the app and leave a record that the data was accessed. + * String, String, String, String)} which will evaluate the permission access based on the + * current fg/bg stateof the app and leave a record that the data was accessed. * * @param context Context for accessing resources. * @param permission The permission to check. @@ -318,7 +702,7 @@ public final class PermissionChecker { * @return The permission check result which is either {@link #PERMISSION_GRANTED} * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * - * @see #checkCallingPermissionForDataDelivery(Context, String, String, String) + * @see #checkCallingPermissionForDataDelivery(Context, String, String, String, String) */ @PermissionResult public static int checkCallingPermissionForPreflight(@NonNull Context context, @@ -370,7 +754,8 @@ public final class PermissionChecker { callingAttributionTag = (Binder.getCallingPid() == Process.myPid()) ? context.getAttributionTag() : callingAttributionTag; return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(), - Binder.getCallingUid(), callingPackageName, callingAttributionTag, message); + Binder.getCallingUid(), callingPackageName, callingAttributionTag, message, + /*startDataDelivery*/ false); } /** @@ -408,88 +793,325 @@ public final class PermissionChecker { Binder.getCallingUid(), packageName); } - static int checkPermissionCommon(@NonNull Context context, @NonNull String permission, - int pid, int uid, @Nullable String packageName, @Nullable String attributionTag, - @Nullable String message, boolean forDataDelivery) { - final PermissionInfo permissionInfo; - try { - // TODO(b/147869157): Cache platform defined app op and runtime permissions to avoid - // calling into the package manager every time. - permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0); - } catch (PackageManager.NameNotFoundException ignored) { - return PERMISSION_HARD_DENIED; - } + @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 (packageName == null) { - String[] packageNames = context.getPackageManager().getPackagesForUid(uid); - if (packageNames != null && packageNames.length > 0) { - packageName = packageNames[0]; + if (permissionInfo == null) { + try { + permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0); + if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) { + // Double addition due to concurrency is fine - the backing store is concurrent. + sPlatformPermissions.put(permission, permissionInfo); + } + } catch (PackageManager.NameNotFoundException ignored) { + return PERMISSION_HARD_DENIED; } } if (permissionInfo.isAppOp()) { - return checkAppOpPermission(context, permission, pid, uid, packageName, attributionTag, - message, forDataDelivery); + return checkAppOpPermission(context, permission, attributionSource, message, + forDataDelivery, fromDatasource); } if (permissionInfo.isRuntime()) { - return checkRuntimePermission(context, permission, pid, uid, packageName, - attributionTag, message, forDataDelivery); + return checkRuntimePermission(context, permission, attributionSource, message, + forDataDelivery, startDataDelivery, fromDatasource); + } + + if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(), + attributionSource.getRenouncedPermissions())) { + return PERMISSION_HARD_DENIED; } - return context.checkPermission(permission, pid, uid); + + 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, - int pid, int uid, @Nullable String packageName, @Nullable String attributionTag, - @Nullable String message, boolean forDataDelivery) { - final String op = AppOpsManager.permissionToOp(permission); - if (op == null || packageName == null) { + @NonNull AttributionSource attributionSource, @Nullable String message, + boolean forDataDelivery, boolean fromDatasource) { + final int op = AppOpsManager.permissionToOpCode(permission); + if (op < 0 || attributionSource.getPackageName() == null) { return PERMISSION_HARD_DENIED; } final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - final int opMode = (forDataDelivery) - ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message) - : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName); - switch (opMode) { - case AppOpsManager.MODE_ALLOWED: - case AppOpsManager.MODE_FOREGROUND: { + 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; + } + + int opMode; + if (forDataDelivery) { + if (next == null) { + opMode = appOpsManager.noteOpNoThrow(op, current.getUid(), + current.getPackageName(), current.getAttributionTag(), message); + } else { + opMode = appOpsManager.noteProxyOpNoThrow(op, current, message, + skipCurrentChecks); + } + } else { + opMode = appOpsManager.unsafeCheckOpRawNoThrow(op, current.getUid(), + current.getPackageName()); + if (next != null && opMode == AppOpsManager.MODE_ALLOWED) { + opMode = appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(), + next.getPackageName()); + } + } + + 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; } - case AppOpsManager.MODE_DEFAULT: { - return context.checkPermission(permission, pid, uid) - == PackageManager.PERMISSION_GRANTED - ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED; + + 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; } - default: { + + // 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) { + 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 int checkRuntimePermission(@NonNull Context context, @NonNull String permission, - int pid, int uid, @Nullable String packageName, @Nullable String attributionTag, - @Nullable String message, boolean forDataDelivery) { - if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) { - return PERMISSION_HARD_DENIED; + private static boolean checkPermission(@NonNull Context context, @NonNull String permission, + int uid, @NonNull Set<String> renouncedPermissions) { + final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1, + uid) == PackageManager.PERMISSION_GRANTED; + if (permissionGranted && renouncedPermissions.contains(permission)) { + return false; } + return permissionGranted; + } - final String op = AppOpsManager.permissionToOp(permission); - if (op == null || packageName == null) { - return PERMISSION_GRANTED; + 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; } - final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - final int opMode = (forDataDelivery) - ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message) - : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName); + 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; + } + } - switch (opMode) { - case AppOpsManager.MODE_ALLOWED: - case AppOpsManager.MODE_FOREGROUND: + if (next == null || next.getNext() == null) { return PERMISSION_GRANTED; - default: - return PERMISSION_SOFT_DENIED; + } + + current = next; + } + } + // If from data source and there is next app after that we need to note SELF of (noteOp) for the app vs proxy + 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); + if (!forDataDelivery) { + final String resolvedPackageName = resolvePackageName(context, attributionSource); + if (resolvedPackageName == null) { + return AppOpsManager.MODE_ERRORED; + } + final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op, + attributionSource.getUid(), resolvedPackageName); + final AttributionSource previous = attributionSource.getNext(); + if (opMode == AppOpsManager.MODE_ALLOWED && previous != null) { + final String resolvedPreviousPackageName = resolvePackageName(context, + previous); + if (resolvedPreviousPackageName == null) { + return AppOpsManager.MODE_ERRORED; + } + return appOpsManager.unsafeCheckOpRawNoThrow(op, previous.getUid(), + resolvedPreviousPackageName); + } + return opMode; + } else if (startDataDelivery) { + final AttributionSource accessorSource = (!singleReceiverFromDatasource) + ? attributionSource : attributionSource.getNext(); + final AttributionSource resolvedAttributionSource = resolveAttributionSource( + context, accessorSource); + if (resolvedAttributionSource.getPackageName() == null) { + return AppOpsManager.MODE_ERRORED; + } + if (selfAccess) { + return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(), + resolvedAttributionSource.getPackageName(), + /*startIfModeDefault*/ false, + resolvedAttributionSource.getAttributionTag(), + message); + } else { + return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message, + skipProxyOperation); + } + } else { + final AttributionSource accessorSource = (!singleReceiverFromDatasource) + ? attributionSource : attributionSource.getNext(); + final AttributionSource resolvedAttributionSource = resolveAttributionSource( + context, accessorSource); + if (resolvedAttributionSource.getPackageName() == null) { + return AppOpsManager.MODE_ERRORED; + } + if (selfAccess) { + return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(), + resolvedAttributionSource.getPackageName(), + resolvedAttributionSource.getAttributionTag(), + message); + } 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]; + } + return null; + } + + 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()); } } diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 8c105be9fbb7..ef075e1efbff 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -16,6 +16,7 @@ package android.permission; +import android.content.AttributionSource; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; @@ -85,4 +86,8 @@ interface IPermissionManager { boolean setAutoRevokeExempted(String packageName, boolean exempted, int userId); boolean isAutoRevokeExempted(String packageName, int userId); + + AttributionSource registerAttributionSource(in AttributionSource source); + + boolean isRegisteredAttributionSource(in AttributionSource source); } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index baa25f07f514..936cbfc70708 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -44,6 +44,7 @@ import android.content.pm.PermissionInfo; import android.content.pm.permission.SplitPermissionInfoParcelable; import android.location.LocationManager; import android.media.AudioManager; +import android.content.AttributionSource; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -1099,6 +1100,48 @@ public final class PermissionManager { callingFeatureId, pid, uid); } + /** + * Registers an attribution source with the OS. An app can only register an attribution + * source for itself. Once an attribution source has been registered another app can + * check whether this registration exists and thus trust the payload in the source + * object. This is important for permission checking and specifically for app op blaming + * since a malicious app should not be able to force the OS to blame another app + * that doesn't participate in an attribution chain. + * + * @param source The attribution source to register. + * + * @see #isRegisteredAttributionSource(AttributionSource) + * + * @hide + */ + public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) { + try { + return mPermissionManager.registerAttributionSource(source); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return null; + } + + /** + * Checks whether an attribution source is registered. + * + * @param source The attribution source to check. + * @return Whether this is a registered source. + * + * @see #registerAttributionSource(AttributionSource) + * + * @hide + */ + public boolean isRegisteredAttributionSource(@NonNull AttributionSource source) { + try { + return mPermissionManager.isRegisteredAttributionSource(source); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + /* @hide */ private static int checkPermissionUncached(@Nullable String permission, int pid, int uid) { final IActivityManager am = ActivityManager.getService(); diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index b704d66d8e41..a5a24c0f1013 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -1102,8 +1102,7 @@ public abstract class DocumentsProvider extends ContentProvider { // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for // MANAGE_DOCUMENTS or associated URI permission here instead final Uri rootUri = extraUri; - enforceWritePermissionInner(rootUri, getCallingPackage(), getCallingAttributionTag(), - null); + enforceWritePermissionInner(rootUri, getCallingAttributionSource()); final String rootId = DocumentsContract.getRootId(rootUri); ejectRoot(rootId); @@ -1121,8 +1120,7 @@ public abstract class DocumentsProvider extends ContentProvider { } if (METHOD_IS_CHILD_DOCUMENT.equals(method)) { - enforceReadPermissionInner(documentUri, getCallingPackage(), - getCallingAttributionTag(), null); + enforceReadPermissionInner(documentUri, getCallingAttributionSource()); final Uri childUri = extraTargetUri; final String childAuthority = childUri.getAuthority(); @@ -1134,8 +1132,7 @@ public abstract class DocumentsProvider extends ContentProvider { && isChildDocument(documentId, childId)); } else if (METHOD_CREATE_DOCUMENT.equals(method)) { - enforceWritePermissionInner(documentUri, getCallingPackage(), - getCallingAttributionTag(), null); + enforceWritePermissionInner(documentUri, getCallingAttributionSource()); final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); @@ -1149,8 +1146,7 @@ public abstract class DocumentsProvider extends ContentProvider { out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); } else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) { - enforceWritePermissionInner(documentUri, getCallingPackage(), - getCallingAttributionTag(), null); + enforceWritePermissionInner(documentUri, getCallingAttributionSource()); final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS); final IntentSender intentSender = createWebLinkIntent(documentId, options); @@ -1158,8 +1154,7 @@ public abstract class DocumentsProvider extends ContentProvider { out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender); } else if (METHOD_RENAME_DOCUMENT.equals(method)) { - enforceWritePermissionInner(documentUri, getCallingPackage(), - getCallingAttributionTag(), null); + enforceWritePermissionInner(documentUri, getCallingAttributionSource()); final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); final String newDocumentId = renameDocument(documentId, displayName); @@ -1183,8 +1178,7 @@ public abstract class DocumentsProvider extends ContentProvider { } } else if (METHOD_DELETE_DOCUMENT.equals(method)) { - enforceWritePermissionInner(documentUri, getCallingPackage(), - getCallingAttributionTag(), null); + enforceWritePermissionInner(documentUri, getCallingAttributionSource()); deleteDocument(documentId); // Document no longer exists, clean up any grants. @@ -1194,10 +1188,8 @@ public abstract class DocumentsProvider extends ContentProvider { final Uri targetUri = extraTargetUri; final String targetId = DocumentsContract.getDocumentId(targetUri); - enforceReadPermissionInner(documentUri, getCallingPackage(), - getCallingAttributionTag(), null); - enforceWritePermissionInner(targetUri, getCallingPackage(), getCallingAttributionTag(), - null); + enforceReadPermissionInner(documentUri, getCallingAttributionSource()); + enforceWritePermissionInner(targetUri, getCallingAttributionSource()); final String newDocumentId = copyDocument(documentId, targetId); @@ -1220,12 +1212,9 @@ public abstract class DocumentsProvider extends ContentProvider { final Uri targetUri = extraTargetUri; final String targetId = DocumentsContract.getDocumentId(targetUri); - enforceWritePermissionInner(documentUri, getCallingPackage(), - getCallingAttributionTag(), null); - enforceReadPermissionInner(parentSourceUri, getCallingPackage(), - getCallingAttributionTag(), null); - enforceWritePermissionInner(targetUri, getCallingPackage(), getCallingAttributionTag(), - null); + enforceWritePermissionInner(documentUri, getCallingAttributionSource()); + enforceReadPermissionInner(parentSourceUri, getCallingAttributionSource()); + enforceWritePermissionInner(targetUri, getCallingAttributionSource()); final String newDocumentId = moveDocument(documentId, parentSourceId, targetId); @@ -1246,10 +1235,8 @@ public abstract class DocumentsProvider extends ContentProvider { final Uri parentSourceUri = extraParentUri; final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri); - enforceReadPermissionInner(parentSourceUri, getCallingPackage(), - getCallingAttributionTag(), null); - enforceWritePermissionInner(documentUri, getCallingPackage(), - getCallingAttributionTag(), null); + enforceReadPermissionInner(parentSourceUri, getCallingAttributionSource()); + enforceWritePermissionInner(documentUri, getCallingAttributionSource()); removeDocument(documentId, parentSourceId); // It's responsibility of the provider to revoke any grants, as the document may be @@ -1258,8 +1245,7 @@ public abstract class DocumentsProvider extends ContentProvider { final boolean isTreeUri = isTreeUri(documentUri); if (isTreeUri) { - enforceReadPermissionInner(documentUri, getCallingPackage(), - getCallingAttributionTag(), null); + enforceReadPermissionInner(documentUri, getCallingAttributionSource()); } else { getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 5d924cc88706..12ff6405b8f2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2754,7 +2754,7 @@ public final class Settings { arg.putBoolean(CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true); } IContentProvider cp = mProviderHolder.getProvider(cr); - cp.call(cr.getPackageName(), cr.getAttributionTag(), + cp.call(cr.getAttributionSource(), mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg); } catch (RemoteException e) { Log.w(TAG, "Can't set key " + name + " in " + mUri, e); @@ -2774,7 +2774,7 @@ public final class Settings { args.putString(CALL_METHOD_PREFIX_KEY, prefix); args.putSerializable(CALL_METHOD_FLAGS_KEY, keyValues); IContentProvider cp = mProviderHolder.getProvider(cr); - Bundle bundle = cp.call(cr.getPackageName(), cr.getAttributionTag(), + Bundle bundle = cp.call(cr.getAttributionSource(), mProviderHolder.mUri.getAuthority(), mCallSetAllCommand, null, args); return bundle.getBoolean(KEY_CONFIG_SET_RETURN); @@ -2862,14 +2862,14 @@ public final class Settings { if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) { final long token = Binder.clearCallingIdentity(); try { - b = cp.call(cr.getPackageName(), cr.getAttributionTag(), + b = cp.call(cr.getAttributionSource(), mProviderHolder.mUri.getAuthority(), mCallGetCommand, name, args); } finally { Binder.restoreCallingIdentity(token); } } else { - b = cp.call(cr.getPackageName(), cr.getAttributionTag(), + b = cp.call(cr.getAttributionSource(), mProviderHolder.mUri.getAuthority(), mCallGetCommand, name, args); } if (b != null) { @@ -2939,13 +2939,13 @@ public final class Settings { if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) { final long token = Binder.clearCallingIdentity(); try { - c = cp.query(cr.getPackageName(), cr.getAttributionTag(), mUri, + c = cp.query(cr.getAttributionSource(), mUri, SELECT_VALUE_PROJECTION, queryArgs, null); } finally { Binder.restoreCallingIdentity(token); } } else { - c = cp.query(cr.getPackageName(), cr.getAttributionTag(), mUri, + c = cp.query(cr.getAttributionSource(), mUri, SELECT_VALUE_PROJECTION, queryArgs, null); } if (c == null) { @@ -3051,7 +3051,7 @@ public final class Settings { } // Fetch all flags for the namespace at once for caching purposes - Bundle b = cp.call(cr.getPackageName(), cr.getAttributionTag(), + Bundle b = cp.call(cr.getAttributionSource(), mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args); if (b == null) { // Invalid response, return an empty map @@ -5877,7 +5877,7 @@ public final class Settings { } arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode); IContentProvider cp = sProviderHolder.getProvider(resolver); - cp.call(resolver.getPackageName(), resolver.getAttributionTag(), + cp.call(resolver.getAttributionSource(), sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_SECURE, null, arg); } catch (RemoteException e) { Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e); @@ -14914,7 +14914,7 @@ public final class Settings { } arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode); IContentProvider cp = sProviderHolder.getProvider(resolver); - cp.call(resolver.getPackageName(), resolver.getAttributionTag(), + cp.call(resolver.getAttributionSource(), sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_GLOBAL, null, arg); } catch (RemoteException e) { Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e); @@ -16053,7 +16053,7 @@ public final class Settings { arg.putString(Settings.CALL_METHOD_PREFIX_KEY, createPrefix(namespace)); } IContentProvider cp = sProviderHolder.getProvider(resolver); - cp.call(resolver.getPackageName(), resolver.getAttributionTag(), + cp.call(resolver.getAttributionSource(), sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_CONFIG, null, arg); } catch (RemoteException e) { Log.w(TAG, "Can't reset to defaults for " + DeviceConfig.CONTENT_URI, e); @@ -16082,7 +16082,7 @@ public final class Settings { arg.putInt(CALL_METHOD_USER_KEY, userHandle); arg.putParcelable(CALL_METHOD_MONITOR_CALLBACK_KEY, callback); IContentProvider cp = sProviderHolder.getProvider(resolver); - cp.call(resolver.getPackageName(), resolver.getAttributionTag(), + cp.call(resolver.getAttributionSource(), sProviderHolder.mUri.getAuthority(), CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG, null, arg); } catch (RemoteException e) { diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl index cc1cdedd0f96..9a5e534a68cf 100644 --- a/core/java/android/speech/IRecognitionService.aidl +++ b/core/java/android/speech/IRecognitionService.aidl @@ -17,6 +17,7 @@ package android.speech; import android.os.Bundle; +import android.content.AttributionSource; import android.content.Intent; import android.speech.IRecognitionListener; @@ -39,11 +40,10 @@ oneway interface IRecognitionService { * this intent can contain extra parameters to manipulate the behavior of the recognition * client. For more information see {@link RecognizerIntent}. * @param listener to receive callbacks, note that this must be non-null - * @param packageName the package name calling this API - * @param featureId The feature in the package + * @param attributionSource The attribution source of the caller. */ void startListening(in Intent recognizerIntent, in IRecognitionListener listener, - String packageName, String featureId, int callingUid); + in AttributionSource attributionSource); /** * Stops listening for speech. Speech captured so far will be recognized as @@ -51,18 +51,14 @@ oneway interface IRecognitionService { * is called during the speech capturing. * * @param listener to receive callbacks, note that this must be non-null - * @param packageName the package name calling this API - * @param featureId The feature in the package */ - void stopListening(in IRecognitionListener listener, String packageName, String featureId); + void stopListening(in IRecognitionListener listener); /** * Cancels the speech recognition. * * @param listener to receive callbacks, note that this must be non-null * @param packageName the package name calling this API - * @param featureId The feature in the package - * @param isShutdown Whether the cancellation is caused by a client calling #shutdown */ - void cancel(in IRecognitionListener listener, String packageName, String featureId, boolean isShutdown); + void cancel(in IRecognitionListener listener, boolean isShutdown); } diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java index fd584f191743..4afa94735d62 100644 --- a/core/java/android/speech/RecognitionService.java +++ b/core/java/android/speech/RecognitionService.java @@ -16,11 +16,16 @@ package android.speech; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; +import android.app.AppOpsManager; import android.app.Service; +import android.content.Context; +import android.content.ContextParams; import android.content.Intent; import android.content.PermissionChecker; import android.os.Binder; @@ -28,13 +33,13 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.content.AttributionSource; import android.os.Process; import android.os.RemoteException; import android.util.Log; -import com.android.internal.util.Preconditions; - import java.lang.ref.WeakReference; +import java.util.Objects; /** * This class provides a base class for recognition service implementations. This class should be @@ -86,13 +91,13 @@ public abstract class RecognitionService extends Service { switch (msg.what) { case MSG_START_LISTENING: StartListeningArgs args = (StartListeningArgs) msg.obj; - dispatchStartListening(args.mIntent, args.mListener, args.mCallingUid); + dispatchStartListening(args.mIntent, args.mListener, args.mAttributionSource); break; case MSG_STOP_LISTENING: dispatchStopListening((IRecognitionListener) msg.obj); break; case MSG_CANCEL: - dispatchCancel((IRecognitionListener) msg.obj); + dispatchCancel((IRecognitionListener) msg.obj, msg.arg1 == 1); break; case MSG_RESET: dispatchClearCallback(); @@ -102,10 +107,11 @@ public abstract class RecognitionService extends Service { }; private void dispatchStartListening(Intent intent, final IRecognitionListener listener, - int callingUid) { + @NonNull AttributionSource attributionSource) { if (mCurrentCallback == null) { if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder()); - mCurrentCallback = new Callback(listener, callingUid); + mCurrentCallback = new Callback(listener, attributionSource); + RecognitionService.this.onStartListening(intent, mCurrentCallback); } else { try { @@ -133,13 +139,16 @@ public abstract class RecognitionService extends Service { } } - private void dispatchCancel(IRecognitionListener listener) { + private void dispatchCancel(IRecognitionListener listener, boolean shutDown) { if (mCurrentCallback == null) { if (DBG) Log.d(TAG, "cancel called with no preceding startListening - ignoring"); } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) { Log.w(TAG, "cancel called by client who did not call startListening - ignoring"); } else { // the correct state RecognitionService.this.onCancel(mCurrentCallback); + if (shutDown) { + mCurrentCallback.finishRecordAudioOpAttributionToCallerIfNeeded(); + } mCurrentCallback = null; if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null"); } @@ -153,12 +162,13 @@ public abstract class RecognitionService extends Service { public final Intent mIntent; public final IRecognitionListener mListener; - public final int mCallingUid; + public final @NonNull AttributionSource mAttributionSource; - public StartListeningArgs(Intent intent, IRecognitionListener listener, int callingUid) { + public StartListeningArgs(Intent intent, IRecognitionListener listener, + @NonNull AttributionSource attributionSource) { this.mIntent = intent; this.mListener = listener; - this.mCallingUid = callingUid; + this.mAttributionSource = attributionSource; } } @@ -247,18 +257,19 @@ public abstract class RecognitionService extends Service { */ public class Callback { private final IRecognitionListener mListener; - private final int mCallingUid; + private final @NonNull AttributionSource mCallingAttributionSource; + private @Nullable Context mAttributionContext; - private Callback(IRecognitionListener listener, int callingUid) { + private Callback(IRecognitionListener listener, + @NonNull AttributionSource attributionSource) { mListener = listener; - mCallingUid = callingUid; + mCallingAttributionSource = attributionSource; } /** * The service should call this method when the user has started to speak. */ public void beginningOfSpeech() throws RemoteException { - if (DBG) Log.d(TAG, "beginningOfSpeech"); mListener.onBeginningOfSpeech(); } @@ -270,6 +281,7 @@ public abstract class RecognitionService extends Service { * single channel audio stream. The sample rate is implementation dependent. */ public void bufferReceived(byte[] buffer) throws RemoteException { + startRecordAudioOpAttributionToCallerIfNeeded(); mListener.onBufferReceived(buffer); } @@ -302,6 +314,7 @@ public abstract class RecognitionService extends Service { * {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter */ public void partialResults(Bundle partialResults) throws RemoteException { + startRecordAudioOpAttributionToCallerIfNeeded(); mListener.onPartialResults(partialResults); } @@ -323,6 +336,7 @@ public abstract class RecognitionService extends Service { * {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter */ public void results(Bundle results) throws RemoteException { + startRecordAudioOpAttributionToCallerIfNeeded(); Message.obtain(mHandler, MSG_RESET).sendToTarget(); mListener.onResults(results); } @@ -342,7 +356,65 @@ public abstract class RecognitionService extends Service { * is being processed. This is obtained from {@link Binder#getCallingUid()}. */ public int getCallingUid() { - return mCallingUid; + return mCallingAttributionSource.getUid(); + } + + /** + * Gets the permission identity of the calling app. If you want to attribute + * the mic access to the calling app you can create an attribution context + * via {@link android.content.Context#createContext(android.content.ContextParams)} + * and passing this identity to {@link + * android.content.ContextParams.Builder#setNextAttributionSource(AttributionSource)}. + * + * + * + * + * @return The permission identity of the calling app. + * + * @see android.content.ContextParams.Builder#setNextAttributionSource( + * AttributionSource) + */ + @SuppressLint("CallbackMethodName") + public @NonNull AttributionSource getCallingAttributionSource() { + return mCallingAttributionSource; + } + + private void startRecordAudioOpAttributionToCallerIfNeeded() throws RemoteException { + if (!isProxyingRecordAudioToCaller()) { + final int result = PermissionChecker.checkPermissionAndStartDataDelivery( + RecognitionService.this, Manifest.permission.RECORD_AUDIO, + getAttributionContextForCaller().getAttributionSource(), + /*message*/ null); + if (result == PermissionChecker.PERMISSION_GRANTED) { + return; + } + error(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS); + } + } + + private @NonNull Context getAttributionContextForCaller() { + if (mAttributionContext == null) { + mAttributionContext = createContext(new ContextParams.Builder() + .setNextAttributionSource(mCallingAttributionSource) + .build()); + } + return mAttributionContext; + } + + void finishRecordAudioOpAttributionToCallerIfNeeded() { + if (isProxyingRecordAudioToCaller()) { + final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO); + PermissionChecker.finishDataDelivery(RecognitionService.this, + op, getAttributionContextForCaller().getAttributionSource()); + } + } + + private boolean isProxyingRecordAudioToCaller() { + final int op = AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO); + final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class); + return appOpsManager.isProxying(op, getAttributionTag(), + mCallingAttributionSource.getUid(), + mCallingAttributionSource.getPackageName()); } } @@ -356,44 +428,35 @@ public abstract class RecognitionService extends Service { @Override public void startListening(Intent recognizerIntent, IRecognitionListener listener, - String packageName, String featureId, int callingUid) { - Preconditions.checkNotNull(packageName); - + @NonNull AttributionSource attributionSource) { + Objects.requireNonNull(attributionSource); + attributionSource.enforceCallingUid(); if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder()); final RecognitionService service = mServiceRef.get(); - if (service != null && service.checkPermissions(listener, true /*forDataDelivery*/, - packageName, featureId)) { + if (service != null) { service.mHandler.sendMessage(Message.obtain(service.mHandler, MSG_START_LISTENING, service.new StartListeningArgs( - recognizerIntent, listener, callingUid))); + recognizerIntent, listener, attributionSource))); } } @Override - public void stopListening(IRecognitionListener listener, String packageName, - String featureId) { - Preconditions.checkNotNull(packageName); - + public void stopListening(IRecognitionListener listener) { if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder()); final RecognitionService service = mServiceRef.get(); - if (service != null && service.checkPermissions(listener, false /*forDataDelivery*/, - packageName, featureId)) { + if (service != null) { service.mHandler.sendMessage(Message.obtain(service.mHandler, MSG_STOP_LISTENING, listener)); } } @Override - public void cancel(IRecognitionListener listener, String packageName, - String featureId, boolean isShutdown) { - Preconditions.checkNotNull(packageName); - + public void cancel(IRecognitionListener listener, boolean isShutdown) { if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder()); final RecognitionService service = mServiceRef.get(); - if (service != null && service.checkPermissions(listener, false /*forDataDelivery*/, - packageName, featureId)) { + if (service != null) { service.mHandler.sendMessage(Message.obtain(service.mHandler, - MSG_CANCEL, listener)); + MSG_CANCEL, isShutdown ? 1 : 0, 0, listener)); } } diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 9b93a64e48a3..7aa5ee51b606 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -386,8 +386,7 @@ public class SpeechRecognizer { return; } try { - mService.startListening(recognizerIntent, mListener, mContext.getOpPackageName(), - mContext.getAttributionTag(), android.os.Process.myUid()); + mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource()); if (DBG) Log.d(TAG, "service start listening command succeded"); } catch (final RemoteException e) { Log.e(TAG, "startListening() failed", e); @@ -401,8 +400,7 @@ public class SpeechRecognizer { return; } try { - mService.stopListening(mListener, mContext.getOpPackageName(), - mContext.getAttributionTag()); + mService.stopListening(mListener); if (DBG) Log.d(TAG, "service stop listening command succeded"); } catch (final RemoteException e) { Log.e(TAG, "stopListening() failed", e); @@ -416,11 +414,7 @@ public class SpeechRecognizer { return; } try { - mService.cancel( - mListener, - mContext.getOpPackageName(), - mContext.getAttributionTag(), - false /* isShutdown */); + mService.cancel(mListener, /*isShutdown*/ false); if (DBG) Log.d(TAG, "service cancel command succeded"); } catch (final RemoteException e) { Log.e(TAG, "cancel() failed", e); @@ -463,8 +457,7 @@ public class SpeechRecognizer { public void destroy() { if (mService != null) { try { - mService.cancel(mListener, mContext.getOpPackageName(), - mContext.getAttributionTag(), true /* isShutdown */); + mService.cancel(mListener, /*isShutdown*/ true); } catch (final RemoteException e) { // Not important } |
