diff options
45 files changed, 1342 insertions, 698 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 0b70a49a5ba0..15696b011095 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -201,6 +201,11 @@ package android.app { method @RequiresPermission("android.permission.MANAGE_APPOPS") public void setHistoryParameters(int, long, int); method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(int, int, String, int); method public static int strOpToOp(@NonNull String); + field public static final int ATTRIBUTION_CHAIN_ID_NONE = -1; // 0xffffffff + field public static final int ATTRIBUTION_FLAGS_NONE = 0; // 0x0 + field public static final int ATTRIBUTION_FLAG_ACCESSOR = 1; // 0x1 + field public static final int ATTRIBUTION_FLAG_INTERMEDIARY = 2; // 0x2 + field public static final int ATTRIBUTION_FLAG_RECEIVER = 4; // 0x4 field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0 field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1 field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2 @@ -230,6 +235,10 @@ package android.app { method public void offsetBeginAndEndTime(long); } + public static interface AppOpsManager.OnOpActiveChangedListener { + method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int); + } + public class BroadcastOptions { ctor public BroadcastOptions(@NonNull android.os.Bundle); method public int getMaxManifestReceiverApiLevel(); @@ -669,7 +678,7 @@ package android.content { public final class AttributionSource implements android.os.Parcelable { ctor public AttributionSource(int, @Nullable String, @Nullable String); - ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable android.content.AttributionSource); + ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder); ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource); } @@ -2056,7 +2065,7 @@ package android.permission { public final class PermissionManager { method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(); method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean); - method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource); + method public void registerAttributionSource(@NonNull android.content.AttributionSource); } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 02520afea147..ea42d957b151 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 @@ -7073,6 +7129,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); + } } /** @@ -7694,14 +7769,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); } }); } @@ -8179,8 +8257,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); } /** @@ -8265,8 +8344,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); } /** @@ -8602,6 +8681,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); @@ -8614,9 +8716,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) { @@ -8653,8 +8755,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); } /** @@ -8700,8 +8803,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); } /** @@ -8715,6 +8819,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( @@ -8729,9 +8850,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) { @@ -8795,8 +8917,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(); } @@ -8817,23 +8949,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 fc676cf5b9c2..1872a98555e7 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4839,6 +4839,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 66b74f25bb2a..8d3452ec2c60 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()); + } } diff --git a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl index 510af770d126..ae6ad326e7b1 100644 --- a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl +++ b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl @@ -18,5 +18,6 @@ package com.android.internal.app; // Iterface to observe op active changes oneway interface IAppOpsActiveCallback { - void opActiveChanged(int op, int uid, String packageName, boolean active); + void opActiveChanged(int op, int uid, String packageName, String attributionTag, + boolean active, int attributionFlags, int attributionChainId); } diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index c112d09d40e4..9ad457200700 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -41,7 +41,8 @@ interface IAppOpsService { boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage); SyncNotedAppOp startOperation(IBinder clientId, int code, int uid, String packageName, @nullable String attributionTag, boolean startIfModeDefault, - boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage); + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, + int attributionFlags, int attributionChainId); @UnsupportedAppUsage void finishOperation(IBinder clientId, int code, int uid, String packageName, @nullable String attributionTag); @@ -57,10 +58,12 @@ interface IAppOpsService { SyncNotedAppOp noteProxyOperation(int code, in AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation); - SyncNotedAppOp startProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource, + SyncNotedAppOp startProxyOperation(int code, in AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, - boolean shouldCollectMessage, boolean skipProxyOperation); - void finishProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource); + boolean shouldCollectMessage, boolean skipProxyOperation, int proxyAttributionFlags, + int proxiedAttributionFlags, int attributionChainId); + void finishProxyOperation(int code, in AttributionSource attributionSource, + boolean skipProxyOperation); // Remaining methods are only used in Java. int checkPackage(int uid, String packageName); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 1f805c94d51c..323b401185c9 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -221,7 +221,6 @@ cc_library_shared { "fd_utils.cpp", "android_hardware_input_InputWindowHandle.cpp", "android_hardware_input_InputApplicationHandle.cpp", - "permission_utils.cpp", ], static_libs: [ @@ -240,7 +239,6 @@ cc_library_shared { "audioflinger-aidl-cpp", "av-types-aidl-cpp", "android.hardware.camera.device@3.2", - "media_permission-aidl-cpp", "libandroidicu", "libbpf_android", "libnetdbpf", @@ -257,6 +255,7 @@ cc_library_shared { "libgraphicsenv", "libgui", "libmediandk", + "libpermission", "libsensor", "libinput", "libcamera_client", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f76cccb65973..de6a2b52a025 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -98,7 +98,6 @@ extern int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env); extern int register_android_media_MicrophoneInfo(JNIEnv *env); extern int register_android_media_ToneGenerator(JNIEnv *env); extern int register_android_media_midi(JNIEnv *env); -extern int register_android_media_permission_Identity(JNIEnv* env); namespace android { @@ -1589,7 +1588,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_media_RemoteDisplay), REG_JNI(register_android_media_ToneGenerator), REG_JNI(register_android_media_midi), - REG_JNI(register_android_media_permission_Identity), REG_JNI(register_android_opengl_classes), REG_JNI(register_android_server_NetworkManagementSocketTagger), diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index 83dc1e0bb0d9..bce4ed78eda1 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -22,13 +22,15 @@ #include <jni.h> #include <nativehelper/JNIHelp.h> #include "core_jni_helpers.h" -#include "permission_utils.h" #include <utils/Log.h> #include <media/AudioRecord.h> #include <media/MicrophoneInfo.h> #include <vector> +#include <android/content/AttributionSourceState.h> +#include <android_os_Parcel.h> + #include <nativehelper/ScopedUtfChars.h> #include "android_media_AudioFormat.h" @@ -38,10 +40,8 @@ #include "android_media_MicrophoneInfo.h" #include "android_media_AudioAttributes.h" -// ---------------------------------------------------------------------------- -using android::media::permission::convertIdentity; -using android::media::permission::Identity; +// ---------------------------------------------------------------------------- using namespace android; @@ -189,7 +189,7 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w jobject jaa, jintArray jSampleRate, jint channelMask, jint channelIndexMask, jint audioFormat, jint buffSizeInBytes, jintArray jSession, - jobject jIdentity, jlong nativeRecordInJavaObj, + jobject jAttributionSource, jlong nativeRecordInJavaObj, jint sharedAudioHistoryMs) { //ALOGV(">> Entering android_media_AudioRecord_setup"); //ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d " @@ -260,14 +260,18 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w size_t bytesPerSample = audio_bytes_per_sample(format); if (buffSizeInBytes == 0) { - ALOGE("Error creating AudioRecord: frameCount is 0."); + ALOGE("Error creating AudioRecord: frameCount is 0."); return (jint) AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT; } size_t frameSize = channelCount * bytesPerSample; size_t frameCount = buffSizeInBytes / frameSize; // create an uninitialized AudioRecord object - lpRecorder = new AudioRecord(convertIdentity(env, jIdentity)); + Parcel* parcel = parcelForJavaObject(env, jAttributionSource); + android::content::AttributionSourceState attributionSource; + attributionSource.readFromParcel(parcel); + + lpRecorder = new AudioRecord(attributionSource); // read the AudioAttributes values auto paa = JNIAudioAttributeHelper::makeUnique(); @@ -912,7 +916,7 @@ static const JNINativeMethod gMethods[] = { {"native_start", "(II)I", (void *)android_media_AudioRecord_start}, {"native_stop", "()V", (void *)android_media_AudioRecord_stop}, {"native_setup", - "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/media/permission/Identity;JI)I", + "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JI)I", (void *)android_media_AudioRecord_setup}, {"native_finalize", "()V", (void *)android_media_AudioRecord_finalize}, {"native_release", "()V", (void *)android_media_AudioRecord_release}, diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index de5df202ba4e..73d2d8d949cd 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -48,7 +48,6 @@ using namespace android; using ::android::media::VolumeShaper; -using ::android::media::permission::Identity; // ---------------------------------------------------------------------------- static const char* const kClassPathName = "android/media/AudioTrack"; @@ -330,9 +329,10 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we // create the native AudioTrack object ScopedUtfChars opPackageNameStr(env, opPackageName); // TODO b/182469354: make consistent with AudioRecord - Identity identity = Identity(); - identity.packageName = std::string(opPackageNameStr.c_str()); - lpTrack = new AudioTrack(identity); + AttributionSourceState attributionSource; + attributionSource.packageName = std::string(opPackageNameStr.c_str()); + attributionSource.token = sp<BBinder>::make(); + lpTrack = new AudioTrack(attributionSource); // read the AudioAttributes values auto paa = JNIAudioAttributeHelper::makeUnique(); @@ -395,7 +395,7 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK : AudioTrack::TRANSFER_SYNC, (offload || encapsulationMode) ? &offloadInfo : NULL, - Identity(), // default uid, pid values + AttributionSourceState(), // default uid, pid values paa.get()); break; @@ -421,7 +421,7 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we sessionId, // audio session ID AudioTrack::TRANSFER_SHARED, NULL, // default offloadInfo - Identity(), // default uid, pid values + AttributionSourceState(), // default uid, pid values paa.get()); break; diff --git a/core/jni/permission_utils.cpp b/core/jni/permission_utils.cpp deleted file mode 100644 index 2b7ef9999491..000000000000 --- a/core/jni/permission_utils.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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. - */ - -#include "permission_utils.h" -#include "core_jni_helpers.h" - -static struct { - jfieldID fieldUid; // Identity.uid - jfieldID fieldPid; // Identity.pid - jfieldID fieldPackageName; // Identity.packageName - jfieldID fieldAttributionTag; // Identity.attributionTag -} javaIdentityFields; - -static const JNINativeMethod method_table[] = { - // no static methods, currently -}; - -int register_android_media_permission_Identity(JNIEnv* env) { - jclass identityClass = android::FindClassOrDie(env, "android/media/permission/Identity"); - javaIdentityFields.fieldUid = android::GetFieldIDOrDie(env, identityClass, "uid", "I"); - javaIdentityFields.fieldPid = android::GetFieldIDOrDie(env, identityClass, "pid", "I"); - javaIdentityFields.fieldPackageName = - android::GetFieldIDOrDie(env, identityClass, "packageName", "Ljava/lang/String;"); - javaIdentityFields.fieldAttributionTag = - android::GetFieldIDOrDie(env, identityClass, "attributionTag", "Ljava/lang/String;"); - - return android::RegisterMethodsOrDie(env, "android/media/permission/Identity", method_table, - NELEM(method_table)); -} - -namespace android::media::permission { - -Identity convertIdentity(JNIEnv* env, const jobject& jIdentity) { - Identity identity; - - identity.uid = env->GetIntField(jIdentity, javaIdentityFields.fieldUid); - identity.pid = env->GetIntField(jIdentity, javaIdentityFields.fieldPid); - - jstring packageNameStr = static_cast<jstring>( - env->GetObjectField(jIdentity, javaIdentityFields.fieldPackageName)); - if (packageNameStr == nullptr) { - identity.packageName = std::nullopt; - } else { - identity.packageName = std::string(ScopedUtfChars(env, packageNameStr).c_str()); - } - - jstring attributionTagStr = static_cast<jstring>( - env->GetObjectField(jIdentity, javaIdentityFields.fieldAttributionTag)); - if (attributionTagStr == nullptr) { - identity.attributionTag = std::nullopt; - } else { - identity.attributionTag = std::string(ScopedUtfChars(env, attributionTagStr).c_str()); - } - - return identity; -} - -} // namespace android::media::permission diff --git a/core/jni/permission_utils.h b/core/jni/permission_utils.h deleted file mode 100644 index d625bb6ba30a..000000000000 --- a/core/jni/permission_utils.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include <android/media/permission/Identity.h> -#include <jni.h> - -namespace android::media::permission { - -Identity convertIdentity(JNIEnv* env, const jobject& jIdentity); -} - -int register_android_media_permission_Identity(JNIEnv* env); diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 0d44a8580ef9..7c6ae28bdd30 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -16,8 +16,6 @@ package android.media; -import static android.media.permission.PermissionUtil.myIdentity; - import android.annotation.CallbackExecutor; import android.annotation.FloatRange; import android.annotation.IntDef; @@ -29,12 +27,13 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; +import android.content.AttributionSource; +import android.content.AttributionSource.ScopedParcelState; import android.content.Context; import android.media.MediaRecorder.Source; import android.media.audiopolicy.AudioMix; import android.media.audiopolicy.AudioPolicy; import android.media.metrics.LogSessionId; -import android.media.permission.Identity; import android.media.projection.MediaProjection; import android.os.Binder; import android.os.Build; @@ -42,6 +41,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ServiceManager; @@ -381,7 +381,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at construction * time. See also {@link AudioManager#generateAudioSessionId()} to obtain a session ID before * construction. - * @param context An optional context to pull an attribution tag from. + * @param context An optional context on whose behalf the recoding is performed. + * * @throws IllegalArgumentException */ private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, @@ -449,10 +450,11 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, audioBuffSizeCheck(bufferSizeInBytes); - Identity identity = myIdentity(context); - if (identity.packageName == null) { + AttributionSource attributionSource = (context != null) + ? context.getAttributionSource() : AttributionSource.myAttributionSource(); + if (attributionSource.getPackageName() == null) { // Command line utility - identity.packageName = "uid:" + Binder.getCallingUid(); + attributionSource = attributionSource.withPackageName("uid:" + Binder.getCallingUid()); } int[] sampleRate = new int[] {mSampleRate}; @@ -461,14 +463,15 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, //TODO: update native initialization when information about hardware init failure // due to capture device already open is available. - int initResult = native_setup(new WeakReference<AudioRecord>(this), - mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask, - mAudioFormat, mNativeBufferSizeInBytes, - session, identity, 0 /*nativeRecordInJavaObj*/, - maxSharedAudioHistoryMs); - if (initResult != SUCCESS) { - loge("Error code "+initResult+" when initializing native AudioRecord object."); - return; // with mState == STATE_UNINITIALIZED + try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) { + int initResult = native_setup(new WeakReference<AudioRecord>(this), mAudioAttributes, + sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat, + mNativeBufferSizeInBytes, session, attributionSourceState.getParcel(), + 0 /*nativeRecordInJavaObj*/, maxSharedAudioHistoryMs); + if (initResult != SUCCESS) { + loge("Error code " + initResult + " when initializing native AudioRecord object."); + return; // with mState == STATE_UNINITIALIZED + } } mSampleRate = sampleRate[0]; @@ -512,23 +515,27 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, */ /* package */ void deferred_connect(long nativeRecordInJavaObj) { if (mState != STATE_INITIALIZED) { - int[] session = { 0 }; - int[] rates = { 0 }; + int[] session = {0}; + int[] rates = {0}; //TODO: update native initialization when information about hardware init failure // due to capture device already open is available. // Note that for this native_setup, we are providing an already created/initialized // *Native* AudioRecord, so the attributes parameters to native_setup() are ignored. - int initResult = native_setup(new WeakReference<AudioRecord>(this), - null /*mAudioAttributes*/, - rates /*mSampleRates*/, - 0 /*mChannelMask*/, - 0 /*mChannelIndexMask*/, - 0 /*mAudioFormat*/, - 0 /*mNativeBufferSizeInBytes*/, - session, - myIdentity(null), - nativeRecordInJavaObj, - 0); + final int initResult; + try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource() + .asScopedParcelState()) { + initResult = native_setup(new WeakReference<>(this), + null /*mAudioAttributes*/, + rates /*mSampleRates*/, + 0 /*mChannelMask*/, + 0 /*mChannelIndexMask*/, + 0 /*mAudioFormat*/, + 0 /*mNativeBufferSizeInBytes*/, + session, + attributionSourceState.getParcel(), + nativeRecordInJavaObj, + 0); + } if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing native AudioRecord object."); return; // with mState == STATE_UNINITIALIZED @@ -620,8 +627,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, /** * Sets the context the record belongs to. This context will be used to pull information, - * such as attribution tags, which will be associated with the AudioRecord. However, the - * context itself will not be retained by the AudioRecord. + * such as {@link android.content.AttributionSource}, which will be associated with + * the AudioRecord. However, the context itself will not be retained by the AudioRecord. * @param context a non-null {@link Context} instance * @return the same Builder instance. */ @@ -2216,7 +2223,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, //-------------------- /** - * @deprecated Use native_setup that takes an Identity object + * @deprecated Use native_setup that takes an {@link AttributionSource} object * @return */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, @@ -2227,18 +2234,20 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, int buffSizeInBytes, int[] sessionId, String opPackageName, long nativeRecordInJavaObj) { - Identity identity = myIdentity(null); - identity.packageName = opPackageName; - - return native_setup(audiorecordThis, attributes, sampleRate, channelMask, channelIndexMask, - audioFormat, buffSizeInBytes, sessionId, identity, nativeRecordInJavaObj, 0); + AttributionSource attributionSource = AttributionSource.myAttributionSource() + .withPackageName(opPackageName); + try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) { + return native_setup(audiorecordThis, attributes, sampleRate, channelMask, + channelIndexMask, audioFormat, buffSizeInBytes, sessionId, + attributionSourceState.getParcel(), nativeRecordInJavaObj, 0); + } } private native int native_setup(Object audiorecordThis, Object /*AudioAttributes*/ attributes, int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, - int buffSizeInBytes, int[] sessionId, Identity identity, long nativeRecordInJavaObj, - int maxSharedAudioHistoryMs); + int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource, + long nativeRecordInJavaObj, int maxSharedAudioHistoryMs); // TODO remove: implementation calls directly into implementation of native_release() private native void native_finalize(); diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index db3e8b0bf888..ccd830a37d89 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -18,7 +18,6 @@ package android.media; import static android.Manifest.permission.BIND_IMS_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.media.permission.PermissionUtil.myIdentity; import android.annotation.IntDef; import android.annotation.NonNull; @@ -27,6 +26,8 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; +import android.content.AttributionSource; +import android.content.AttributionSource.ScopedParcelState; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; @@ -34,7 +35,6 @@ import android.content.res.AssetFileDescriptor; import android.graphics.SurfaceTexture; import android.media.SubtitleController.Anchor; import android.media.SubtitleTrack.RenderingWidget; -import android.media.permission.Identity; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -55,7 +55,6 @@ import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; @@ -684,14 +683,18 @@ public class MediaPlayer extends PlayerBase mTimeProvider = new TimeProvider(this); mOpenSubtitleSources = new Vector<InputStream>(); - Identity identity = myIdentity(null); + AttributionSource attributionSource = AttributionSource.myAttributionSource(); // set the package name to empty if it was null - identity.packageName = TextUtils.emptyIfNull(identity.packageName); + if (attributionSource.getPackageName() == null) { + attributionSource = attributionSource.withPackageName(""); + } /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. */ - native_setup(new WeakReference<MediaPlayer>(this), identity); + try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) { + native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel()); + } baseRegisterPlayer(sessionId); } @@ -2474,7 +2477,8 @@ public class MediaPlayer extends PlayerBase private native final int native_setMetadataFilter(Parcel request); private static native final void native_init(); - private native void native_setup(Object mediaplayerThis, @NonNull Identity identity); + private native void native_setup(Object mediaplayerThis, + @NonNull Parcel attributionSource); private native final void native_finalize(); /** diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index c63e64af37cd..da18a77a497e 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -16,8 +16,6 @@ package android.media; -import static android.media.permission.PermissionUtil.myIdentity; - import android.annotation.CallbackExecutor; import android.annotation.FloatRange; import android.annotation.IntDef; @@ -27,14 +25,16 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; +import android.content.AttributionSource; +import android.content.AttributionSource.ScopedParcelState; import android.content.Context; import android.hardware.Camera; import android.media.metrics.LogSessionId; -import android.media.permission.Identity; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.Parcel; import android.os.PersistableBundle; import android.util.ArrayMap; import android.util.Log; @@ -163,8 +163,11 @@ public class MediaRecorder implements AudioRouting, /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. */ - native_setup(new WeakReference<MediaRecorder>(this), - ActivityThread.currentPackageName(), myIdentity(context)); + try (ScopedParcelState attributionSourceState = context.getAttributionSource() + .asScopedParcelState()) { + native_setup(new WeakReference<>(this), ActivityThread.currentPackageName(), + attributionSourceState.getParcel()); + } } /** @@ -1898,14 +1901,15 @@ public class MediaRecorder implements AudioRouting, publicAlternatives = "{@link MediaRecorder}") private void native_setup(Object mediarecorderThis, String clientName, String opPackageName) throws IllegalStateException { - Identity identity = myIdentity(null); - identity.packageName = opPackageName; - - native_setup(mediarecorderThis, clientName, identity); + AttributionSource attributionSource = AttributionSource.myAttributionSource() + .withPackageName(opPackageName); + try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) { + native_setup(mediarecorderThis, clientName, attributionSourceState.getParcel()); + } } private native void native_setup(Object mediarecorderThis, - String clientName, Identity identity) + String clientName, @NonNull Parcel attributionSource) throws IllegalStateException; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index fd3c4057ad21..70bb9608f1c2 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -16,8 +16,6 @@ package android.media.audiofx; -import static android.media.permission.PermissionUtil.myIdentity; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -26,10 +24,11 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.AttributionSource; +import android.content.AttributionSource.ScopedParcelState; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioSystem; -import android.media.permission.Identity; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -518,10 +517,13 @@ public class AudioEffect { // native initialization // TODO b/182469354: Make consistent with AudioRecord - int initResult = native_setup(new WeakReference<AudioEffect>(this), - type.toString(), uuid.toString(), priority, audioSession, - deviceType, deviceAddress, - id, desc, myIdentity(null), probe); + int initResult; + try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource() + .asScopedParcelState()) { + initResult = native_setup(new WeakReference<>(this), type.toString(), uuid.toString(), + priority, audioSession, deviceType, deviceAddress, id, desc, + attributionSourceState.getParcel(), probe); + } if (initResult != SUCCESS && initResult != ALREADY_EXISTS) { Log.e(TAG, "Error code " + initResult + " when initializing AudioEffect."); @@ -1388,7 +1390,7 @@ public class AudioEffect { private native final int native_setup(Object audioeffect_this, String type, String uuid, int priority, int audioSession, int deviceType, String deviceAddress, int[] id, Object[] desc, - Identity identity, boolean probe); + @NonNull Parcel attributionSource, boolean probe); private native final void native_finalize(); diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java index 58c9e650bb90..3349277ede3b 100644 --- a/media/java/android/media/audiofx/Visualizer.java +++ b/media/java/android/media/audiofx/Visualizer.java @@ -16,12 +16,13 @@ package android.media.audiofx; -import static android.media.permission.PermissionUtil.myIdentity; - +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; -import android.media.permission.Identity; +import android.content.AttributionSource; +import android.content.AttributionSource.ScopedParcelState; import android.os.Handler; import android.os.Looper; +import android.os.Parcel; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -222,8 +223,12 @@ public class Visualizer { // native initialization // TODO b/182469354: make consistent with AudioRecord - int result = native_setup(new WeakReference<Visualizer>(this), audioSession, id, - myIdentity(null)); + int result; + try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource() + .asScopedParcelState()) { + result = native_setup(new WeakReference<>(this), audioSession, id, + attributionSourceState.getParcel()); + } if (result != SUCCESS && result != ALREADY_EXISTS) { Log.e(TAG, "Error code "+result+" when initializing Visualizer."); switch (result) { @@ -690,7 +695,7 @@ public class Visualizer { private native final int native_setup(Object audioeffect_this, int audioSession, int[] id, - Identity identity); + @NonNull Parcel attributionSource); @GuardedBy("mStateLock") private native final void native_finalize(); diff --git a/media/java/android/media/permission/PermissionUtil.java b/media/java/android/media/permission/PermissionUtil.java index 92fe8820570c..b08d111db89c 100644 --- a/media/java/android/media/permission/PermissionUtil.java +++ b/media/java/android/media/permission/PermissionUtil.java @@ -51,24 +51,6 @@ import java.util.Objects; * @hide */ public class PermissionUtil { - /** - * Create an identity for the current process and the passed context. - * - * @param context The process the identity is for. If {@code null}, the process's default - * identity is chosen. - * @return The identity for the current process and context - */ - public static @NonNull Identity myIdentity(@Nullable Context context) { - Identity identity = new Identity(); - - identity.pid = Process.myPid(); - identity.uid = Process.myUid(); - identity.packageName = context != null ? context.getOpPackageName() - : ActivityThread.currentOpPackageName(); - identity.attributionTag = context != null ? context.getAttributionTag() : null; - - return identity; - } /** * Authenticate an originator, where the binder call is coming from a middleman. diff --git a/media/jni/Android.bp b/media/jni/Android.bp index d49790e3ecd3..bc73f6ad1ad2 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -51,6 +51,7 @@ cc_library_shared { shared_libs: [ "audioclient-types-aidl-cpp", "av-types-aidl-cpp", + "framework-permission-aidl-cpp", "libandroid_runtime", "libaudioclient", "libnativehelper", @@ -85,7 +86,6 @@ cc_library_shared { "android.hardware.drm@1.4", "android.hidl.memory@1.0", "android.hidl.token@1.0-utils", - "media_permission-aidl-cpp", ], header_libs: [ diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index a3607597f05e..2636ab227646 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -17,7 +17,6 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaPlayer-JNI" -#include "permission_utils.h" #include "utils/Log.h" #include <media/mediaplayer.h> @@ -80,8 +79,6 @@ static StateExceptionFields gStateExceptionFields; using namespace android; using media::VolumeShaper; -using media::permission::Identity; -using media::permission::convertIdentity; // ---------------------------------------------------------------------------- @@ -949,11 +946,14 @@ android_media_MediaPlayer_native_init(JNIEnv *env) static void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jobject jIdentity) + jobject jAttributionSource) { ALOGV("native_setup"); - sp<MediaPlayer> mp = new MediaPlayer(convertIdentity(env, jIdentity)); + Parcel* parcel = parcelForJavaObject(env, jAttributionSource); + android::content::AttributionSourceState attributionSource; + attributionSource.readFromParcel(parcel); + sp<MediaPlayer> mp = new MediaPlayer(attributionSource); if (mp == NULL) { jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; @@ -1409,7 +1409,7 @@ static const JNINativeMethod gMethods[] = { {"native_setMetadataFilter", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_setMetadataFilter}, {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_getMetadata}, {"native_init", "()V", (void *)android_media_MediaPlayer_native_init}, - {"native_setup", "(Ljava/lang/Object;Landroid/media/permission/Identity;)V",(void *)android_media_MediaPlayer_native_setup}, + {"native_setup", "(Ljava/lang/Object;Landroid/os/Parcel;)V",(void *)android_media_MediaPlayer_native_setup}, {"native_finalize", "()V", (void *)android_media_MediaPlayer_native_finalize}, {"getAudioSessionId", "()I", (void *)android_media_MediaPlayer_get_audio_session_id}, {"native_setAudioSessionId", "(I)V", (void *)android_media_MediaPlayer_set_audio_session_id}, diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 66411233216f..7ef0f7709408 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -24,7 +24,6 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaRecorderJNI" -#include "permission_utils.h" #include <utils/Log.h> #include <gui/Surface.h> @@ -46,13 +45,13 @@ #include <system/audio.h> #include <android_runtime/android_view_Surface.h> +#include <android/content/AttributionSourceState.h> +#include <android_os_Parcel.h> // ---------------------------------------------------------------------------- using namespace android; -using android::media::permission::convertIdentity; - // ---------------------------------------------------------------------------- // helper function to extract a native Camera object from a Camera Java object @@ -620,11 +619,14 @@ android_media_MediaRecorder_native_init(JNIEnv *env) static void android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jstring packageName, jobject jIdentity) + jstring packageName, jobject jAttributionSource) { ALOGV("setup"); - sp<MediaRecorder> mr = new MediaRecorder(convertIdentity(env, jIdentity)); + Parcel* parcel = parcelForJavaObject(env, jAttributionSource); + android::content::AttributionSourceState attributionSource; + attributionSource.readFromParcel(parcel); + sp<MediaRecorder> mr = new MediaRecorder(attributionSource); if (mr == NULL) { jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); @@ -871,7 +873,7 @@ static const JNINativeMethod gMethods[] = { {"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset}, {"release", "()V", (void *)android_media_MediaRecorder_release}, {"native_init", "()V", (void *)android_media_MediaRecorder_native_init}, - {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Landroid/media/permission/Identity;)V", + {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Landroid/os/Parcel;)V", (void *)android_media_MediaRecorder_native_setup}, {"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize}, {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface }, diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp index bfed983c8bce..2ddfacf3884a 100644 --- a/media/jni/audioeffect/Android.bp +++ b/media/jni/audioeffect/Android.bp @@ -19,6 +19,7 @@ cc_library_shared { ], shared_libs: [ + "framework-permission-aidl-cpp", "liblog", "libcutils", "libutils", @@ -27,11 +28,11 @@ cc_library_shared { "libaudioclient", "libaudioutils", "libaudiofoundation", - "media_permission-aidl-cpp", + "libbinder" ], export_shared_lib_headers: [ - "media_permission-aidl-cpp", + "framework-permission-aidl-cpp", ], version_script: "exports.lds", diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp index 8a52456849f0..84a8d5122470 100644 --- a/media/jni/audioeffect/Visualizer.cpp +++ b/media/jni/audioeffect/Visualizer.cpp @@ -28,14 +28,16 @@ #include <cutils/bitops.h> #include <utils/Thread.h> +#include <android/content/AttributionSourceState.h> + #include "Visualizer.h" namespace android { // --------------------------------------------------------------------------- -Visualizer::Visualizer (const Identity& identity) - : AudioEffect(identity) +Visualizer::Visualizer (const android::content::AttributionSourceState& attributionSource) + : AudioEffect(attributionSource) { } diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h index 3ee91f0f8b1e..aa07ce8055ae 100644 --- a/media/jni/audioeffect/Visualizer.h +++ b/media/jni/audioeffect/Visualizer.h @@ -20,9 +20,7 @@ #include <media/AudioEffect.h> #include <system/audio_effects/effect_visualizer.h> #include <utils/Thread.h> -#include "android/media/permission/Identity.h" - -using namespace android::media::permission; +#include "android/content/AttributionSourceState.h" /** * The Visualizer class enables application to retrieve part of the currently playing audio for @@ -68,9 +66,9 @@ public: /* Constructor. * See AudioEffect constructor for details on parameters. */ - explicit Visualizer(const Identity& identity); + explicit Visualizer(const android::content::AttributionSourceState& attributionSource); - ~Visualizer(); + ~Visualizer(); /** * Initialize an uninitialized Visualizer. diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index 953b7e01c983..3a8decdad18f 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -25,7 +25,9 @@ #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> #include "media/AudioEffect.h" -#include "permission_utils.h" + +#include <android/content/AttributionSourceState.h> +#include <android_os_Parcel.h> #include <nativehelper/ScopedUtfChars.h> @@ -35,8 +37,6 @@ using namespace android; -using media::permission::convertIdentity; - #define AUDIOEFFECT_SUCCESS 0 #define AUDIOEFFECT_ERROR (-1) #define AUDIOEFFECT_ERROR_ALREADY_EXISTS (-2) @@ -273,7 +273,7 @@ static jint android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, jstring type, jstring uuid, jint priority, jint sessionId, jint deviceType, jstring deviceAddress, - jintArray jId, jobjectArray javadesc, jobject jIdentity, jboolean probe) + jintArray jId, jobjectArray javadesc, jobject jAttributionSource, jboolean probe) { ALOGV("android_media_AudioEffect_native_setup"); AudioEffectJniStorage* lpJniStorage = NULL; @@ -285,6 +285,8 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t effect_descriptor_t desc; jobject jdesc; AudioDeviceTypeAddr device; + AttributionSourceState attributionSource; + Parcel* parcel = NULL; setAudioEffect(env, thiz, 0); @@ -338,7 +340,9 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t } // create the native AudioEffect object - lpAudioEffect = new AudioEffect(convertIdentity(env, jIdentity)); + parcel = parcelForJavaObject(env, jAttributionSource); + attributionSource.readFromParcel(parcel); + lpAudioEffect = new AudioEffect(attributionSource); if (lpAudioEffect == 0) { ALOGE("Error creating AudioEffect"); goto setup_failure; @@ -774,7 +778,7 @@ android_media_AudioEffect_native_queryPreProcessings(JNIEnv *env, jclass clazz _ // Dalvik VM type signatures static const JNINativeMethod gMethods[] = { {"native_init", "()V", (void *)android_media_AudioEffect_native_init}, - {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Landroid/media/permission/Identity;Z)I", + {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Landroid/os/Parcel;Z)I", (void *)android_media_AudioEffect_native_setup}, {"native_finalize", "()V", (void *)android_media_AudioEffect_native_finalize}, {"native_release", "()V", (void *)android_media_AudioEffect_native_release}, diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index 439715cbb811..b30f00fdf7de 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -25,12 +25,16 @@ #include <android_runtime/AndroidRuntime.h> #include <utils/threads.h> #include "Visualizer.h" -#include "permission_utils.h" #include <nativehelper/ScopedUtfChars.h> +#include <android/content/AttributionSourceState.h> +#include <android_os_Parcel.h> + using namespace android; +using content::AttributionSourceState; + #define VISUALIZER_SUCCESS 0 #define VISUALIZER_ERROR (-1) #define VISUALIZER_ERROR_ALREADY_EXISTS (-2) @@ -348,13 +352,15 @@ static void android_media_visualizer_effect_callback(int32_t event, static jint android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jint sessionId, jintArray jId, jobject jIdentity) + jint sessionId, jintArray jId, jobject jAttributionSource) { ALOGV("android_media_visualizer_native_setup"); VisualizerJniStorage* lpJniStorage = NULL; int lStatus = VISUALIZER_ERROR_NO_MEMORY; sp<Visualizer> lpVisualizer; jint* nId = NULL; + AttributionSourceState attributionSource; + Parcel* parcel = nullptr; setVisualizer(env, thiz, 0); @@ -381,7 +387,9 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th } // create the native Visualizer object - lpVisualizer = new Visualizer(convertIdentity(env, jIdentity)); + parcel = parcelForJavaObject(env, jAttributionSource); + attributionSource.readFromParcel(parcel); + lpVisualizer = sp<Visualizer>::make(attributionSource); if (lpVisualizer == 0) { ALOGE("Error creating Visualizer"); goto setup_failure; @@ -678,7 +686,7 @@ android_media_setPeriodicCapture(JNIEnv *env, jobject thiz, jint rate, jboolean // Dalvik VM type signatures static const JNINativeMethod gMethods[] = { {"native_init", "()V", (void *)android_media_visualizer_native_init}, - {"native_setup", "(Ljava/lang/Object;I[ILandroid/media/permission/Identity;)I", + {"native_setup", "(Ljava/lang/Object;I[ILandroid/os/Parcel;)I", (void *)android_media_visualizer_native_setup}, {"native_finalize", "()V", (void *)android_media_visualizer_native_finalize}, {"native_release", "()V", (void *)android_media_visualizer_native_release}, diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp index 4227cd8cbb29..ee473f5deb15 100644 --- a/media/jni/soundpool/Android.bp +++ b/media/jni/soundpool/Android.bp @@ -126,6 +126,7 @@ cc_library_shared { ], shared_libs: [ + "framework-permission-aidl-cpp", "libaudioutils", "liblog", "libcutils", @@ -135,7 +136,6 @@ cc_library_shared { "libaudioclient", "libmediandk", "libbinder", - "media_permission-aidl-cpp", ], cflags: [ diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp index 95fe000bd2c2..bbbef3853b23 100644 --- a/media/jni/soundpool/Stream.cpp +++ b/media/jni/soundpool/Stream.cpp @@ -17,7 +17,7 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "SoundPool::Stream" #include <utils/Log.h> -#include<android/media/permission/Identity.h> +#include <android/content/AttributionSourceState.h> #include "Stream.h" @@ -25,8 +25,6 @@ namespace android::soundpool { -using media::permission::Identity; - Stream::~Stream() { ALOGV("%s(%p)", __func__, this); @@ -330,15 +328,16 @@ void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID, // do not create a new audio track if current track is compatible with sound parameters - Identity identity = Identity(); - identity.packageName = mStreamManager->getOpPackageName(); + android::content::AttributionSourceState attributionSource; + attributionSource.packageName = mStreamManager->getOpPackageName(); + attributionSource.token = sp<BBinder>::make(); // TODO b/182469354 make consistent with AudioRecord, add util for native source newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(), channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, staticCallback, userData, 0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE, AudioTrack::TRANSFER_DEFAULT, - nullptr /*offloadInfo*/, identity, + nullptr /*offloadInfo*/, attributionSource, mStreamManager->getAttributes(), false /*doNotReconnect*/, 1.0f /*maxRequiredSpeed*/); // Set caller name so it can be logged in destructor. diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 562f8f449833..84a24d86d84c 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -806,7 +806,8 @@ public final class ActiveServices { } mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null, - true, false, null, false); + true, false, null, false, AppOpsManager.ATTRIBUTION_FLAGS_NONE, + AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); } final ServiceMap smap = getServiceMapLocked(r.userId); @@ -1875,7 +1876,8 @@ public final class ActiveServices { mAm.mAppOpsService.startOperation( AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, - null, true, false, "", false); + null, true, false, "", false, AppOpsManager.ATTRIBUTION_FLAGS_NONE, + AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); registerAppOpCallbackLocked(r); mAm.updateForegroundServiceUsageStats(r.name, r.userId, true); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b518490d3adb..172c3f7dc7c2 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -156,6 +156,7 @@ import android.app.ActivityThread; import android.app.AnrController; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.AppOpsManager.AttributionFlags; import android.app.AppOpsManagerInternal.CheckOpsDelegate; import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; @@ -346,6 +347,7 @@ import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.MemInfoReader; import com.android.internal.util.Preconditions; +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; @@ -353,6 +355,7 @@ 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; import com.android.server.AlarmManagerInternal; import com.android.server.DeviceIdleInternal; import com.android.server.DisplayThread; @@ -1833,7 +1836,9 @@ public class ActivityManagerService extends IActivityManager.Stub final int[] cameraOp = {AppOpsManager.OP_CAMERA}; mAppOpsService.startWatchingActive(cameraOp, 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) { cameraActiveChanged(uid, active); } }); @@ -16851,7 +16856,7 @@ public class ActivityManagerService extends IActivityManager.Stub try { return superImpl.apply(code, new AttributionSource(shellUid, "com.android.shell", attributionSource.getAttributionTag(), - attributionSource.getNext()), + attributionSource.getToken(), attributionSource.getNext()), shouldCollectAsyncNotedOp, message, shouldCollectMessage, skiProxyOperation); } finally { @@ -16867,8 +16872,9 @@ public class ActivityManagerService extends IActivityManager.Stub @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) { + @AttributionFlags int attributionFlags, int attributionChainId, + @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean, + Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) { if (uid == mTargetUid && isTargetOp(code)) { final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid), Process.SHELL_UID); @@ -16876,57 +16882,62 @@ public class ActivityManagerService extends IActivityManager.Stub try { return superImpl.apply(token, code, shellUid, "com.android.shell", attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage); + shouldCollectMessage, attributionFlags, attributionChainId); } finally { Binder.restoreCallingIdentity(identity); } } return superImpl.apply(token, code, uid, packageName, attributionTag, - startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage); + startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, + attributionFlags, attributionChainId); } @Override - public SyncNotedAppOp startProxyOperation(IBinder token, int code, + public SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, - boolean skipProsyOperation, @NonNull OctFunction<IBinder, Integer, - AttributionSource, Boolean, Boolean, String, Boolean, Boolean, - SyncNotedAppOp> superImpl) { + boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, + @AttributionFlags int proxiedAttributionFlags, int attributionChainId, + @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean, + Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) { if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) { final int shellUid = UserHandle.getUid(UserHandle.getUserId( attributionSource.getUid()), Process.SHELL_UID); final long identity = Binder.clearCallingIdentity(); try { - return superImpl.apply(token, code, new AttributionSource(shellUid, - "com.android.shell", attributionSource.getAttributionTag(), - attributionSource.getNext()), startIfModeDefault, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, - skipProsyOperation); + return superImpl.apply(code, new AttributionSource(shellUid, + "com.android.shell", attributionSource.getAttributionTag(), + attributionSource.getToken(), attributionSource.getNext()), + startIfModeDefault, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, + proxiedAttributionFlags, attributionChainId); } finally { Binder.restoreCallingIdentity(identity); } } - return superImpl.apply(token, code, attributionSource, startIfModeDefault, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProsyOperation); + return superImpl.apply(code, attributionSource, startIfModeDefault, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation, + proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); } @Override - public void finishProxyOperation(IBinder clientId, int code, - @NonNull AttributionSource attributionSource, - @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl) { + public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource, + boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource, + Boolean, Void> superImpl) { if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) { final int shellUid = UserHandle.getUid(UserHandle.getUserId( attributionSource.getUid()), Process.SHELL_UID); final long identity = Binder.clearCallingIdentity(); try { - superImpl.apply(clientId, code, new AttributionSource(shellUid, + superImpl.apply(code, new AttributionSource(shellUid, "com.android.shell", attributionSource.getAttributionTag(), - attributionSource.getNext())); + attributionSource.getToken(), attributionSource.getNext()), + skipProxyOperation); } finally { Binder.restoreCallingIdentity(identity); } } - superImpl.apply(clientId, code, attributionSource); + superImpl.apply(code, attributionSource, skipProxyOperation); } private boolean isTargetOp(int code) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 541dcdc94a51..fdb3d8ca371c 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -84,6 +84,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.AppOpsManager.AttributionFlags; import android.app.AppOpsManager.AttributedOpEntry; import android.app.AppOpsManager.HistoricalOps; import android.app.AppOpsManager.Mode; @@ -200,6 +201,7 @@ import java.util.Map; import java.util.Objects; import java.util.Scanner; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; public class AppOpsService extends IAppOpsService.Stub { @@ -257,11 +259,6 @@ public class AppOpsService extends IAppOpsService.Stub { private static final int MAX_UNUSED_POOLED_OBJECTS = 3; private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000; - //TODO: remove this when development is done. - private static final int DEBUG_FGS_ALLOW_WHILE_IN_USE = 0; - private static final int DEBUG_FGS_ENFORCE_TYPE = 1; - - final Context mContext; final AtomicFile mFile; private final @Nullable File mNoteOpCallerStacktracesFile; @@ -414,9 +411,10 @@ public class AppOpsService extends IAppOpsService.Stub { } InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId, - @NonNull Runnable onDeath, int proxyUid, @Nullable String proxyPackageName, - @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, - @OpFlags int flags) throws RemoteException { + @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { InProgressStartOpEvent recycled = acquire(); @@ -427,13 +425,14 @@ public class AppOpsService extends IAppOpsService.Stub { } if (recycled != null) { - recycled.reinit(startTime, elapsedTime, clientId, onDeath, uidState, flags, - proxyInfo, mOpEventProxyInfoPool); + recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath, + uidState, flags, proxyInfo, attributionFlags, attributionChainId, + mOpEventProxyInfoPool); return recycled; } - return new InProgressStartOpEvent(startTime, elapsedTime, clientId, onDeath, uidState, - proxyInfo, flags); + return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag, + onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId); } } @@ -685,6 +684,9 @@ public class AppOpsService extends IAppOpsService.Stub { /** Id of the client that started the event */ private @NonNull IBinder mClientId; + /** The attribution tag for this operation */ + private @Nullable String mAttributionTag; + /** To call when client dies */ private @NonNull Runnable mOnDeath; @@ -700,30 +702,44 @@ public class AppOpsService extends IAppOpsService.Stub { /** How many times the op was started but not finished yet */ int numUnfinishedStarts; + /** The attribution flags related to this event */ + private @AttributionFlags int mAttributionFlags; + + /** The id of the attribution chain this even is a part of */ + private int mAttributionChainId; + /** * Create a new {@link InProgressStartOpEvent}. * * @param startTime The time {@link #startOperation} was called * @param startElapsedTime The elapsed time when {@link #startOperation} was called * @param clientId The client id of the caller of {@link #startOperation} + * @param attributionTag The attribution tag for the operation. * @param onDeath The code to execute on client death * @param uidState The uidstate of the app {@link #startOperation} was called for + * @param attributionFlags the attribution flags for this operation. + * @param attributionChainId the unique id of the attribution chain this op is a part of. * @param proxy The proxy information, if {@link #startProxyOperation} was called * @param flags The trusted/nontrusted/self flags. * * @throws RemoteException If the client is dying */ private InProgressStartOpEvent(long startTime, long startElapsedTime, - @NonNull IBinder clientId, @NonNull Runnable onDeath, - @AppOpsManager.UidState int uidState, @Nullable OpEventProxyInfo proxy, - @OpFlags int flags) throws RemoteException { + @NonNull IBinder clientId, @Nullable String attributionTag, + @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, + @Nullable OpEventProxyInfo proxy, @OpFlags int flags, + @AttributionFlags int attributionFlags, int attributionChainId) + throws RemoteException { mStartTime = startTime; mStartElapsedTime = startElapsedTime; mClientId = clientId; + mAttributionTag = attributionTag; mOnDeath = onDeath; mUidState = uidState; mProxy = proxy; mFlags = flags; + mAttributionFlags = attributionFlags; + mAttributionChainId = attributionChainId; clientId.linkToDeath(this, 0); } @@ -744,21 +760,27 @@ public class AppOpsService extends IAppOpsService.Stub { * @param startTime The time {@link #startOperation} was called * @param startElapsedTime The elapsed time when {@link #startOperation} was called * @param clientId The client id of the caller of {@link #startOperation} + * @param attributionTag The attribution tag for this operation. * @param onDeath The code to execute on client death * @param uidState The uidstate of the app {@link #startOperation} was called for * @param flags The flags relating to the proxy * @param proxy The proxy information, if {@link #startProxyOperation} was called + * @param attributionFlags the attribution flags for this operation. + * @param attributionChainId the unique id of the attribution chain this op is a part of. * @param proxyPool The pool to release previous {@link OpEventProxyInfo} to * * @throws RemoteException If the client is dying */ public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId, - @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, @OpFlags int flags, - @Nullable OpEventProxyInfo proxy, @NonNull Pools.Pool<OpEventProxyInfo> proxyPool + @Nullable String attributionTag, @NonNull Runnable onDeath, + @AppOpsManager.UidState int uidState, @OpFlags int flags, + @Nullable OpEventProxyInfo proxy, @AttributionFlags int attributionFlags, + int attributionChainId, @NonNull Pools.Pool<OpEventProxyInfo> proxyPool ) throws RemoteException { mStartTime = startTime; mStartElapsedTime = startElapsedTime; mClientId = clientId; + mAttributionTag = attributionTag; mOnDeath = onDeath; mUidState = uidState; mFlags = flags; @@ -767,6 +789,8 @@ public class AppOpsService extends IAppOpsService.Stub { proxyPool.release(mProxy); } mProxy = proxy; + mAttributionFlags = attributionFlags; + mAttributionChainId = attributionChainId; clientId.linkToDeath(this, 0); } @@ -791,7 +815,7 @@ public class AppOpsService extends IAppOpsService.Stub { return mUidState; } - /** @return proxy info for the access */ + /** @return proxy tag for the access */ public @Nullable OpEventProxyInfo getProxy() { return mProxy; } @@ -800,6 +824,16 @@ public class AppOpsService extends IAppOpsService.Stub { public @OpFlags int getFlags() { return mFlags; } + + /** @return attributoin flags used for the access */ + public @AttributionFlags int getAttributionFlags() { + return mAttributionFlags; + } + + /** @return attributoin chiang id for the access */ + public int getAttributionChainId() { + return mAttributionChainId; + } } private final class AttributedOp { @@ -943,32 +977,38 @@ public class AppOpsService extends IAppOpsService.Stub { * @param proxyAttributionTag The attribution tag of the proxy app * @param uidState UID state of the app startOp is called for * @param flags The proxy flags + * @param attributionFlags The attribution flags associated with this operation. + * @param attributionChainId The if of the attribution chain this operations is a part of. */ public void started(@NonNull IBinder clientId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags) throws RemoteException { - started(clientId, proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, - true); + @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { + started(clientId, proxyUid, proxyPackageName, proxyAttributionTag, + uidState, flags,/*triggerCallbackIfNeeded*/ true, attributionFlags, + attributionChainId); } private void started(@NonNull IBinder clientId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @OpFlags int flags, - boolean triggerCallbackIfNeeded) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag, - uidState, flags, triggerCallbackIfNeeded, true); + boolean triggerCallbackIfNeeded, @AttributionFlags int attributionFlags, + int attributionChainId) throws RemoteException { + startedOrPaused(clientId, proxyUid, proxyPackageName, + proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded, + /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId); } private void startedOrPaused(@NonNull IBinder clientId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, @OpFlags int flags, - boolean triggerCallbackIfNeeded, boolean isStarted) throws RemoteException { + boolean triggerCallbackIfNeeded, boolean isStarted, @AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) { - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, true); + scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, + tag, true, attributionFlags, attributionChainId); } - if (isStarted && mInProgressEvents == null) { mInProgressEvents = new ArrayMap<>(1); } else if (mPausedInProgressEvents == null) { @@ -981,9 +1021,10 @@ public class AppOpsService extends IAppOpsService.Stub { InProgressStartOpEvent event = events.get(clientId); if (event == null) { event = mInProgressStartOpEventPool.acquire(startTime, - SystemClock.elapsedRealtime(), clientId, + SystemClock.elapsedRealtime(), clientId, tag, PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), - proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags); + proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, + attributionFlags, attributionChainId); events.put(clientId, event); } else { if (uidState != event.mUidState) { @@ -994,10 +1035,10 @@ public class AppOpsService extends IAppOpsService.Stub { event.numUnfinishedStarts++; if (isStarted) { + // TODO: Consider storing the attribution chain flags and id mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, tag, uidState, flags, startTime); } - } /** @@ -1063,7 +1104,8 @@ public class AppOpsService extends IAppOpsService.Stub { // TODO ntmyren: Also callback for single attribution tag activity changes if (triggerCallbackIfNeeded && !parent.isRunning()) { scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, false); + parent.packageName, tag, false, event.getAttributionFlags(), + event.getAttributionChainId()); } } } @@ -1103,9 +1145,10 @@ public class AppOpsService extends IAppOpsService.Stub { */ public void createPaused(@NonNull IBinder clientId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags) throws RemoteException { + @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag, - uidState, flags, true, false); + uidState, flags, true, false, attributionFlags, attributionChainId); } /** @@ -1124,9 +1167,11 @@ public class AppOpsService extends IAppOpsService.Stub { InProgressStartOpEvent event = mInProgressEvents.valueAt(i); mPausedInProgressEvents.put(event.mClientId, event); finishOrPause(event.mClientId, true, true); + + scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, false, + event.getAttributionFlags(), event.getAttributionChainId()); } - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, false); mInProgressEvents = null; } @@ -1153,10 +1198,10 @@ public class AppOpsService extends IAppOpsService.Stub { event.mStartTime = startTime; mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, tag, event.mUidState, event.mFlags, startTime); - } - if (shouldSendActive) { - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, true); + if (shouldSendActive) { + scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, + tag, true, event.getAttributionFlags(), event.getAttributionChainId()); + } } mPausedInProgressEvents = null; } @@ -1210,10 +1255,12 @@ public class AppOpsService extends IAppOpsService.Stub { // previously removed unfinished start counts back if (proxy != null) { started(event.getClientId(), proxy.getUid(), proxy.getPackageName(), - proxy.getAttributionTag(), newState, event.getFlags(), false); + proxy.getAttributionTag(), newState, event.getFlags(), false, + event.getAttributionFlags(), event.getAttributionChainId()); } else { started(event.getClientId(), Process.INVALID_UID, null, null, newState, - OP_FLAG_SELF, false); + OP_FLAG_SELF, false, event.getAttributionFlags(), + event.getAttributionChainId()); } InProgressStartOpEvent newEvent = mInProgressEvents.get(binders.get(i)); @@ -3321,9 +3368,10 @@ public class AppOpsService extends IAppOpsService.Stub { scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, AppOpsManager.MODE_IGNORED); if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid - + " package " + packageName); + + " package " + packageName + "flags: " + + AppOpsManager.flagsToString(flags)); return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, - packageName); + packageName + " flags: " + AppOpsManager.flagsToString(flags)); } final Op op = getOpLocked(ops, code, uid, true); final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); @@ -3349,7 +3397,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (uidMode != AppOpsManager.MODE_ALLOWED) { if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " - + packageName); + + packageName + " flags: " + AppOpsManager.flagsToString(flags)); attributedOp.rejected(uidState.state, flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, uidMode); @@ -3362,7 +3410,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (mode != AppOpsManager.MODE_ALLOWED) { if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " - + packageName); + + packageName + " flags: " + AppOpsManager.flagsToString(flags)); attributedOp.rejected(uidState.state, flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, mode); @@ -3373,7 +3421,8 @@ public class AppOpsService extends IAppOpsService.Stub { Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid + " package " + packageName + (attributionTag == null ? "" - : "." + attributionTag)); + : "." + attributionTag) + " flags: " + + AppOpsManager.flagsToString(flags)); } scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, AppOpsManager.MODE_ALLOWED); @@ -3674,16 +3723,18 @@ public class AppOpsService extends IAppOpsService.Stub { public SyncNotedAppOp startOperation(IBinder token, int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, - String message, boolean shouldCollectMessage) { + String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, + int attributionChainId) { return mCheckOpsDelegateDispatcher.startOperation(token, code, uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage); + shouldCollectMessage, attributionFlags, attributionChainId); } private SyncNotedAppOp startOperationImpl(@NonNull IBinder clientId, int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message, - boolean shouldCollectMessage) { + boolean shouldCollectMessage, @AttributionFlags int attributionFlags, + int attributionChainId) { verifyIncomingUid(uid); verifyIncomingOp(code); verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); @@ -3707,29 +3758,36 @@ public class AppOpsService extends IAppOpsService.Stub { } return startOperationUnchecked(clientId, code, uid, packageName, attributionTag, Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, false); + shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, + attributionChainId, /*dryRun*/ false); } @Override - public SyncNotedAppOp startProxyOperation(IBinder clientId, int code, + public SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, - boolean skipProxyOperation) { - return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource, + boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, + @AttributionFlags int proxiedAttributionFlags, int attributionChainId) { + return mCheckOpsDelegateDispatcher.startProxyOperation(code, attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - skipProxyOperation); + skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags, + attributionChainId); } - private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code, + private SyncNotedAppOp startProxyOperationImpl(int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, - boolean shouldCollectMessage, boolean skipProxyOperation) { + boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags + int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags, + int attributionChainId) { final int proxyUid = attributionSource.getUid(); final String proxyPackageName = attributionSource.getPackageName(); final String proxyAttributionTag = attributionSource.getAttributionTag(); + final IBinder proxyToken = attributionSource.getToken(); final int proxiedUid = attributionSource.getNextUid(); final String proxiedPackageName = attributionSource.getNextPackageName(); final String proxiedAttributionTag = attributionSource.getNextAttributionTag(); + final IBinder proxiedToken = attributionSource.getNextToken(); verifyIncomingProxyUid(attributionSource); verifyIncomingOp(code); @@ -3762,10 +3820,11 @@ public class AppOpsService extends IAppOpsService.Stub { if (!skipProxyOperation) { // Test if the proxied operation will succeed before starting the proxy operation - final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code, proxiedUid, - resolvedProxiedPackageName, proxiedAttributionTag, proxyUid, + final SyncNotedAppOp testProxiedOp = startOperationUnchecked(proxiedToken, code, + proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, true); + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + proxiedAttributionFlags, attributionChainId, /*dryRun*/ true); if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) { return testProxiedOp; } @@ -3773,19 +3832,21 @@ public class AppOpsService extends IAppOpsService.Stub { final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY; - final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid, + final SyncNotedAppOp proxyAppOp = startOperationUnchecked(proxyToken, code, proxyUid, resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null, proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message, - shouldCollectMessage, false); + shouldCollectMessage, proxyAttributionFlags, attributionChainId, + /*dryRun*/ false); if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) { return proxyAppOp; } } - return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, + return startOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, false); + shouldCollectMessage, proxiedAttributionFlags, attributionChainId, + /*dryRun*/ false); } private boolean shouldStartForMode(int mode, boolean startIfModeDefault) { @@ -3796,7 +3857,8 @@ public class AppOpsService extends IAppOpsService.Stub { @NonNull String packageName, @Nullable String attributionTag, int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message, - boolean shouldCollectMessage, boolean dryRun) { + boolean shouldCollectMessage, @AttributionFlags int attributionFlags, + int attributionChainId, boolean dryRun) { RestrictionBypass bypass; try { bypass = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); @@ -3818,7 +3880,8 @@ public class AppOpsService extends IAppOpsService.Stub { flags, AppOpsManager.MODE_IGNORED); } if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid - + " package " + packageName); + + " package " + packageName + " flags: " + + AppOpsManager.flagsToString(flags)); return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, packageName); } @@ -3835,7 +3898,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (DEBUG) { Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " - + packageName); + + packageName + " flags: " + AppOpsManager.flagsToString(flags)); } if (!dryRun) { attributedOp.rejected(uidState.state, flags); @@ -3852,7 +3915,7 @@ public class AppOpsService extends IAppOpsService.Stub { && (!startIfModeDefault || mode != MODE_DEFAULT)) { if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " - + packageName); + + packageName + " flags: " + AppOpsManager.flagsToString(flags)); if (!dryRun) { attributedOp.rejected(uidState.state, flags); scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, @@ -3862,15 +3925,18 @@ public class AppOpsService extends IAppOpsService.Stub { } } if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid - + " package " + packageName + " restricted: " + isRestricted); + + " package " + packageName + " restricted: " + isRestricted + + " flags: " + AppOpsManager.flagsToString(flags)); if (!dryRun) { try { if (isRestricted) { attributedOp.createPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState.state, flags); + proxyAttributionTag, uidState.state, flags, attributionFlags, + attributionChainId); } else { attributedOp.started(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState.state, flags); + proxyAttributionTag, uidState.state, flags, attributionFlags, + attributionChainId); } } catch (RemoteException e) { throw new RuntimeException(e); @@ -3905,21 +3971,26 @@ public class AppOpsService extends IAppOpsService.Stub { } @Override - public void finishProxyOperation(IBinder clientId, int code, - @NonNull AttributionSource attributionSource) { - mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource); + public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource, + boolean skipProxyOperation) { + mCheckOpsDelegateDispatcher.finishProxyOperation(code, attributionSource, + skipProxyOperation); } - private Void finishProxyOperationImpl(IBinder clientId, int code, - @NonNull AttributionSource attributionSource) { + private Void finishProxyOperationImpl(int code, @NonNull AttributionSource attributionSource, + boolean skipProxyOperation) { final int proxyUid = attributionSource.getUid(); final String proxyPackageName = attributionSource.getPackageName(); final String proxyAttributionTag = attributionSource.getAttributionTag(); + final IBinder proxyToken = attributionSource.getToken(); final int proxiedUid = attributionSource.getNextUid(); final String proxiedPackageName = attributionSource.getNextPackageName(); final String proxiedAttributionTag = attributionSource.getNextAttributionTag(); + final IBinder proxiedToken = attributionSource.getNextToken(); - verifyIncomingUid(proxyUid); + skipProxyOperation = resolveSkipProxyOperation(skipProxyOperation, attributionSource); + + verifyIncomingProxyUid(attributionSource); verifyIncomingOp(code); verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid)); verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid)); @@ -3930,8 +4001,10 @@ public class AppOpsService extends IAppOpsService.Stub { return null; } - finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, - proxyAttributionTag); + if (!skipProxyOperation) { + finishOperationUnchecked(proxyToken, code, proxyUid, resolvedProxyPackageName, + proxyAttributionTag); + } String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid, proxiedPackageName); @@ -3939,7 +4012,7 @@ public class AppOpsService extends IAppOpsService.Stub { return null; } - finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, + finishOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag); return null; @@ -3981,8 +4054,9 @@ public class AppOpsService extends IAppOpsService.Stub { } } - private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, String packageName, - boolean active) { + private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull + String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags + int attributionFlags, int attributionChainId) { ArraySet<ActiveCallback> dispatchedCallbacks = null; final int callbackListCount = mActiveWatchers.size(); for (int i = 0; i < callbackListCount; i++) { @@ -4003,11 +4077,13 @@ public class AppOpsService extends IAppOpsService.Stub { } mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpActiveChanged, - this, dispatchedCallbacks, code, uid, packageName, active)); + this, dispatchedCallbacks, code, uid, packageName, attributionTag, active, + attributionFlags, attributionChainId)); } private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks, - int code, int uid, String packageName, boolean active) { + int code, int uid, @NonNull String packageName, @Nullable String attributionTag, + boolean active, @AttributionFlags int attributionFlags, int attributionChainId) { // There are features watching for mode changes such as window manager // and location manager which are in our process. The callbacks in these // features may require permissions our remote caller does not have. @@ -4017,7 +4093,8 @@ public class AppOpsService extends IAppOpsService.Stub { for (int i = 0; i < callbackCount; i++) { final ActiveCallback callback = callbacks.valueAt(i); try { - callback.mCallback.opActiveChanged(code, uid, packageName, active); + callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag, + active, attributionFlags, attributionChainId); } catch (RemoteException e) { /* do nothing */ } @@ -5023,6 +5100,8 @@ public class AppOpsService extends IAppOpsService.Stub { } static class Shell extends ShellCommand { + static final AtomicInteger sAttributionChainIds = new AtomicInteger(0); + final IAppOpsService mInterface; final AppOpsService mInternal; @@ -5491,7 +5570,9 @@ public class AppOpsService extends IAppOpsService.Stub { if (shell.packageName != null) { shell.mInterface.startOperation(shell.mToken, shell.op, shell.packageUid, shell.packageName, shell.attributionTag, true, true, - "appops start shell command", true); + "appops start shell command", true, + AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR, + shell.sAttributionChainIds.incrementAndGet()); } else { return -1; } @@ -6421,7 +6502,9 @@ public class AppOpsService extends IAppOpsService.Stub { @NonNull String proxiedPackageName) { Objects.requireNonNull(proxyPackageName); Objects.requireNonNull(proxiedPackageName); - Binder.withCleanCallingIdentity(() -> { + final long callingUid = Binder.getCallingUid(); + final long identity = Binder.clearCallingIdentity(); + try { final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid, proxiedPackageName, new int[] {op}); if (packageOps == null || packageOps.isEmpty()) { @@ -6436,13 +6519,13 @@ public class AppOpsService extends IAppOpsService.Stub { return false; } final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo( - AppOpsManager.OP_FLAG_TRUSTED_PROXY - | AppOpsManager.OP_FLAG_UNTRUSTED_PROXY); - return proxyInfo != null && Binder.getCallingUid() == proxyInfo.getUid() + OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED); + return proxyInfo != null && callingUid == proxyInfo.getUid() && proxyPackageName.equals(proxyInfo.getPackageName()) && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag()); - }); - return false; + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override @@ -7275,89 +7358,101 @@ public class AppOpsService extends IAppOpsService.Stub { public SyncNotedAppOp startOperation(IBinder token, int code, int uid, @Nullable String packageName, @NonNull String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, - @Nullable String message, boolean shouldCollectMessage) { + @Nullable String message, boolean shouldCollectMessage, + @AttributionFlags int attributionFlags, int attributionChainId) { if (mPolicy != null) { if (mCheckOpsDelegate != null) { return mPolicy.startOperation(token, code, uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, this::startDelegateOperationImpl); + shouldCollectMessage, attributionFlags, attributionChainId, + this::startDelegateOperationImpl); } else { return mPolicy.startOperation(token, code, uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, AppOpsService.this::startOperationImpl); + shouldCollectMessage, attributionFlags, attributionChainId, + AppOpsService.this::startOperationImpl); } } else if (mCheckOpsDelegate != null) { return startDelegateOperationImpl(token, code, uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage); + shouldCollectMessage, attributionFlags, attributionChainId); } return startOperationImpl(token, code, uid, packageName, attributionTag, - startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage); + startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, + attributionFlags, attributionChainId); } private SyncNotedAppOp startDelegateOperationImpl(IBinder token, int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, - boolean shouldCollectMessage) { + boolean shouldCollectMessage, @AttributionFlags int attributionFlags, + int attributionChainId) { return mCheckOpsDelegate.startOperation(token, code, uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - AppOpsService.this::startOperationImpl); + attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl); } - public SyncNotedAppOp startProxyOperation(IBinder clientId, int code, + public SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, - boolean skipProxyOperation) { + boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, + @AttributionFlags int proxiedAttributionFlags, int attributionChainId) { if (mPolicy != null) { if (mCheckOpsDelegate != null) { - return mPolicy.startProxyOperation(clientId, code, attributionSource, + return mPolicy.startProxyOperation(code, attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, skipProxyOperation, + shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, + proxiedAttributionFlags, attributionChainId, this::startDelegateProxyOperationImpl); } else { - return mPolicy.startProxyOperation(clientId, code, attributionSource, + return mPolicy.startProxyOperation(code, attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, skipProxyOperation, + shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, + proxiedAttributionFlags, attributionChainId, AppOpsService.this::startProxyOperationImpl); } } else if (mCheckOpsDelegate != null) { - return startDelegateProxyOperationImpl(clientId, code, - attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, skipProxyOperation); + return startDelegateProxyOperationImpl(code, attributionSource, + startIfModeDefault, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, + proxiedAttributionFlags, attributionChainId); } - return startProxyOperationImpl(clientId, code, attributionSource, startIfModeDefault, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation); + return startProxyOperationImpl(code, attributionSource, startIfModeDefault, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation, + proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); } - - private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder token, int code, + private SyncNotedAppOp startDelegateProxyOperationImpl(int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, - boolean skipProxyOperation) { - return mCheckOpsDelegate.startProxyOperation(token, code, attributionSource, + boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, + @AttributionFlags int proxiedAttributionFlsgs, int attributionChainId) { + return mCheckOpsDelegate.startProxyOperation(code, attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - skipProxyOperation, AppOpsService.this::startProxyOperationImpl); + skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlsgs, + attributionChainId, AppOpsService.this::startProxyOperationImpl); } - public void finishProxyOperation(IBinder clientId, int code, - @NonNull AttributionSource attributionSource) { + public void finishProxyOperation(int code, + @NonNull AttributionSource attributionSource, boolean skipProxyOperation) { if (mPolicy != null) { if (mCheckOpsDelegate != null) { - mPolicy.finishProxyOperation(clientId, code, attributionSource, - this::finishDelegateProxyOperationImpl); + mPolicy.finishProxyOperation(code, attributionSource, + skipProxyOperation, this::finishDelegateProxyOperationImpl); } else { - mPolicy.finishProxyOperation(clientId, code, attributionSource, - AppOpsService.this::finishProxyOperationImpl); + mPolicy.finishProxyOperation(code, attributionSource, + skipProxyOperation, AppOpsService.this::finishProxyOperationImpl); } } else if (mCheckOpsDelegate != null) { - finishDelegateProxyOperationImpl(clientId, code, attributionSource); + finishDelegateProxyOperationImpl(code, attributionSource, skipProxyOperation); + } else { + finishProxyOperationImpl(code, attributionSource, skipProxyOperation); } - finishProxyOperationImpl(clientId, code, attributionSource); } - private Void finishDelegateProxyOperationImpl(IBinder clientId, int code, - @NonNull AttributionSource attributionSource) { - mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource, + private Void finishDelegateProxyOperationImpl(int code, + @NonNull AttributionSource attributionSource, boolean skipProxyOperation) { + mCheckOpsDelegate.finishProxyOperation(code, attributionSource, skipProxyOperation, AppOpsService.this::finishProxyOperationImpl); return null; } diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index c1209d466d20..62db886b90e9 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -154,7 +154,7 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { // must have the required permission and the users must be in the same profile group // in order to launch any of its own activities. if (callerUserId != userId) { - final int permissionFlag = PermissionChecker.checkPermissionForPreflight( + final int permissionFlag = PermissionChecker.checkPermissionForPreflight( mContext, INTERACT_ACROSS_PROFILES, callingPid, diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 552351119393..24c095b8117a 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED; import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED; @@ -65,6 +66,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.AppOpsManager.AttributionFlags; import android.app.IActivityManager; import android.app.admin.DevicePolicyManagerInternal; import android.compat.annotation.ChangeId; @@ -109,6 +111,7 @@ import android.os.storage.StorageManager; import android.permission.IOnPermissionsChangeListener; import android.permission.IPermissionChecker; import android.permission.IPermissionManager; +import android.permission.PermissionCheckerManager; import android.permission.PermissionControllerManager; import android.permission.PermissionManager; import android.permission.PermissionManagerInternal; @@ -172,6 +175,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; /** @@ -3321,8 +3325,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Override - public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) { - return mAttributionSourceRegistry.registerAttributionSource(source); + public void registerAttributionSource(@NonNull AttributionSource source) { + mAttributionSourceRegistry.registerAttributionSource(source); } @Override @@ -5288,8 +5292,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { * @param permissionName the name of the permission to be checked * @param userId the user ID * @param superImpl the original implementation that can be delegated to - * @return {@link android.content.pm.PackageManager.PERMISSION_GRANTED} if the package has - * the permission, or {@link android.content.pm.PackageManager.PERMISSION_DENITED} otherwise + * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has + * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise * * @see android.content.pm.PackageManager#checkPermission(String, String) */ @@ -5303,8 +5307,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { * @param uid the UID to be checked * @param permissionName the name of the permission to be checked * @param superImpl the original implementation that can be delegated to - * @return {@link android.content.pm.PackageManager.PERMISSION_GRANTED} if the package has - * the permission, or {@link android.content.pm.PackageManager.PERMISSION_DENITED} otherwise + * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has + * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise */ int checkUidPermission(int uid, @NonNull String permissionName, BiFunction<Integer, String, Integer> superImpl); @@ -5388,8 +5392,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { private final WeakHashMap<IBinder, AttributionSource> mAttributions = new WeakHashMap<>(); - public @NonNull AttributionSource registerAttributionSource( - @NonNull AttributionSource source) { + public void registerAttributionSource(@NonNull AttributionSource source) { // Here we keep track of attribution sources that were created by an app // from an attribution chain that called into the app and the apps's // own attribution source. An app can register an attribution chain up @@ -5436,10 +5439,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } synchronized (mLock) { - final IBinder token = new Binder(); - final AttributionSource result = source.withToken(token); - mAttributions.put(token, result); - return result; + mAttributions.put(source.getToken(), source); } } @@ -5465,6 +5465,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions = new ConcurrentHashMap<>(); + private static final AtomicInteger sAttributionChainIds = new AtomicInteger(0); + private final @NonNull Context mContext; private final @NonNull AppOpsManager mAppOpsManager; @@ -5474,53 +5476,108 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Override - @PermissionChecker.PermissionResult + @PermissionCheckerManager.PermissionResult public int checkPermission(@NonNull String permission, @NonNull AttributionSourceState attributionSourceState, @Nullable String message, - boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) { + boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, + int attributedOp) { Objects.requireNonNull(permission); Objects.requireNonNull(attributionSourceState); final AttributionSource attributionSource = new AttributionSource( attributionSourceState); final int result = checkPermission(mContext, permission, attributionSource, message, - forDataDelivery, startDataDelivery, fromDatasource); + forDataDelivery, startDataDelivery, fromDatasource, attributedOp); // Finish any started op if some step in the attribution chain failed. if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) { - finishDataDelivery(AppOpsManager.permissionToOp(permission), - attributionSource.asState()); + if (attributedOp == AppOpsManager.OP_NONE) { + finishDataDelivery(AppOpsManager.permissionToOpCode(permission), + attributionSource.asState(), fromDatasource); + } else { + finishDataDelivery(attributedOp, attributionSource.asState(), fromDatasource); + } } return result; } @Override - public void finishDataDelivery(@NonNull String op, - @NonNull AttributionSourceState attributionSourceState) { - if (op == null || attributionSourceState.packageName == null) { + public void finishDataDelivery(int op, + @NonNull AttributionSourceState attributionSourceState, boolean fromDatasource) { + Objects.requireNonNull(attributionSourceState); + + if (op == AppOpsManager.OP_NONE) { return; } - mAppOpsManager.finishProxyOp(op, new AttributionSource(attributionSourceState)); - if (attributionSourceState.next != null) { - finishDataDelivery(op, attributionSourceState.next[0]); + + AttributionSource current = new AttributionSource(attributionSourceState); + AttributionSource next = null; + + while (true) { + final boolean skipCurrentFinish = (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.asState() == attributionSourceState) + && next != null && !current.isTrusted(mContext)) { + return; + } + + // 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.asState() == attributionSourceState && next != null + && next.getNext() == null); + final boolean selfAccess = singleReceiverFromDatasource || next == null; + + final AttributionSource accessorSource = (!singleReceiverFromDatasource) + ? current : next; + + if (selfAccess) { + final String resolvedPackageName = resolvePackageName(mContext, accessorSource); + if (resolvedPackageName == null) { + return; + } + mAppOpsManager.finishOp(accessorSource.getToken(), op, + accessorSource.getUid(), resolvedPackageName, + accessorSource.getAttributionTag()); + } else { + final AttributionSource resolvedAttributionSource = + resolveAttributionSource(mContext, accessorSource); + if (resolvedAttributionSource.getPackageName() == null) { + return; + } + mAppOpsManager.finishProxyOp(AppOpsManager.opToPublicName(op), + resolvedAttributionSource, skipCurrentFinish); + } + + if (next == null || next.getNext() == null) { + return; + } + + current = next; } } @Override - @PermissionChecker.PermissionResult + @PermissionCheckerManager.PermissionResult public int checkOp(int op, AttributionSourceState attributionSource, String message, boolean forDataDelivery, boolean startDataDelivery) { int result = checkOp(mContext, op, new AttributionSource(attributionSource), message, forDataDelivery, startDataDelivery); if (result != PermissionChecker.PERMISSION_GRANTED && startDataDelivery) { // Finish any started op if some step in the attribution chain failed. - finishDataDelivery(AppOpsManager.opToName(op), attributionSource); + finishDataDelivery(op, attributionSource, /*fromDatasource*/ false); } return result; } - @PermissionChecker.PermissionResult + @PermissionCheckerManager.PermissionResult private static int checkPermission(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, - boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) { + boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, + int attributedOp) { PermissionInfo permissionInfo = sPlatformPermissions.get(permission); if (permissionInfo == null) { @@ -5542,7 +5599,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } if (permissionInfo.isRuntime()) { return checkRuntimePermission(context, permission, attributionSource, message, - forDataDelivery, startDataDelivery, fromDatasource); + forDataDelivery, startDataDelivery, fromDatasource, attributedOp); } if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(), @@ -5551,15 +5608,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { } if (attributionSource.getNext() != null) { - return checkPermission(context, permission, - attributionSource.getNext(), message, forDataDelivery, - startDataDelivery, /*fromDatasource*/ false); + return checkPermission(context, permission, attributionSource.getNext(), message, + forDataDelivery, startDataDelivery, /*fromDatasource*/ false, attributedOp); } return PermissionChecker.PERMISSION_GRANTED; } - @PermissionChecker.PermissionResult + @PermissionCheckerManager.PermissionResult private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean fromDatasource) { @@ -5579,7 +5635,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // 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) + if (!(fromDatasource && current.equals(attributionSource)) && next != null && !current.isTrusted(context)) { return PermissionChecker.PERMISSION_HARD_DENIED; } @@ -5588,12 +5644,15 @@ public class PermissionManagerService extends IPermissionManager.Stub { // 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); + && current.equals(attributionSource) && next != null + && next.getNext() == null); final boolean selfAccess = singleReceiverFromDatasource || next == null; final int opMode = performOpTransaction(context, op, current, message, forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks, - selfAccess, singleReceiverFromDatasource); + selfAccess, singleReceiverFromDatasource, AppOpsManager.OP_NONE, + AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE, + AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); switch (opMode) { case AppOpsManager.MODE_IGNORED: @@ -5624,9 +5683,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, - boolean fromDatasource) { + boolean fromDatasource, int attributedOp) { // Now let's check the identity chain... final int op = AppOpsManager.permissionToOpCode(permission); + final int attributionChainId = (startDataDelivery) + ? sAttributionChainIds.incrementAndGet() + : AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; AttributionSource current = attributionSource; AttributionSource next = null; @@ -5637,7 +5699,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // 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) + if (!(fromDatasource && current.equals(attributionSource)) && next != null && !current.isTrusted(context)) { return PermissionChecker.PERMISSION_HARD_DENIED; } @@ -5671,12 +5733,21 @@ public class PermissionManagerService extends IPermissionManager.Stub { // 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); + && current.equals(attributionSource) + && next != null && next.getNext() == null); final boolean selfAccess = singleReceiverFromDatasource || next == null; + final int proxyAttributionFlags = (!skipCurrentChecks) + ? resolveProxyAttributionFlags(attributionSource, current, fromDatasource, + startDataDelivery, selfAccess) + : AppOpsManager.ATTRIBUTION_FLAGS_NONE; + final int proxiedAttributionFlags = resolveProxiedAttributionFlags( + attributionSource, next, fromDatasource, startDataDelivery, selfAccess); + final int opMode = performOpTransaction(context, op, current, message, forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess, - singleReceiverFromDatasource); + singleReceiverFromDatasource, attributedOp, proxyAttributionFlags, + proxiedAttributionFlags, attributionChainId); switch (opMode) { case AppOpsManager.MODE_ERRORED: { @@ -5707,6 +5778,50 @@ public class PermissionManagerService extends IPermissionManager.Stub { return permissionGranted; } + private static @AttributionFlags int resolveProxyAttributionFlags( + @NonNull AttributionSource attributionChain, + @NonNull AttributionSource current, boolean fromDatasource, + boolean startDataDelivery, boolean selfAccess) { + return resolveAttributionFlags(attributionChain, current, fromDatasource, + startDataDelivery, selfAccess, /*flagsForProxy*/ true); + } + + private static @AttributionFlags int resolveProxiedAttributionFlags( + @NonNull AttributionSource attributionChain, + @NonNull AttributionSource current, boolean fromDatasource, + boolean startDataDelivery, boolean selfAccess) { + return resolveAttributionFlags(attributionChain, current, fromDatasource, + startDataDelivery, selfAccess, /*flagsForProxy*/ false); + } + + private static @AttributionFlags int resolveAttributionFlags( + @NonNull AttributionSource attributionChain, + @NonNull AttributionSource current, boolean fromDatasource, + boolean startDataDelivery, boolean selfAccess, boolean flagsForProxy) { + if (current == null || !startDataDelivery) { + return AppOpsManager.ATTRIBUTION_FLAGS_NONE; + } + if (flagsForProxy) { + if (selfAccess) { + return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; + } else if (!fromDatasource && current.equals(attributionChain)) { + return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; + } + } else { + if (selfAccess) { + return AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; + } else if (fromDatasource && current.equals(attributionChain.getNext())) { + return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; + } else if (current.getNext() == null) { + return AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; + } + } + if (fromDatasource && current.equals(attributionChain)) { + return AppOpsManager.ATTRIBUTION_FLAGS_NONE; + } + return AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY; + } + private static int checkOp(@NonNull Context context, @NonNull int op, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean startDataDelivery) { @@ -5714,6 +5829,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { return PermissionChecker.PERMISSION_HARD_DENIED; } + final int attributionChainId = (startDataDelivery) + ? sAttributionChainIds.incrementAndGet() + : AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; + AttributionSource current = attributionSource; AttributionSource next = null; @@ -5730,9 +5849,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { // The access is for oneself if this is the single attribution source in the chain. final boolean selfAccess = (next == null); + final int proxyAttributionFlags = (!skipCurrentChecks) + ? resolveProxyAttributionFlags(attributionSource, current, + /*fromDatasource*/ false, startDataDelivery, selfAccess) + : AppOpsManager.ATTRIBUTION_FLAGS_NONE; + final int proxiedAttributionFlags = resolveProxiedAttributionFlags( + attributionSource, next, /*fromDatasource*/ false, startDataDelivery, + selfAccess); + final int opMode = performOpTransaction(context, op, current, message, forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess, - /*fromDatasource*/ false); + /*fromDatasource*/ false, AppOpsManager.OP_NONE, proxyAttributionFlags, + proxiedAttributionFlags, attributionChainId); switch (opMode) { case AppOpsManager.MODE_ERRORED: { @@ -5751,10 +5879,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } + @SuppressWarnings("ConstantConditions") 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) { + boolean selfAccess, boolean singleReceiverFromDatasource, int attributedOp, + @AttributionFlags int proxyAttributionFlags, + @AttributionFlags int proxiedAttributionFlags, int attributionChainId) { // 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 @@ -5786,36 +5917,75 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (resolvedAttributionSource.getPackageName() == null) { return AppOpsManager.MODE_ERRORED; } + // If the datasource is not in a trusted platform component then in would not + // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that + // an app is exposing runtime permission protected data but cannot blame others + // in a trusted way which would not properly show in permission usage UIs. + // As a fallback we note a proxy op that blames the app and the datasource. + int startedOp = op; + int checkedOpResult = MODE_ALLOWED; + int startedOpResult; + + // If the datasource wants to attribute to another app op we need to + // make sure the op for the permission and the attributed ops allow + // the operation. We return the less permissive of the two and check + // the permission op while start the attributed op. + if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) { + checkedOpResult = appOpsManager.checkOpNoThrow(op, + resolvedAttributionSource.getUid(), resolvedAttributionSource + .getPackageName()); + if (checkedOpResult == MODE_ERRORED) { + return checkedOpResult; + } + startedOp = attributedOp; + } if (selfAccess) { - // If the datasource is not in a trusted platform component then in would not - // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that - // an app is exposing runtime permission protected data but cannot blame others - // in a trusted way which would not properly show in permission usage UIs. - // As a fallback we note a proxy op that blames the app and the datasource. try { - return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(), + startedOpResult = appOpsManager.startOpNoThrow( + resolvedAttributionSource.getToken(), startedOp, + resolvedAttributionSource.getUid(), resolvedAttributionSource.getPackageName(), /*startIfModeDefault*/ false, resolvedAttributionSource.getAttributionTag(), - message); + message, proxyAttributionFlags, attributionChainId); } catch (SecurityException e) { Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with" + " platform defined runtime permission " + AppOpsManager.opToPermission(op) + " while not having " + Manifest.permission.UPDATE_APP_OPS_STATS); - return appOpsManager.startProxyOpNoThrow(op, attributionSource, message, - skipProxyOperation); + startedOpResult = appOpsManager.startProxyOpNoThrow(attributedOp, + attributionSource, message, skipProxyOperation, + proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); } } else { - return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message, - skipProxyOperation); + startedOpResult = appOpsManager.startProxyOpNoThrow(startedOp, + resolvedAttributionSource, message, skipProxyOperation, + proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); } + return Math.max(checkedOpResult, startedOpResult); } else { final AttributionSource resolvedAttributionSource = resolveAttributionSource( context, accessorSource); if (resolvedAttributionSource.getPackageName() == null) { return AppOpsManager.MODE_ERRORED; } + int notedOp = op; + int checkedOpResult = MODE_ALLOWED; + int notedOpResult; + + // If the datasource wants to attribute to another app op we need to + // make sure the op for the permission and the attributed ops allow + // the operation. We return the less permissive of the two and check + // the permission op while start the attributed op. + if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) { + checkedOpResult = appOpsManager.checkOpNoThrow(op, + resolvedAttributionSource.getUid(), resolvedAttributionSource + .getPackageName()); + if (checkedOpResult == MODE_ERRORED) { + return checkedOpResult; + } + notedOp = attributedOp; + } if (selfAccess) { // If the datasource is not in a trusted platform component then in would not // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that @@ -5823,7 +5993,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { // in a trusted way which would not properly show in permission usage UIs. // As a fallback we note a proxy op that blames the app and the datasource. try { - return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(), + notedOpResult = appOpsManager.noteOpNoThrow(notedOp, + resolvedAttributionSource.getUid(), resolvedAttributionSource.getPackageName(), resolvedAttributionSource.getAttributionTag(), message); @@ -5832,13 +6003,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { + " platform defined runtime permission " + AppOpsManager.opToPermission(op) + " while not having " + Manifest.permission.UPDATE_APP_OPS_STATS); - return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message, - skipProxyOperation); + notedOpResult = appOpsManager.noteProxyOpNoThrow(notedOp, attributionSource, + message, skipProxyOperation); } } else { - return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message, - skipProxyOperation); + notedOpResult = appOpsManager.noteProxyOpNoThrow(notedOp, + resolvedAttributionSource, message, skipProxyOperation); } + return Math.max(checkedOpResult, notedOpResult); } } diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 2cfbf26cf996..607bc569ad64 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -19,6 +19,7 @@ package com.android.server.policy; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.app.AppOpsManager.AttributionFlags; import android.app.AppOpsManagerInternal; import android.app.SyncNotedAppOp; import android.app.role.RoleManager; @@ -39,6 +40,7 @@ import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; +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; @@ -46,6 +48,7 @@ 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; import com.android.server.LocalServices; import java.util.List; @@ -179,32 +182,37 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat public SyncNotedAppOp startOperation(IBinder token, int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, - boolean shouldCollectMessage, @NonNull NonaFunction<IBinder, Integer, Integer, String, - String, Boolean, Boolean, String, Boolean, SyncNotedAppOp> superImpl) { + boolean shouldCollectMessage, @AttributionFlags int attributionFlags, + int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String, + String, Boolean, Boolean, String, Boolean, Integer, Integer, + SyncNotedAppOp> superImpl) { return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag), uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, - message, shouldCollectMessage); + message, shouldCollectMessage, attributionFlags, attributionChainId); } @Override - public SyncNotedAppOp startProxyOperation(IBinder token, int code, + public SyncNotedAppOp startProxyOperation(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> superImpl) { - return superImpl.apply(token, resolveDatasourceOp(code, attributionSource.getUid(), + boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, + @AttributionFlags int proxiedAttributionFlags, int attributionChainId, + @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean, + Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) { + return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(), attributionSource.getPackageName(), attributionSource.getAttributionTag()), attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, skipProxyOperation); + shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, + proxiedAttributionFlags, attributionChainId); } @Override - public void finishProxyOperation(IBinder clientId, int code, - @NonNull AttributionSource attributionSource, - @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl) { - superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(), + public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource, + boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource, + Boolean, Void> superImpl) { + superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(), attributionSource.getPackageName(), attributionSource.getAttributionTag()), - attributionSource); + attributionSource, skipProxyOperation); } private int resolveDatasourceOp(int code, int uid, @NonNull String packageName, diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java index 98bc0673f79c..a2aaccc679dc 100644 --- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java @@ -20,6 +20,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; @@ -66,7 +68,8 @@ public class AppOpsActiveWatcherTest { // Verify that we got called for the op being active verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS) .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA), - eq(Process.myUid()), eq(getContext().getPackageName()), eq(true)); + eq(Process.myUid()), eq(getContext().getPackageName()), + isNull(), eq(true), anyInt(), anyInt()); // This should be the only callback we got verifyNoMoreInteractions(listener); @@ -84,7 +87,8 @@ public class AppOpsActiveWatcherTest { // Verify that we got called for the op being active verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS) .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA), - eq(Process.myUid()), eq(getContext().getPackageName()), eq(false)); + eq(Process.myUid()), eq(getContext().getPackageName()), isNull(), + eq(false), anyInt(), anyInt()); // Verify that the op is not active assertThat(appOpsManager.isOperationActive(AppOpsManager.OP_CAMERA, @@ -121,7 +125,8 @@ public class AppOpsActiveWatcherTest { // We should get the callback again (and since we reset the listener, we therefore expect 1) verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS) .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA), - eq(Process.myUid()), eq(getContext().getPackageName()), eq(true)); + eq(Process.myUid()), eq(getContext().getPackageName()), isNull(), + eq(true), anyInt(), anyInt()); // Finish up appOpsManager.finishOp(AppOpsManager.OP_CAMERA); diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java index 2162c0b20eac..e811c1f315fe 100644 --- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java @@ -5,22 +5,29 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; + import static org.testng.Assert.assertThrows; +import android.Manifest; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.IApplicationThread; import android.app.admin.DevicePolicyManagerInternal; +import android.content.AttributionSourceState; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.PermissionChecker; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.CrossProfileAppsInternal; @@ -34,6 +41,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; import android.os.UserManager; +import android.permission.PermissionCheckerManager; import android.permission.PermissionManager; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; @@ -411,6 +419,18 @@ public class CrossProfileAppsServiceImplTest { } mActivityInfo.exported = false; + + // There's a bug in static mocking if the APK is large - so here is the next best thing... + doReturn(Context.PERMISSION_CHECKER_SERVICE).when(mContext) + .getSystemServiceName(PermissionCheckerManager.class); + PermissionCheckerManager permissionCheckerManager = mock(PermissionCheckerManager.class); + doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(permissionCheckerManager) + .checkPermission(eq(Manifest.permission.INTERACT_ACROSS_PROFILES), any( + AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(), + anyBoolean(), anyInt()); + doReturn(permissionCheckerManager).when(mContext).getSystemService( + Context.PERMISSION_CHECKER_SERVICE); + assertThrows( SecurityException.class, () -> |