diff options
| author | Svetoslav Ganov <svetoslavganov@google.com> | 2021-06-02 23:07:22 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2021-06-02 23:07:22 +0000 |
| commit | 1028eb2f62cc0fd4529039e6bc19d80d1759706b (patch) | |
| tree | 4b7ce5b1fc2c7cb6a5c6dc316597d016910c6b2f /core/java/android | |
| parent | 13b9214a174f81c51b6ac0727a405e502af912ab (diff) | |
| parent | 2eebf929650e0d320a21f0d13677a27d7ab278e9 (diff) | |
Merge "Switch media fw permissions checks to AttributionSource" into sc-dev
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/app/AppOpsManager.java | 177 | ||||
| -rw-r--r-- | core/java/android/app/AppOpsManagerInternal.java | 40 | ||||
| -rw-r--r-- | core/java/android/app/ContextImpl.java | 8 | ||||
| -rw-r--r-- | core/java/android/app/SystemServiceRegistry.java | 9 | ||||
| -rw-r--r-- | core/java/android/content/AttributionSource.java | 131 | ||||
| -rw-r--r-- | core/java/android/content/ContentProvider.java | 13 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 8 | ||||
| -rw-r--r-- | core/java/android/content/PermissionChecker.java | 139 | ||||
| -rw-r--r-- | core/java/android/permission/IPermissionManager.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/permission/PermissionCheckerManager.java | 186 | ||||
| -rw-r--r-- | core/java/android/permission/PermissionManager.java | 5 | ||||
| -rw-r--r-- | core/java/android/speech/RecognitionService.java | 125 |
12 files changed, 619 insertions, 224 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 1415212ef07d..eb14c11e7e3c 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -708,6 +708,62 @@ public class AppOpsManager { } } + /** + * Attribution chain flag: specifies that this is the accessor. When + * an app A accesses the data that is then passed to app B that is then + * passed to C, we call app A accessor, app B intermediary, and app C + * receiver. If A accesses the data for itself, then it is the accessor + * and the receiver. + * @hide + */ + @TestApi + public static final int ATTRIBUTION_FLAG_ACCESSOR = 0x1; + + /** + * Attribution chain flag: specifies that this is the intermediary. When + * an app A accesses the data that is then passed to app B that is then + * passed to C, we call app A accessor, app B intermediary, and app C + * receiver. If A accesses the data for itself, then it is the accessor + * and the receiver. + * @hide + */ + @TestApi + public static final int ATTRIBUTION_FLAG_INTERMEDIARY = 0x2; + + /** + * Attribution chain flag: specifies that this is the receiver. When + * an app A accesses the data that is then passed to app B that is then + * passed to C, we call app A accessor, app B intermediary, and app C + * receiver. If A accesses the data for itself, then it is the accessor + * and the receiver. + * @hide + */ + @TestApi + public static final int ATTRIBUTION_FLAG_RECEIVER = 0x4; + + /** + * No attribution flags. + * @hide + */ + @TestApi + public static final int ATTRIBUTION_FLAGS_NONE = 0x0; + + /** + * No attribution chain id. + * @hide + */ + @TestApi + public static final int ATTRIBUTION_CHAIN_ID_NONE = -1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "FLAG_" }, value = { + ATTRIBUTION_FLAG_ACCESSOR, + ATTRIBUTION_FLAG_INTERMEDIARY, + ATTRIBUTION_FLAG_RECEIVER + }) + public @interface AttributionFlags {} + // These constants are redefined here to work around a metalava limitation/bug where // @IntDef is not able to see @hide symbols when they are hidden via package hiding: // frameworks/base/core/java/com/android/internal/package.html @@ -7063,6 +7119,25 @@ public class AppOpsManager { */ void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName, boolean active); + + /** + * Called when the active state of an app-op changes. + * + * @param op The operation that changed. + * @param uid The UID performing the operation. + * @param packageName The package performing the operation. + * @param attributionTag The operation's attribution tag. + * @param active Whether the operation became active or inactive. + * @param attributionFlags the attribution flags for this operation. + * @param attributionChainId the unique id of the attribution chain this op is a part of. + * @hide + */ + @TestApi + default void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName, + @Nullable String attributionTag, boolean active, @AttributionFlags + int attributionFlags, int attributionChainId) { + onOpActiveChanged(op, uid, packageName, active); + } } /** @@ -7684,14 +7759,17 @@ public class AppOpsManager { } cb = new IAppOpsActiveCallback.Stub() { @Override - public void opActiveChanged(int op, int uid, String packageName, boolean active) { + public void opActiveChanged(int op, int uid, String packageName, + String attributionTag, boolean active, @AttributionFlags + int attributionFlags, int attributionChainId) { executor.execute(() -> { if (callback instanceof OnOpActiveChangedInternalListener) { ((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op, uid, packageName, active); } if (sOpToString[op] != null) { - callback.onOpActiveChanged(sOpToString[op], uid, packageName, active); + callback.onOpActiveChanged(sOpToString[op], uid, packageName, + attributionTag, active, attributionFlags, attributionChainId); } }); } @@ -8169,8 +8247,9 @@ public class AppOpsManager { public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) { return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(), - new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)), - message, /*skipProxyOperation*/ false); + new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag, + mContext.getAttributionSource().getToken())), message, + /*skipProxyOperation*/ false); } /** @@ -8255,8 +8334,8 @@ public class AppOpsManager { int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) { return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource( mContext.getAttributionSource(), new AttributionSource(proxiedUid, - proxiedPackageName, proxiedAttributionTag)), message, - /*skipProxyOperation*/ false); + proxiedPackageName, proxiedAttributionTag, mContext.getAttributionSource() + .getToken())), message,/*skipProxyOperation*/ false); } /** @@ -8592,6 +8671,29 @@ public class AppOpsManager { */ public int startOpNoThrow(int op, int uid, @NonNull String packageName, boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message) { + return startOpNoThrow(mContext.getAttributionSource().getToken(), op, uid, packageName, + startIfModeDefault, attributionTag, message); + } + + /** + * @see #startOpNoThrow(String, int, String, String, String) + * + * @hide + */ + public int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName, + boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message) { + return startOpNoThrow(token, op, uid, packageName, startIfModeDefault, attributionTag, + message, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE); + } + + /** + * @see #startOpNoThrow(String, int, String, String, String) + * + * @hide + */ + public int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName, + boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message, + @AttributionFlags int attributionFlags, int attributionChainId) { try { collectNoteOpCallsForValidation(op); int collectionMode = getNotedOpCollectionMode(uid, packageName, op); @@ -8604,9 +8706,9 @@ public class AppOpsManager { } } - SyncNotedAppOp syncOp = mService.startOperation(getClientId(), op, uid, packageName, + SyncNotedAppOp syncOp = mService.startOperation(token, op, uid, packageName, attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message, - shouldCollectMessage); + shouldCollectMessage, attributionFlags, attributionChainId); if (syncOp.getOpMode() == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { @@ -8643,8 +8745,9 @@ public class AppOpsManager { public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag, @Nullable String message) { return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(), - new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)), - message, /*skipProxyOperation*/ false); + new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag, + mContext.getAttributionSource().getToken())), message, + /*skipProxyOperation*/ false); } /** @@ -8690,8 +8793,9 @@ public class AppOpsManager { @Nullable String message) { return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource( mContext.getAttributionSource(), new AttributionSource(proxiedUid, - proxiedPackageName, proxiedAttributionTag)), message, - /*skipProxyOperation*/ false); + proxiedPackageName, proxiedAttributionTag, + mContext.getAttributionSource().getToken())), message, + /*skipProxyOperation*/ false); } /** @@ -8705,6 +8809,23 @@ public class AppOpsManager { */ public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource, @Nullable String message, boolean skipProxyOperation) { + return startProxyOpNoThrow(op, attributionSource, message, skipProxyOperation, + ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE); + } + + /** + * 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, @AttributionFlags + int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags, + int attributionChainId) { try { collectNoteOpCallsForValidation(op); int collectionMode = getNotedOpCollectionMode( @@ -8719,9 +8840,10 @@ public class AppOpsManager { } } - SyncNotedAppOp syncOp = mService.startProxyOperation(getClientId(), op, + SyncNotedAppOp syncOp = mService.startProxyOperation(op, attributionSource, false, collectionMode == COLLECT_ASYNC, message, - shouldCollectMessage, skipProxyOperation); + shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, + proxiedAttributionFlags, attributionChainId); if (syncOp.getOpMode() == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { @@ -8785,8 +8907,18 @@ public class AppOpsManager { */ public void finishOp(int op, int uid, @NonNull String packageName, @Nullable String attributionTag) { + finishOp(mContext.getAttributionSource().getToken(), op, uid, packageName, attributionTag); + } + + /** + * @see #finishOp(String, int, String, String) + * + * @hide + */ + public void finishOp(IBinder token, int op, int uid, @NonNull String packageName, + @Nullable String attributionTag) { try { - mService.finishOperation(getClientId(), op, uid, packageName, attributionTag); + mService.finishOperation(token, op, uid, packageName, attributionTag); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -8807,23 +8939,26 @@ public class AppOpsManager { 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))); + new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag, + mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false); } /** * 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. + * been started with {@link #startProxyOp(String, AttributionSource, String, boolean)}. 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 + * @param skipProxyOperation Whether to skip the proxy finish. * * @hide */ - public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource) { + public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource, + boolean skipProxyOperation) { try { - mService.finishProxyOperation(getClientId(), strOpToOp(op), attributionSource); + mService.finishProxyOperation(strOpToOp(op), attributionSource, skipProxyOperation); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java index 2de0ddb17646..a757e32d0d75 100644 --- a/core/java/android/app/AppOpsManagerInternal.java +++ b/core/java/android/app/AppOpsManagerInternal.java @@ -16,6 +16,7 @@ package android.app; +import android.app.AppOpsManager.AttributionFlags; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.AttributionSource; @@ -24,13 +25,13 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.app.IAppOpsCallback; +import com.android.internal.util.function.DecFunction; import com.android.internal.util.function.HeptFunction; import com.android.internal.util.function.HexFunction; -import com.android.internal.util.function.NonaFunction; -import com.android.internal.util.function.OctFunction; import com.android.internal.util.function.QuadFunction; import com.android.internal.util.function.QuintFunction; import com.android.internal.util.function.TriFunction; +import com.android.internal.util.function.UndecFunction; /** * App ops service local interface. @@ -52,8 +53,8 @@ public abstract class AppOpsManagerInternal { * @return The app op check result. */ int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag, - boolean raw, - QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl); + boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer> + superImpl); /** * Allows overriding check audio operation behavior. @@ -116,20 +117,22 @@ public abstract class AppOpsManagerInternal { * @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 attributionFlags the attribution flags for this operation. + * @param attributionChainId the unique id of the attribution chain this op is a part of. * @param superImpl The super implementation. * @return The app op note result. */ SyncNotedAppOp startOperation(IBinder token, int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, - @Nullable String message, boolean shouldCollectMessage, @NonNull NonaFunction< - IBinder, Integer, Integer, String, String, Boolean, Boolean, String, - Boolean, SyncNotedAppOp> superImpl); + @Nullable String message, boolean shouldCollectMessage, + @AttributionFlags int attributionFlags, int attributionChainId, + @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean, + Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl); /** * Allows overriding start proxy operation behavior. * - * @param token The client state. * @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. @@ -137,26 +140,29 @@ public abstract class AppOpsManagerInternal { * @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 proxyAttributionFlags The attribution flags for the proxy. + * @param proxiedAttributionFlags The attribution flags for the proxied. + * @oaram attributionChainId The id of the attribution chain this operation is a part of. * @param superImpl The super implementation. * @return The app op note result. */ - SyncNotedAppOp 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, + SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource, + boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, + boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags + int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags, + int attributionChainId, @NonNull DecFunction<Integer, AttributionSource, Boolean, + Boolean, String, Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> 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); + void finishProxyOperation(int code, @NonNull AttributionSource attributionSource, + boolean skipProxyOperation, + @NonNull TriFunction<Integer, AttributionSource, Boolean, Void> superImpl); } /** diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index daef8b1eae08..16b6ea5bcf42 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -3157,13 +3157,7 @@ class ContextImpl extends Context { // 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 registeredAttributionSource = getSystemService( - PermissionManager.class).registerAttributionSource(attributionSource); - if (registeredAttributionSource != null) { - return registeredAttributionSource; - } + getSystemService(PermissionManager.class).registerAttributionSource(attributionSource); } return attributionSource; } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 91dad2a1f13c..871d48b07a20 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -186,6 +186,7 @@ import android.os.incremental.IIncrementalService; import android.os.incremental.IncrementalManager; import android.os.storage.StorageManager; import android.permission.LegacyPermissionManager; +import android.permission.PermissionCheckerManager; import android.permission.PermissionControllerManager; import android.permission.PermissionManager; import android.print.IPrintManager; @@ -1334,6 +1335,14 @@ public final class SystemServiceRegistry { ctx.getMainThreadHandler()); }}); + registerService(Context.PERMISSION_CHECKER_SERVICE, PermissionCheckerManager.class, + new CachedServiceFetcher<PermissionCheckerManager>() { + @Override + public PermissionCheckerManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + return new PermissionCheckerManager(ctx.getOuterContext()); + }}); + registerService(Context.DYNAMIC_SYSTEM_SERVICE, DynamicSystemManager.class, new CachedServiceFetcher<DynamicSystemManager>() { @Override diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 7ab731f15ad2..1dda6374a474 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.ActivityThread; import android.os.Binder; import android.os.Build; import android.os.IBinder; @@ -94,14 +95,22 @@ public final class AttributionSource implements Parcelable { @TestApi public AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag) { - this(uid, packageName, attributionTag, /*next*/ null); + this(uid, packageName, attributionTag, new Binder()); } /** @hide */ @TestApi public AttributionSource(int uid, @Nullable String packageName, - @Nullable String attributionTag, @Nullable AttributionSource next) { - this(uid, packageName, attributionTag, /*renouncedPermissions*/ null, next); + @Nullable String attributionTag, @NonNull IBinder token) { + this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, + /*next*/ null); + } + + /** @hide */ + public AttributionSource(int uid, @Nullable String packageName, + @Nullable String attributionTag, @NonNull IBinder token, + @Nullable AttributionSource next) { + this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next); } /** @hide */ @@ -109,28 +118,32 @@ public final class AttributionSource implements Parcelable { public AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions, @Nullable AttributionSource next) { - this(uid, packageName, attributionTag, /*token*/ null, (renouncedPermissions != null) + this(uid, packageName, attributionTag, (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null, next); } /** @hide */ - public AttributionSource(@NonNull AttributionSource current, - @Nullable AttributionSource next) { + public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) { this(current.getUid(), current.getPackageName(), current.getAttributionTag(), - /*token*/ null, /*renouncedPermissions*/ null, next); + current.getToken(), current.mAttributionSourceState.renouncedPermissions, next); } AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, - @Nullable IBinder token, @Nullable String[] renouncedPermissions, + @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) { + this(uid, packageName, attributionTag, new Binder(), renouncedPermissions, next); + } + + AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, + @NonNull IBinder token, @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) { mAttributionSourceState = new AttributionSourceState(); mAttributionSourceState.uid = uid; + mAttributionSourceState.token = token; mAttributionSourceState.packageName = packageName; mAttributionSourceState.attributionTag = attributionTag; - mAttributionSourceState.token = token; mAttributionSourceState.renouncedPermissions = renouncedPermissions; mAttributionSourceState.next = (next != null) ? new AttributionSourceState[] - {next.mAttributionSourceState} : null; + {next.mAttributionSourceState} : new AttributionSourceState[0]; } AttributionSource(@NonNull Parcel in) { @@ -145,18 +158,12 @@ public final class AttributionSource implements Parcelable { /** @hide */ public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) { return new AttributionSource(getUid(), getPackageName(), getAttributionTag(), - getToken(), mAttributionSourceState.renouncedPermissions, next); - } - - /** @hide */ - public AttributionSource withToken(@Nullable IBinder token) { - return new AttributionSource(getUid(), getPackageName(), getAttributionTag(), - token, mAttributionSourceState.renouncedPermissions, getNext()); + mAttributionSourceState.renouncedPermissions, next); } /** @hide */ public AttributionSource withPackageName(@Nullable String packageName) { - return new AttributionSource(getUid(), packageName, getAttributionTag(), getToken(), + return new AttributionSource(getUid(), packageName, getAttributionTag(), mAttributionSourceState.renouncedPermissions, getNext()); } @@ -165,6 +172,45 @@ public final class AttributionSource implements Parcelable { return mAttributionSourceState; } + /** @hide */ + public @NonNull ScopedParcelState asScopedParcelState() { + return new ScopedParcelState(this); + } + + /** @hide */ + public static AttributionSource myAttributionSource() { + return new AttributionSource(Process.myUid(), ActivityThread.currentOpPackageName(), + /*attributionTag*/ null, (String[]) /*renouncedPermissions*/ null, /*next*/ null); + } + + /** + * This is a scoped object that exposes the content of an attribution source + * as a parcel. This is useful when passing one to native and avoid custom + * conversion logic from Java to native state that needs to be kept in sync + * as attribution source evolves. This way we use the same logic for passing + * to native as the ones for passing in an IPC - in both cases this is the + * same auto generated code. + * + * @hide + */ + public static class ScopedParcelState implements AutoCloseable { + private final Parcel mParcel; + + public @NonNull Parcel getParcel() { + return mParcel; + } + + public ScopedParcelState(AttributionSource attributionSource) { + mParcel = Parcel.obtain(); + attributionSource.writeToParcel(mParcel, 0); + mParcel.setDataPosition(0); + } + + public void close() { + mParcel.recycle(); + } + } + /** * 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 @@ -209,7 +255,8 @@ public final class AttributionSource implements Parcelable { "attributionTag = " + mAttributionSourceState.attributionTag + ", " + "token = " + mAttributionSourceState.token + ", " + "next = " + (mAttributionSourceState.next != null - ? mAttributionSourceState.next[0]: null) + + && mAttributionSourceState.next.length > 0 + ? mAttributionSourceState.next[0] : null) + " }"; } return super.toString(); @@ -221,7 +268,8 @@ public final class AttributionSource implements Parcelable { * @hide */ public int getNextUid() { - if (mAttributionSourceState.next != null) { + if (mAttributionSourceState.next != null + && mAttributionSourceState.next.length > 0) { return mAttributionSourceState.next[0].uid; } return Process.INVALID_UID; @@ -233,26 +281,42 @@ public final class AttributionSource implements Parcelable { * @hide */ public @Nullable String getNextPackageName() { - if (mAttributionSourceState.next != null) { + if (mAttributionSourceState.next != null + && mAttributionSourceState.next.length > 0) { return mAttributionSourceState.next[0].packageName; } return null; } /** - * @return The nexxt package's attribution tag that would receive + * @return The next package's attribution tag that would receive * the permission protected data. * * @hide */ public @Nullable String getNextAttributionTag() { - if (mAttributionSourceState.next != null) { + if (mAttributionSourceState.next != null + && mAttributionSourceState.next.length > 0) { return mAttributionSourceState.next[0].attributionTag; } return null; } /** + * @return The next package's token that would receive + * the permission protected data. + * + * @hide + */ + public @Nullable IBinder getNextToken() { + if (mAttributionSourceState.next != null + && mAttributionSourceState.next.length > 0) { + return mAttributionSourceState.next[0].token; + } + 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. * @@ -311,7 +375,7 @@ public final class AttributionSource implements Parcelable { * * @hide */ - public @Nullable IBinder getToken() { + public @NonNull IBinder getToken() { return mAttributionSourceState.token; } @@ -319,7 +383,8 @@ public final class AttributionSource implements Parcelable { * The next app to receive the permission protected data. */ public @Nullable AttributionSource getNext() { - if (mNextCached == null && mAttributionSourceState.next != null) { + if (mNextCached == null && mAttributionSourceState.next != null + && mAttributionSourceState.next.length > 0) { mNextCached = new AttributionSource(mAttributionSourceState.next[0]); } return mNextCached; @@ -442,7 +507,7 @@ public final class AttributionSource implements Parcelable { @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) { checkNotUsed(); - mBuilderFieldsSet |= 0x10; + mBuilderFieldsSet |= 0x8; mAttributionSourceState.renouncedPermissions = (value != null) ? value.toArray(new String[0]) : null; return this; @@ -453,9 +518,9 @@ public final class AttributionSource implements Parcelable { */ public @NonNull Builder setNext(@Nullable AttributionSource value) { checkNotUsed(); - mBuilderFieldsSet |= 0x20; + mBuilderFieldsSet |= 0x10; mAttributionSourceState.next = (value != null) ? new AttributionSourceState[] - {value.mAttributionSourceState} : null; + {value.mAttributionSourceState} : mAttributionSourceState.next; return this; } @@ -471,14 +536,16 @@ public final class AttributionSource implements Parcelable { mAttributionSourceState.attributionTag = null; } if ((mBuilderFieldsSet & 0x8) == 0) { - mAttributionSourceState.token = null; - } - if ((mBuilderFieldsSet & 0x10) == 0) { mAttributionSourceState.renouncedPermissions = null; } - if ((mBuilderFieldsSet & 0x20) == 0) { + if ((mBuilderFieldsSet & 0x10) == 0) { mAttributionSourceState.next = null; } + mAttributionSourceState.token = new Binder(); + if (mAttributionSourceState.next == null) { + // The NDK aidl backend doesn't support null parcelable arrays. + mAttributionSourceState.next = new AttributionSourceState[0]; + } return new AttributionSource(mAttributionSourceState); } diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 88686a309ee4..dc29c5e25f3c 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -49,6 +49,7 @@ import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; +import android.permission.PermissionCheckerManager; import android.text.TextUtils; import android.util.Log; @@ -670,7 +671,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } } - @PermissionChecker.PermissionResult + @PermissionCheckerManager.PermissionResult private void enforceFilePermission(@NonNull AttributionSource attributionSource, Uri uri, String mode) throws FileNotFoundException, SecurityException { @@ -687,7 +688,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } } - @PermissionChecker.PermissionResult + @PermissionCheckerManager.PermissionResult private int enforceReadPermission(@NonNull AttributionSource attributionSource, Uri uri) throws SecurityException { final int result = enforceReadPermissionInner(uri, attributionSource); @@ -705,7 +706,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall return PermissionChecker.PERMISSION_GRANTED; } - @PermissionChecker.PermissionResult + @PermissionCheckerManager.PermissionResult private int enforceWritePermission(@NonNull AttributionSource attributionSource, Uri uri) throws SecurityException { final int result = enforceWritePermissionInner(uri, attributionSource); @@ -738,7 +739,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * Verify that calling app holds both the given permission and any app-op * associated with that permission. */ - @PermissionChecker.PermissionResult + @PermissionCheckerManager.PermissionResult private int checkPermission(String permission, @NonNull AttributionSource attributionSource) { if (Binder.getCallingPid() == Process.myPid()) { @@ -753,7 +754,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } /** {@hide} */ - @PermissionChecker.PermissionResult + @PermissionCheckerManager.PermissionResult protected int enforceReadPermissionInner(Uri uri, @NonNull AttributionSource attributionSource) throws SecurityException { final Context context = getContext(); @@ -836,7 +837,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } /** {@hide} */ - @PermissionChecker.PermissionResult + @PermissionCheckerManager.PermissionResult protected int enforceWritePermissionInner(Uri uri, @NonNull AttributionSource attributionSource) throws SecurityException { final Context context = getContext(); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 886cd7f48d35..ea0e321377c8 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4836,6 +4836,14 @@ public abstract class Context { public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller"; /** + * Official published name of the (internal) permission checker service. + * + * @see #getSystemService(String) + * @hide + */ + public static final String PERMISSION_CHECKER_SERVICE = "permission_checker"; + + /** * Use with {@link #getSystemService(String) to retrieve an * {@link android.apphibernation.AppHibernationManager}} for * communicating with the hibernation service. diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java index 66e088359459..a167cb3d851f 100644 --- a/core/java/android/content/PermissionChecker.java +++ b/core/java/android/content/PermissionChecker.java @@ -16,19 +16,14 @@ package android.content; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.os.Binder; -import android.os.IBinder; import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; import android.permission.IPermissionChecker; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import android.permission.PermissionCheckerManager; +import android.permission.PermissionCheckerManager.PermissionResult; /** * This class provides permission check APIs that verify both the @@ -75,7 +70,7 @@ public final class PermissionChecker { * * @hide */ - public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED; + public static final int PERMISSION_GRANTED = PermissionCheckerManager.PERMISSION_GRANTED; /** * The permission is denied. Applicable only to runtime and app op permissions. @@ -89,7 +84,8 @@ public final class PermissionChecker { * * @hide */ - public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED; + public static final int PERMISSION_SOFT_DENIED = + PermissionCheckerManager.PERMISSION_SOFT_DENIED; /** * The permission is denied. @@ -103,18 +99,12 @@ public final class PermissionChecker { * * @hide */ - public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED; + public static final int PERMISSION_HARD_DENIED = + PermissionCheckerManager.PERMISSION_HARD_DENIED; /** Constant when the PID for which we check permissions is unknown. */ public static final int PID_UNKNOWN = -1; - /** @hide */ - @IntDef({PERMISSION_GRANTED, - PERMISSION_SOFT_DENIED, - PERMISSION_HARD_DENIED}) - @Retention(RetentionPolicy.SOURCE) - public @interface PermissionResult {} - private static volatile IPermissionChecker sService; private PermissionChecker() { @@ -157,7 +147,7 @@ public final class PermissionChecker { * * @see #checkPermissionForPreflight(Context, String, int, int, String) */ - @PermissionResult + @PermissionCheckerManager.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) { @@ -321,19 +311,13 @@ public final class PermissionChecker { message, startDataDelivery, /*fromDatasource*/ false); } + @SuppressWarnings("ConstantConditions") private static int checkPermissionForDataDeliveryCommon(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean startDataDelivery, boolean fromDatasource) { - // If the check failed in the middle of the chain, finish any started op. - try { - final int result = getPermissionCheckerService().checkPermission(permission, - attributionSource.asState(), message, true /*forDataDelivery*/, - startDataDelivery, fromDatasource); - return result; - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return PERMISSION_HARD_DENIED; + return context.getSystemService(PermissionCheckerManager.class).checkPermission(permission, + attributionSource.asState(), message, true /*forDataDelivery*/, startDataDelivery, + fromDatasource, AppOpsManager.OP_NONE); } /** @@ -365,17 +349,13 @@ public final class PermissionChecker { * @see #checkPermissionForPreflight(Context, String, AttributionSource) */ @PermissionResult + @SuppressWarnings("ConstantConditions") public static int checkPermissionAndStartDataDelivery(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message) { - try { - return getPermissionCheckerService().checkPermission(permission, - attributionSource.asState(), message, true /*forDataDelivery*/, - /*startDataDelivery*/ true, /*fromDatasource*/ false); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return PERMISSION_HARD_DENIED; + return context.getSystemService(PermissionCheckerManager.class).checkPermission( + permission, attributionSource.asState(), message, true /*forDataDelivery*/, + /*startDataDelivery*/ true, /*fromDatasource*/ false, AppOpsManager.OP_NONE); } /** @@ -404,17 +384,13 @@ public final class PermissionChecker { * @see #finishDataDelivery(Context, String, AttributionSource) */ @PermissionResult + @SuppressWarnings("ConstantConditions") public static int startOpForDataDelivery(@NonNull Context context, @NonNull String opName, @NonNull AttributionSource attributionSource, @Nullable String message) { - try { - return getPermissionCheckerService().checkOp( - AppOpsManager.strOpToOp(opName), attributionSource.asState(), message, - true /*forDataDelivery*/, true /*startDataDelivery*/); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return PERMISSION_HARD_DENIED; + return context.getSystemService(PermissionCheckerManager.class).checkOp( + AppOpsManager.strOpToOp(opName), attributionSource.asState(), message, + true /*forDataDelivery*/, true /*startDataDelivery*/); } /** @@ -428,13 +404,32 @@ public final class PermissionChecker { * @see #startOpForDataDelivery(Context, String, AttributionSource, String) * @see #checkPermissionAndStartDataDelivery(Context, String, AttributionSource, String) */ + @SuppressWarnings("ConstantConditions") public static void finishDataDelivery(@NonNull Context context, @NonNull String op, @NonNull AttributionSource attributionSource) { - try { - getPermissionCheckerService().finishDataDelivery(op, attributionSource.asState()); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } + context.getSystemService(PermissionCheckerManager.class).finishDataDelivery( + AppOpsManager.strOpToOp(op), attributionSource.asState(), + /*fromDatasource*/ false); + } + + /** + * Finishes an ongoing op for data access chain described by the given {@link + * AttributionSource}. Call this method if you are the datasource which would + * not finish an op for your attribution source as it was not started. + * + * @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) + */ + @SuppressWarnings("ConstantConditions") + public static void finishDataDeliveryFromDatasource(@NonNull Context context, + @NonNull String op, @NonNull AttributionSource attributionSource) { + context.getSystemService(PermissionCheckerManager.class).finishDataDelivery( + AppOpsManager.strOpToOp(op), attributionSource.asState(), + /*fromDatasource*/ true); } /** @@ -466,17 +461,13 @@ public final class PermissionChecker { * @see #checkOpForDataDelivery(Context, String, AttributionSource, String) */ @PermissionResult + @SuppressWarnings("ConstantConditions") public static int checkOpForPreflight(@NonNull Context context, @NonNull String opName, @NonNull AttributionSource attributionSource, @Nullable String message) { - try { - return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName), - attributionSource.asState(), message, false /*forDataDelivery*/, - false /*startDataDelivery*/); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return PERMISSION_HARD_DENIED; + return context.getSystemService(PermissionCheckerManager.class).checkOp( + AppOpsManager.strOpToOp(opName), attributionSource.asState(), message, + false /*forDataDelivery*/, false /*startDataDelivery*/); } /** @@ -505,17 +496,13 @@ public final class PermissionChecker { * @see #checkOpForPreflight(Context, String, AttributionSource, String) */ @PermissionResult + @SuppressWarnings("ConstantConditions") public static int checkOpForDataDelivery(@NonNull Context context, @NonNull String opName, @NonNull AttributionSource attributionSource, @Nullable String message) { - try { - return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName), - attributionSource.asState(), message, true /*forDataDelivery*/, - false /*startDataDelivery*/); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return PERMISSION_HARD_DENIED; + return context.getSystemService(PermissionCheckerManager.class).checkOp( + AppOpsManager.strOpToOp(opName), attributionSource.asState(), message, + true /*forDataDelivery*/, false /*startDataDelivery*/); } /** @@ -584,16 +571,13 @@ public final class PermissionChecker { * String, boolean) */ @PermissionResult + @SuppressWarnings("ConstantConditions") public static int checkPermissionForPreflight(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource) { - try { - return getPermissionCheckerService().checkPermission(permission, - attributionSource.asState(), null /*message*/, false /*forDataDelivery*/, - /*startDataDelivery*/ false, /*fromDatasource*/ false); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return PERMISSION_HARD_DENIED; + return context.getSystemService(PermissionCheckerManager.class) + .checkPermission(permission, attributionSource.asState(), null /*message*/, + false /*forDataDelivery*/, /*startDataDelivery*/ false, /*fromDatasource*/ false, + AppOpsManager.OP_NONE); } /** @@ -827,13 +811,4 @@ public final class PermissionChecker { return checkPermissionForPreflight(context, permission, Binder.getCallingPid(), Binder.getCallingUid(), packageName); } - - private static @NonNull IPermissionChecker getPermissionCheckerService() { - // Race is fine, we may end up looking up the same instance twice, no big deal. - if (sService == null) { - final IBinder service = ServiceManager.getService("permission_checker"); - sService = IPermissionChecker.Stub.asInterface(service); - } - return sService; - } } diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index ef075e1efbff..9ab69552f1a0 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -87,7 +87,7 @@ interface IPermissionManager { boolean isAutoRevokeExempted(String packageName, int userId); - AttributionSource registerAttributionSource(in AttributionSource source); + void registerAttributionSource(in AttributionSource source); boolean isRegisteredAttributionSource(in AttributionSource source); } diff --git a/core/java/android/permission/PermissionCheckerManager.java b/core/java/android/permission/PermissionCheckerManager.java new file mode 100644 index 000000000000..7523816250b0 --- /dev/null +++ b/core/java/android/permission/PermissionCheckerManager.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2018 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.permission; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.content.AttributionSourceState; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.os.ServiceManager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Manager for checking runtime and app op permissions. This is a temporary + * class and we may fold its function in the PermissionManager once the + * permission re-architecture starts falling into place. The main benefit + * of this class is to allow context level caching. + * + * @hide + */ +public class PermissionCheckerManager { + + /** + * The permission is granted. + */ + public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED; + + /** + * The permission is denied. Applicable only to runtime and app op permissions. + * + * <p>Returned when: + * <ul> + * <li>the runtime permission is granted, but the corresponding app op is denied + * for runtime permissions.</li> + * <li>the app ops is ignored for app op permissions.</li> + * </ul> + */ + public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED; + + /** + * The permission is denied. + * + * <p>Returned when: + * <ul> + * <li>the permission is denied for non app op permissions.</li> + * <li>the app op is denied or app op is {@link AppOpsManager#MODE_DEFAULT} + * and permission is denied.</li> + * </ul> + */ + public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED; + + /** @hide */ + @IntDef({PERMISSION_GRANTED, + PERMISSION_SOFT_DENIED, + PERMISSION_HARD_DENIED}) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionResult {} + + @NonNull + private final Context mContext; + + @NonNull + private final IPermissionChecker mService; + + @NonNull + private final PackageManager mPackageManager; + + public PermissionCheckerManager(@NonNull Context context) + throws ServiceManager.ServiceNotFoundException { + mContext = context; + mService = IPermissionChecker.Stub.asInterface(ServiceManager.getServiceOrThrow( + Context.PERMISSION_CHECKER_SERVICE)); + mPackageManager = context.getPackageManager(); + } + + /** + * Checks a permission by validating the entire attribution source chain. If the + * permission is associated with an app op the op is also noted/started for the + * entire attribution chain. + * + * @param permission The permission + * @param attributionSource The attribution chain to check. + * @param message Message associated with the permission if permission has an app op + * @param forDataDelivery Whether the check is for delivering data if permission has an app op + * @param startDataDelivery Whether to start data delivery (start op) if permission has + * an app op + * @param fromDatasource Whether the check is by a datasource (skip checks for the + * first attribution source in the chain as this is the datasource) + * @param attributedOp Alternative app op to attribute + * @return The permission check result. + */ + @PermissionResult + public int checkPermission(@NonNull String permission, + @NonNull AttributionSourceState attributionSource, @Nullable String message, + boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, + int attributedOp) { + Objects.requireNonNull(permission); + Objects.requireNonNull(attributionSource); + // Fast path for non-runtime, non-op permissions where the attribution chain has + // length one. This is the majority of the cases and we want these to be fast by + // hitting the local in process permission cache. + if (AppOpsManager.permissionToOpCode(permission) == AppOpsManager.OP_NONE) { + if (fromDatasource) { + if (attributionSource.next != null && attributionSource.next.length > 0) { + return mContext.checkPermission(permission, attributionSource.next[0].pid, + attributionSource.next[0].uid) == PackageManager.PERMISSION_GRANTED + ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED; + } + } else { + return (mContext.checkPermission(permission, attributionSource.pid, + attributionSource.uid) == PackageManager.PERMISSION_GRANTED) + ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED; + } + } + try { + return mService.checkPermission(permission, attributionSource, message, forDataDelivery, + startDataDelivery, fromDatasource, attributedOp); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return PERMISSION_HARD_DENIED; + } + + /** + * Finishes an app op by validating the entire attribution source chain. + * + * @param op The op to finish. + * @param attributionSource The attribution chain to finish. + * @param fromDatasource Whether the finish is by a datasource (skip finish for the + * first attribution source in the chain as this is the datasource) + */ + public void finishDataDelivery(int op, @NonNull AttributionSourceState attributionSource, + boolean fromDatasource) { + Objects.requireNonNull(attributionSource); + try { + mService.finishDataDelivery(op, attributionSource, fromDatasource); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Checks an app op by validating the entire attribution source chain. The op is + * also noted/started for the entire attribution chain. + * + * @param op The op to check. + * @param attributionSource The attribution chain to check. + * @param message Message associated with the permission if permission has an app op + * @param forDataDelivery Whether the check is for delivering data if permission has an app op + * @param startDataDelivery Whether to start data delivery (start op) if permission has + * an app op + * @return The op check result. + */ + @PermissionResult + public int checkOp(int op, @NonNull AttributionSourceState attributionSource, + @Nullable String message, boolean forDataDelivery, boolean startDataDelivery) { + Objects.requireNonNull(attributionSource); + try { + return mService.checkOp(op, attributionSource, message, forDataDelivery, + startDataDelivery); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return PERMISSION_HARD_DENIED; + } +} diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index d490e7a7b454..f3cc35b32223 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1156,13 +1156,12 @@ public final class PermissionManager { * @hide */ @TestApi - public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) { + public void registerAttributionSource(@NonNull AttributionSource source) { try { - return mPermissionManager.registerAttributionSource(source); + mPermissionManager.registerAttributionSource(source); } catch (RemoteException e) { e.rethrowFromSystemServer(); } - return null; } /** diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java index bb48757988e9..b9ff5e7be86a 100644 --- a/core/java/android/speech/RecognitionService.java +++ b/core/java/android/speech/RecognitionService.java @@ -65,22 +65,20 @@ public abstract class RecognitionService extends Service { private static final String TAG = "RecognitionService"; /** Debugging flag */ - private static final boolean DBG = true; - - private static final String RECORD_AUDIO_APP_OP = - AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO); - private static final int RECORD_AUDIO_APP_OP_CODE = - AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO); + private static final boolean DBG = false; /** Binder of the recognition service */ private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this); /** * The current callback of an application that invoked the + * * {@link RecognitionService#onStartListening(Intent, Callback)} method */ private Callback mCurrentCallback = null; + private boolean mStartedDataDelivery; + private static final int MSG_START_LISTENING = 1; private static final int MSG_STOP_LISTENING = 2; @@ -120,6 +118,11 @@ public abstract class RecognitionService extends Service { mCurrentCallback = new Callback(listener, attributionSource); RecognitionService.this.onStartListening(intent, mCurrentCallback); + if (!checkPermissionAndStartDataDelivery()) { + listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS); + Log.i(TAG, "caller doesn't have permission:" + + Manifest.permission.RECORD_AUDIO); + } } else { listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY); Log.i(TAG, "concurrent startListening received - ignoring this call"); @@ -152,13 +155,15 @@ public abstract class RecognitionService extends Service { Log.w(TAG, "cancel called by client who did not call startListening - ignoring"); } else { // the correct state RecognitionService.this.onCancel(mCurrentCallback); - mCurrentCallback = null; + dispatchClearCallback(); if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null"); } } private void dispatchClearCallback() { + finishDataDelivery(); mCurrentCallback = null; + mStartedDataDelivery = false; } private class StartListeningArgs { @@ -177,7 +182,30 @@ public abstract class RecognitionService extends Service { /** * Notifies the service that it should start listening for speech. - * + * + * <p> If you are recognizing speech from the microphone, in this callback you + * should create an attribution context for the caller such that when you access + * the mic the caller would be properly blamed (and their permission checked in + * the process) for accessing the microphone and that you served as a proxy for + * this sensitive data (and your permissions would be checked in the process). + * You should also open the mic in this callback via the attribution context + * and close the mic before returning the recognized result. If you don't do + * that then the caller would be blamed and you as being a proxy as well as you + * would get one more blame on yourself when you open the microphone. + * + * <pre> + * Context attributionContext = context.createContext(new ContextParams.Builder() + * .setNextAttributionSource(callback.getCallingAttributionSource()) + * .build()); + * + * AudioRecord recorder = AudioRecord.Builder() + * .setContext(attributionContext); + * . . . + * .build(); + * + * recorder.startRecording() + * </pre> + * * @param recognizerIntent contains parameters for the recognition to be performed. The intent * may also contain optional extras, see {@link RecognizerIntent}. If these values are * not set explicitly, default values should be used by the recognizer. @@ -335,57 +363,13 @@ public abstract class RecognitionService extends Service { return mCallingAttributionSource; } - boolean maybeStartAttribution() { - if (DBG) { - Log.i(TAG, "Starting attribution"); - } - - if (DBG && isProxyingRecordAudioToCaller()) { - Log.i(TAG, "Proxying already in progress, not starting the attribution"); - } - - if (!isProxyingRecordAudioToCaller()) { + @NonNull Context getAttributionContextForCaller() { + if (mAttributionContext == null) { mAttributionContext = createContext(new ContextParams.Builder() .setNextAttributionSource(mCallingAttributionSource) .build()); - - final int result = PermissionChecker.checkPermissionAndStartDataDelivery( - RecognitionService.this, - Manifest.permission.RECORD_AUDIO, - mAttributionContext.getAttributionSource(), - /*message*/ null); - - return result == PermissionChecker.PERMISSION_GRANTED; - } - return false; - } - - void maybeFinishAttribution() { - if (DBG) { - Log.i(TAG, "Finishing attribution"); - } - - if (DBG && !isProxyingRecordAudioToCaller()) { - Log.i(TAG, "Not proxying currently, not finishing the attribution"); - } - - if (isProxyingRecordAudioToCaller()) { - PermissionChecker.finishDataDelivery( - RecognitionService.this, - RECORD_AUDIO_APP_OP, - mAttributionContext.getAttributionSource()); - - mAttributionContext = null; } - } - - private boolean isProxyingRecordAudioToCaller() { - final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class); - return appOpsManager.isProxying( - RECORD_AUDIO_APP_OP_CODE, - getAttributionTag(), - mCallingAttributionSource.getUid(), - mCallingAttributionSource.getPackageName()); + return mAttributionContext; } } @@ -435,4 +419,35 @@ public abstract class RecognitionService extends Service { mServiceRef.clear(); } } + + private boolean checkPermissionAndStartDataDelivery() { + if (isPerformingDataDelivery()) { + return true; + } + if (PermissionChecker.checkPermissionAndStartDataDelivery( + RecognitionService.this, Manifest.permission.RECORD_AUDIO, + mCurrentCallback.getAttributionContextForCaller().getAttributionSource(), + /*message*/ null) == PermissionChecker.PERMISSION_GRANTED) { + mStartedDataDelivery = true; + } + return mStartedDataDelivery; + } + + void finishDataDelivery() { + if (mStartedDataDelivery) { + mStartedDataDelivery = false; + final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO); + PermissionChecker.finishDataDelivery(RecognitionService.this, op, + mCurrentCallback.getAttributionContextForCaller().getAttributionSource()); + } + } + + @SuppressWarnings("ConstantCondition") + private boolean isPerformingDataDelivery() { + final int op = AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO); + final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class); + return appOpsManager.isProxying(op, getAttributionTag(), + mCurrentCallback.getCallingAttributionSource().getUid(), + mCurrentCallback.getCallingAttributionSource().getPackageName()); + } } |
