summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java49
-rw-r--r--services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java308
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java177
-rw-r--r--services/core/java/com/android/server/wm/AppTaskImpl.java3
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java506
-rw-r--r--services/core/java/com/android/server/wm/OWNERS2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java2
10 files changed, 532 insertions, 528 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 582536b662ce..b2eb3830b02e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -324,7 +324,6 @@ import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
import android.util.MergedConfiguration;
-import android.util.Pair;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -725,9 +724,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private final boolean mIsUserAlwaysVisible;
/** Allow activity launches which would otherwise be blocked by
- * {@link ActivityTransitionSecurityController#checkActivityAllowedToStart}
+ * {@link BackgroundActivityStartController#checkActivityAllowedToStart}
*/
- private boolean mAllowCrossUidActivitySwitchFromBelow;
+ boolean mAllowCrossUidActivitySwitchFromBelow;
/** Have we been asked to have this token keep the screen frozen? */
private boolean mFreezingScreen;
@@ -10191,50 +10190,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mAllowCrossUidActivitySwitchFromBelow = allowed;
}
- /**
- * Determines if a source is allowed to add or remove activities from the task,
- * if the current ActivityRecord is above it in the stack
- *
- * A transition is blocked ({@code false} returned) if all of the following are met:
- * <pre>
- * 1. The source activity and the current activity record belong to different apps
- * (i.e, have different UIDs).
- * 2. Both the source activity and the current activity target U+
- * 3. The current activity has not set
- * {@link ActivityRecord#setAllowCrossUidActivitySwitchFromBelow(boolean)} to {@code true}
- * </pre>
- *
- * Returns a pair where the elements mean:
- * <pre>
- * First: {@code false} if we should actually block the transition (takes into consideration
- * feature flag and targetSdk).
- * Second: {@code false} if we should warn about the transition via toasts. This happens if
- * the transition would be blocked in case both the app was targeting U+ and the feature was
- * enabled.
- * </pre>
- *
- * @param sourceUid The source (s) activity performing the state change
- */
- Pair<Boolean, Boolean> allowCrossUidActivitySwitchFromBelow(int sourceUid) {
- int myUid = info.applicationInfo.uid;
- if (sourceUid == myUid) {
- return new Pair<>(true, true);
- }
-
- // If mAllowCrossUidActivitySwitchFromBelow is set, honor it.
- if (mAllowCrossUidActivitySwitchFromBelow) {
- return new Pair<>(true, true);
- }
-
- // If it is not set, default to true if both records target ≥ U, false otherwise
- // TODO(b/258792202) Replace with CompatChanges and replace Pair with boolean once feature
- // flag is removed
- boolean restrictActivitySwitch =
- ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(myUid)
- && ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(sourceUid);
- return new Pair<>(!restrictActivitySwitch, false);
- }
-
boolean getTurnScreenOnFlag() {
return mTurnScreenOn || containsTurnScreenOnWindow();
}
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index 19d8129bb606..16c14f51c8ff 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -53,7 +53,7 @@ class ActivitySecurityModelFeatureFlags {
private static final String KEY_ASM_EXEMPTED_PACKAGES = KEY_ASM_PREFIX
+ "asm_exempted_packages";
private static final int VALUE_DISABLE = 0;
- private static final int VALUE_ENABLE_FOR_U = 1;
+ private static final int VALUE_ENABLE_FOR_V = 1;
private static final int VALUE_ENABLE_FOR_ALL = 2;
private static final int DEFAULT_VALUE = VALUE_DISABLE;
@@ -84,7 +84,7 @@ class ActivitySecurityModelFeatureFlags {
private static boolean flagEnabledForUid(int flag, int uid) {
boolean flagEnabled = flag == VALUE_ENABLE_FOR_ALL
- || (flag == VALUE_ENABLE_FOR_U
+ || (flag == VALUE_ENABLE_FOR_V
&& CompatChanges.isChangeEnabled(ASM_RESTRICTIONS, uid));
if (flagEnabled) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index a6e50405e7d9..c39b266a7701 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -98,8 +98,6 @@ public class ActivityStartController {
/** Whether an {@link ActivityStarter} is currently executing (starting an Activity). */
private boolean mInExecution = false;
- private final BackgroundActivityStartController mBalController;
-
/**
* TODO(b/64750076): Capture information necessary for dump and
* {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object
@@ -122,7 +120,6 @@ public class ActivityStartController {
mFactory.setController(this);
mPendingRemoteAnimationRegistry = new PendingRemoteAnimationRegistry(service.mGlobalLock,
service.mH);
- mBalController = new BackgroundActivityStartController(mService, mSupervisor);
}
/**
@@ -666,8 +663,4 @@ public class ActivityStartController {
pw.println("(nothing)");
}
}
-
- BackgroundActivityStartController getBackgroundActivityLaunchController() {
- return mBalController;
- }
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 458d1e8fa04b..c732b22433b3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -74,16 +74,8 @@ import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_UID;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_DEFAULT;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PENDING_INTENT;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
-import static com.android.server.wm.BackgroundActivityStartController.balCodeToString;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
@@ -126,18 +118,14 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.service.voice.IVoiceInteractionSession;
import android.text.TextUtils;
-import android.util.Pair;
import android.util.Pools.SynchronizedPool;
import android.util.Slog;
-import android.widget.Toast;
import android.window.RemoteTransition;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.HeavyWeightSwitcherActivity;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.UiThread;
import com.android.server.am.PendingIntentRecord;
import com.android.server.pm.InstantAppResolver;
import com.android.server.power.ShutdownCheckPoints;
@@ -151,10 +139,6 @@ import com.android.server.wm.TaskFragment.EmbeddingCheckResult;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.Date;
-import java.util.StringJoiner;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
/**
* Controller for interpreting how and then launching an activity.
@@ -188,7 +172,7 @@ class ActivityStarter {
* Feature flag for go/activity-security rules
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
static final long ASM_RESTRICTIONS = 230590090L;
private final ActivityTaskManagerService mService;
@@ -1114,7 +1098,7 @@ class ActivityStarter {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
"shouldAbortBackgroundActivityStart");
BackgroundActivityStartController balController =
- mController.getBackgroundActivityLaunchController();
+ mSupervisor.getBackgroundActivityLaunchController();
balCode =
balController.checkBackgroundActivityStart(
callingUid,
@@ -1973,7 +1957,9 @@ class ActivityStarter {
}
}
- if (!checkActivitySecurityModel(r, newTask, targetTask)) {
+ if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart(
+ mSourceRecord, r, newTask, targetTask, mLaunchFlags, mBalCode, mCallingUid,
+ mRealCallingUid)) {
return START_ABORTED;
}
@@ -1981,226 +1967,6 @@ class ActivityStarter {
}
/**
- * TODO(b/263368846): Shift to BackgroundActivityStartController once class is ready
- * Log activity starts which violate one of the following rules of the
- * activity security model (ASM):
- * See go/activity-security for rationale behind the rules.
- * 1. Within a task, only an activity matching a top UID of the task can start activities
- * 2. Only activities within a foreground task, which match a top UID of the task, can
- * create a new task or bring an existing one into the foreground
- */
- private boolean checkActivitySecurityModel(ActivityRecord r, boolean newTask, Task targetTask) {
- // BAL Exception allowed in all cases
- if (mBalCode == BAL_ALLOW_ALLOWLISTED_UID) {
- return true;
- }
-
- // Intents with FLAG_ACTIVITY_NEW_TASK will always be considered as creating a new task
- // even if the intent is delivered to an existing task.
- boolean taskToFront = newTask
- || (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK;
-
- // BAL exception only allowed for new tasks
- if (taskToFront) {
- if (mBalCode == BAL_ALLOW_ALLOWLISTED_COMPONENT
- || mBalCode == BAL_ALLOW_PERMISSION
- || mBalCode == BAL_ALLOW_PENDING_INTENT
- || mBalCode == BAL_ALLOW_SAW_PERMISSION
- || mBalCode == BAL_ALLOW_VISIBLE_WINDOW) {
- return true;
- }
- }
-
- Pair<Boolean, Boolean> pair = null;
- if (mSourceRecord != null) {
- boolean passesAsmChecks = true;
- Task sourceTask = mSourceRecord.getTask();
-
- // Allow launching into a new task (or a task matching the launched activity's
- // affinity) only if the current task is foreground or mutating its own task.
- // The latter can happen eg. if caller uses NEW_TASK flag and the activity being
- // launched matches affinity of source task.
- if (taskToFront) {
- passesAsmChecks = sourceTask != null
- && (sourceTask.isVisible() || sourceTask == targetTask);
- }
-
- if (passesAsmChecks) {
- Task taskToCheck = taskToFront ? sourceTask : targetTask;
- pair = ActivityTaskSupervisor
- .doesTopActivityMatchingUidExistForAsm(taskToCheck, mSourceRecord.getUid(),
- mSourceRecord);
- }
- } else if (!taskToFront) {
- // We don't have a sourceRecord, and we're launching into an existing task.
- // Allow if callingUid is top of stack.
- pair = ActivityTaskSupervisor
- .doesTopActivityMatchingUidExistForAsm(targetTask, mCallingUid,
- /*sourceRecord*/null);
- }
-
- boolean shouldBlockActivityStart = true;
- if (pair != null) {
- // We block if feature flag is enabled
- shouldBlockActivityStart = !pair.first;
- // Used for logging/toasts. Would we block if target sdk was U and feature was
- // enabled? If so, we can't return here but we also might not block at the end
- boolean wouldBlockActivityStartIgnoringFlags = !pair.second;
-
- if (!wouldBlockActivityStartIgnoringFlags) {
- return true;
- }
- }
-
- // ASM rules have failed. Log why
- return logAsmFailureAndCheckFeatureEnabled(r, newTask, targetTask, shouldBlockActivityStart,
- taskToFront);
- }
-
- private boolean logAsmFailureAndCheckFeatureEnabled(ActivityRecord r, boolean newTask,
- Task targetTask, boolean shouldBlockActivityStart, boolean taskToFront) {
- // ASM rules have failed. Log why
- ActivityRecord targetTopActivity = targetTask == null ? null
- : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
-
- int action = newTask || mSourceRecord == null
- ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK
- : (mSourceRecord.getTask().equals(targetTask)
- ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
- : FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
-
- boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags
- .shouldRestrictActivitySwitch(mCallingUid)
- && shouldBlockActivityStart;
-
- String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", r, targetTask,
- targetTopActivity, blockActivityStartAndFeatureEnabled, /*taskToFront*/taskToFront);
-
- FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
- /* caller_uid */
- mSourceRecord != null ? mSourceRecord.getUid() : mCallingUid,
- /* caller_activity_class_name */
- mSourceRecord != null ? mSourceRecord.info.name : null,
- /* target_task_top_activity_uid */
- targetTopActivity != null ? targetTopActivity.getUid() : -1,
- /* target_task_top_activity_class_name */
- targetTopActivity != null ? targetTopActivity.info.name : null,
- /* target_task_is_different */
- newTask || mSourceRecord == null || targetTask == null
- || !targetTask.equals(mSourceRecord.getTask()),
- /* target_activity_uid */
- r.getUid(),
- /* target_activity_class_name */
- r.info.name,
- /* target_intent_action */
- r.intent.getAction(),
- /* target_intent_flags */
- mLaunchFlags,
- /* action */
- action,
- /* version */
- ActivitySecurityModelFeatureFlags.ASM_VERSION,
- /* multi_window - we have our source not in the target task, but both are visible */
- targetTask != null && mSourceRecord != null
- && !targetTask.equals(mSourceRecord.getTask()) && targetTask.isVisible(),
- /* bal_code */
- mBalCode,
- /* task_stack */
- asmDebugInfo
- );
-
- String launchedFromPackageName = r.launchedFromPackage;
- if (ActivitySecurityModelFeatureFlags.shouldShowToast(mCallingUid)) {
- String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
- + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
- + getApplicationLabel(mService.mContext.getPackageManager(),
- launchedFromPackageName);
- UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
- toastText, Toast.LENGTH_LONG).show());
-
- Slog.i(TAG, asmDebugInfo);
- }
-
- if (blockActivityStartAndFeatureEnabled) {
- Slog.e(TAG, "[ASM] Abort Launching r: " + r
- + " as source: "
- + (mSourceRecord != null ? mSourceRecord : launchedFromPackageName)
- + " is in background. New task: " + newTask
- + ". Top activity: " + targetTopActivity
- + ". BAL Code: " + balCodeToString(mBalCode));
-
- return false;
- }
-
- return true;
- }
-
- /** Only called when an activity launch may be blocked, which should happen very rarely */
- private String getDebugInfoForActivitySecurity(String action, ActivityRecord r, Task targetTask,
- ActivityRecord targetTopActivity, boolean blockActivityStartAndFeatureEnabled,
- boolean taskToFront) {
- final String prefix = "[ASM] ";
- Function<ActivityRecord, String> recordToString = (ar) -> {
- if (ar == null) {
- return null;
- }
- return (ar == mSourceRecord ? " [source]=> "
- : ar == targetTopActivity ? " [ top ]=> "
- : ar == r ? " [target]=> "
- : " => ")
- + ar
- + " :: visible=" + ar.isVisible()
- + ", finishing=" + ar.isFinishing()
- + ", alwaysOnTop=" + ar.isAlwaysOnTop()
- + ", taskFragment=" + ar.getTaskFragment();
- };
-
- StringJoiner joiner = new StringJoiner("\n");
- joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
- joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
- joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
-
- boolean targetTaskMatchesSourceTask = targetTask != null
- && mSourceRecord != null && mSourceRecord.getTask() == targetTask;
-
- if (mSourceRecord == null) {
- joiner.add(prefix + "Source Package: " + r.launchedFromPackage);
- String realCallingPackage = mService.mContext.getPackageManager().getNameForUid(
- mRealCallingUid);
- joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage);
- } else {
- joiner.add(prefix + "Source Record: " + recordToString.apply(mSourceRecord));
- if (targetTaskMatchesSourceTask) {
- joiner.add(prefix + "Source/Target Task: " + mSourceRecord.getTask());
- joiner.add(prefix + "Source/Target Task Stack: ");
- } else {
- joiner.add(prefix + "Source Task: " + mSourceRecord.getTask());
- joiner.add(prefix + "Source Task Stack: ");
- }
- mSourceRecord.getTask().forAllActivities((Consumer<ActivityRecord>)
- ar -> joiner.add(prefix + recordToString.apply(ar)));
- }
-
- joiner.add(prefix + "Target Task Top: " + recordToString.apply(targetTopActivity));
- if (!targetTaskMatchesSourceTask) {
- joiner.add(prefix + "Target Task: " + targetTask);
- if (targetTask != null) {
- joiner.add(prefix + "Target Task Stack: ");
- targetTask.forAllActivities((Consumer<ActivityRecord>)
- ar -> joiner.add(prefix + recordToString.apply(ar)));
- }
- }
-
- joiner.add(prefix + "Target Record: " + recordToString.apply(r));
- joiner.add(prefix + "Intent: " + mIntent);
- joiner.add(prefix + "TaskToFront: " + taskToFront);
- joiner.add(prefix + "BalCode: " + balCodeToString(mBalCode));
-
- joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------");
- return joiner.toString();
- }
-
- /**
* Returns whether embedding of {@code starting} is allowed.
*
* @param taskFragment the TaskFragment for embedding.
@@ -2284,8 +2050,9 @@ class ActivityStarter {
reusedTask != null ? reusedTask.getTopNonFinishingActivity() : null, intentGrants);
if (mAddingToTask) {
- clearTopIfNeeded(targetTask, mCallingUid, mRealCallingUid, mStartActivity.getUid(),
- mLaunchFlags);
+ mSupervisor.getBackgroundActivityLaunchController().clearTopIfNeeded(targetTask,
+ mSourceRecord, mStartActivity, mCallingUid, mRealCallingUid, mLaunchFlags,
+ mBalCode);
return START_SUCCESS;
}
@@ -2318,65 +2085,6 @@ class ActivityStarter {
}
/**
- * If the top activity uid does not match the launching or launched activity, and the launch was
- * not requested from the top uid, we want to clear out all non matching activities to prevent
- * the top activity being sandwiched.
- *
- * Both creator and sender UID are considered for the launching activity.
- */
- private void clearTopIfNeeded(@NonNull Task targetTask, int callingUid, int realCallingUid,
- int startingUid, int launchFlags) {
- if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK
- || mBalCode == BAL_ALLOW_ALLOWLISTED_UID) {
- // Launch is from the same task, (a top or privileged UID), or is directly privileged.
- return;
- }
-
- Predicate<ActivityRecord> isLaunchingOrLaunched = ar ->
- ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid);
-
- // Return early if we know for sure we won't need to clear any activities by just checking
- // the top activity.
- ActivityRecord targetTaskTop = targetTask.getTopMostActivity();
- if (targetTaskTop == null || isLaunchingOrLaunched.test(targetTaskTop)) {
- return;
- }
-
- // Find the first activity which matches a safe UID and is not finishing. Clear everything
- // above it
- boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags
- .shouldRestrictActivitySwitch(callingUid);
- int[] finishCount = new int[0];
- if (shouldBlockActivityStart) {
- ActivityRecord activity = targetTask.getActivity(isLaunchingOrLaunched);
- if (activity == null) {
- // mStartActivity is not in task, so clear everything
- activity = mStartActivity;
- }
-
- finishCount = new int[1];
- targetTask.performClearTop(activity, launchFlags, finishCount);
- if (finishCount[0] > 0) {
- Slog.w(TAG, "Cleared top n: " + finishCount[0] + " activities from task t: "
- + targetTask + " not matching top uid: " + callingUid);
- }
- }
-
- if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)
- && (!shouldBlockActivityStart || finishCount[0] > 0)) {
- UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
- (shouldBlockActivityStart
- ? "Top activities cleared by "
- : "Top activities would be cleared by ")
- + ActivitySecurityModelFeatureFlags.DOC_LINK,
- Toast.LENGTH_LONG).show());
-
- getDebugInfoForActivitySecurity("Clear Top", mStartActivity, targetTask, targetTaskTop,
- shouldBlockActivityStart, /* taskToFront */ true);
- }
- }
-
- /**
* Check if the activity being launched is the same as the one currently at the top and it
* should only be launched once.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index fd42077bed7d..7fff5754caaa 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2221,7 +2221,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
callerApp = getProcessController(appThread);
}
final BackgroundActivityStartController balController =
- getActivityStartController().getBackgroundActivityLaunchController();
+ mTaskSupervisor.getBackgroundActivityLaunchController();
if (balController.shouldAbortBackgroundActivityStart(
callingUid,
callingPid,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 6eb9ed695133..69e679d71669 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -134,12 +134,10 @@ import android.os.WorkSource;
import android.provider.MediaStore;
import android.util.ArrayMap;
import android.util.MergedConfiguration;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;
-import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -147,10 +145,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
-import com.android.server.UiThread;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.HostingRecord;
import com.android.server.am.UserState;
@@ -256,6 +252,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
final ActivityTaskManagerService mService;
RootWindowContainer mRootWindowContainer;
+ /** Helper class for checking if an activity transition meets security rules */
+ BackgroundActivityStartController mBalController;
+
/** The historial list of recent tasks including inactive tasks */
RecentTasks mRecentTasks;
@@ -466,6 +465,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mLaunchParamsPersister = new LaunchParamsPersister(mPersisterQueue, this);
mLaunchParamsController = new LaunchParamsController(mService, mLaunchParamsPersister);
mLaunchParamsController.registerDefaultModifiers(this);
+
+ mBalController = new BackgroundActivityStartController(mService, this);
}
void onSystemReady() {
@@ -1284,6 +1285,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
return mAppOpsManager;
}
+ BackgroundActivityStartController getBackgroundActivityLaunchController() {
+ return mBalController;
+ }
+
private int getComponentRestrictionForCallingPackage(ActivityInfo activityInfo,
String callingPackage, @Nullable String callingFeatureId, int callingPid,
int callingUid, boolean ignoreTargetSecurity) {
@@ -1700,172 +1705,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
if (task.isPersistable) {
mService.notifyTaskPersisterLocked(null, true);
}
- checkActivitySecurityForTaskClear(callingUid, task, callerActivityClassName);
+ mBalController
+ .checkActivityAllowedToClearTask(task, callingUid, callerActivityClassName);
} finally {
task.mInRemoveTask = false;
}
}
-
- // TODO(b/263368846) Move to live with the rest of the ASM logic.
- /**
- * Returns home if the passed in callingUid is not top of the stack, rather than returning to
- * previous task.
- */
- private void checkActivitySecurityForTaskClear(int callingUid, Task task,
- String callerActivityClassName) {
- // We may have already checked that the callingUid has additional clearTask privileges, and
- // cleared the calling identify. If so, we infer we do not need further restrictions here.
- if (callingUid == SYSTEM_UID || !task.isVisible() || task.inMultiWindowMode()) {
- return;
- }
-
- TaskDisplayArea displayArea = task.getTaskDisplayArea();
- if (displayArea == null) {
- // If there is no associated display area, we can not return home.
- return;
- }
-
- Pair<Boolean, Boolean> pair = doesTopActivityMatchingUidExistForAsm(task, callingUid, null);
- boolean shouldBlockActivitySwitchIfFeatureEnabled = !pair.first;
- boolean wouldBlockActivitySwitchIgnoringFlags = !pair.second;
-
- if (!wouldBlockActivitySwitchIgnoringFlags) {
- return;
- }
-
- ActivityRecord topActivity = task.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
- FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
- /* caller_uid */
- callingUid,
- /* caller_activity_class_name */
- callerActivityClassName,
- /* target_task_top_activity_uid */
- topActivity == null ? -1 : topActivity.getUid(),
- /* target_task_top_activity_class_name */
- topActivity == null ? null : topActivity.info.name,
- /* target_task_is_different */
- false,
- /* target_activity_uid */
- -1,
- /* target_activity_class_name */
- null,
- /* target_intent_action */
- null,
- /* target_intent_flags */
- 0,
- /* action */
- FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
- /* version */
- ActivitySecurityModelFeatureFlags.ASM_VERSION,
- /* multi_window */
- false,
- /* bal_code */
- -1,
- /* task_stack */
- null
- );
-
- boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
- .shouldRestrictActivitySwitch(callingUid)
- && shouldBlockActivitySwitchIfFeatureEnabled;
-
- PackageManager pm = mService.mContext.getPackageManager();
- String callingPackage = pm.getNameForUid(callingUid);
- final CharSequence callingLabel;
- if (callingPackage == null) {
- callingPackage = String.valueOf(callingUid);
- callingLabel = callingPackage;
- } else {
- callingLabel = getApplicationLabel(pm, callingPackage);
- }
-
- if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
- UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
- (ActivitySecurityModelFeatureFlags.DOC_LINK
- + (restrictActivitySwitch ? " returned home due to "
- : " would return home due to ")
- + callingLabel), Toast.LENGTH_LONG).show());
- }
-
- // If the activity switch should be restricted, return home rather than the
- // previously top task, to prevent users from being confused which app they're
- // viewing
- if (restrictActivitySwitch) {
- Slog.w(TAG, "[ASM] Return to home as source: " + callingPackage
- + " is not on top of task t: " + task);
- displayArea.moveHomeActivityToTop("taskRemoved");
- } else {
- Slog.i(TAG, "[ASM] Would return to home as source: " + callingPackage
- + " is not on top of task t: " + task);
- }
- }
-
- /**
- * For the purpose of ASM, ‘Top UID” for a task is defined as an activity UID
- * 1. Which is top of the stack in z-order
- * a. Excluding any activities with the flag ‘isAlwaysOnTop’ and
- * b. Excluding any activities which are `finishing`
- * 2. Or top of an adjacent task fragment to (1)
- *
- * The 'sourceRecord' can be considered top even if it is 'finishing'
- *
- * @return A pair where the first value is the return value matching the checks above, and the
- * second value is the return value disregarding the feature flag or target api levels. Use the
- * first value for blocking launches - the second value is only used to determine if a toast
- * should be displayed, and will be used alongside a feature flag in {@link ActivityStarter}.
- */
- // TODO(b/263368846) Shift to BackgroundActivityStartController once class is ready
- @Nullable
- static Pair<Boolean, Boolean> doesTopActivityMatchingUidExistForAsm(@Nullable Task task,
- int uid, @Nullable ActivityRecord sourceRecord) {
- // If the source is visible, consider it 'top'.
- if (sourceRecord != null && sourceRecord.isVisible()) {
- return new Pair<>(true, true);
- }
-
- // Always allow actual top activity to clear task
- ActivityRecord topActivity = task.getTopMostActivity();
- if (topActivity != null && topActivity.isUid(uid)) {
- return new Pair<>(true, true);
- }
-
- // Consider the source activity, whether or not it is finishing. Do not consider any other
- // finishing activity.
- Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
- || (!ar.finishing && !ar.isAlwaysOnTop());
-
- // Check top of stack (or the first task fragment for embedding).
- topActivity = task.getActivity(topOfStackPredicate);
- if (topActivity == null) {
- return new Pair<>(false, false);
- }
-
- Pair<Boolean, Boolean> pair = topActivity.allowCrossUidActivitySwitchFromBelow(uid);
- if (pair.first) {
- return new Pair<>(true, pair.second);
- }
-
- // Even if the top activity is not a match, we may be in an embedded activity scenario with
- // an adjacent task fragment. Get the second fragment.
- TaskFragment taskFragment = topActivity.getTaskFragment();
- if (taskFragment == null) {
- return new Pair<>(false, false);
- }
-
- TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
- if (adjacentTaskFragment == null) {
- return new Pair<>(false, false);
- }
-
- // Check the second fragment.
- topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
- if (topActivity == null) {
- return new Pair<>(false, false);
- }
-
- return topActivity.allowCrossUidActivitySwitchFromBelow(uid);
- }
-
static CharSequence getApplicationLabel(PackageManager pm, String packageName) {
try {
ApplicationInfo launchedFromPackageInfo = pm.getApplicationInfo(
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index de38a20d9b53..761b0a8f0b39 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -122,8 +122,7 @@ class AppTaskImpl extends IAppTask.Stub {
callerApp = mService.getProcessController(appThread);
}
final BackgroundActivityStartController balController =
- mService.getActivityStartController()
- .getBackgroundActivityLaunchController();
+ mService.mTaskSupervisor.getBackgroundActivityLaunchController();
if (balController.shouldAbortBackgroundActivityStart(
callingUid,
callingPid,
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 188f4d0b8ced..c3c16d59deab 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -17,7 +17,9 @@
package com.android.server.wm;
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.SYSTEM_UID;
import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
import static com.android.internal.util.Preconditions.checkState;
@@ -26,10 +28,12 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
+import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -45,12 +49,17 @@ import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.Slog;
-
+import android.widget.Toast;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.UiThread;
import com.android.server.am.PendingIntentRecord;
import java.lang.annotation.Retention;
+import java.util.StringJoiner;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
/**
* Helper class to check permissions for starting Activities.
@@ -575,6 +584,487 @@ public class BackgroundActivityStartController {
return BAL_BLOCK;
}
+ /**
+ * Log activity starts which violate one of the following rules of the
+ * activity security model (ASM):
+ * See go/activity-security for rationale behind the rules.
+ * 1. Within a task, only an activity matching a top UID of the task can start activities
+ * 2. Only activities within a foreground task, which match a top UID of the task, can
+ * create a new task or bring an existing one into the foreground
+ */
+ boolean checkActivityAllowedToStart(@Nullable ActivityRecord sourceRecord,
+ @NonNull ActivityRecord targetRecord, boolean newTask, @Nullable Task targetTask,
+ int launchFlags, int balCode, int callingUid, int realCallingUid) {
+ // BAL Exception allowed in all cases
+ if (balCode == BAL_ALLOW_ALLOWLISTED_UID) {
+ return true;
+ }
+
+ // Intents with FLAG_ACTIVITY_NEW_TASK will always be considered as creating a new task
+ // even if the intent is delivered to an existing task.
+ boolean taskToFront = newTask
+ || (launchFlags & FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK;
+
+ // BAL exception only allowed for new tasks
+ if (taskToFront) {
+ if (balCode == BAL_ALLOW_ALLOWLISTED_COMPONENT
+ || balCode == BAL_ALLOW_PERMISSION
+ || balCode == BAL_ALLOW_PENDING_INTENT
+ || balCode == BAL_ALLOW_SAW_PERMISSION
+ || balCode == BAL_ALLOW_VISIBLE_WINDOW) {
+ return true;
+ }
+ }
+
+ BlockActivityStart bas = null;
+ if (sourceRecord != null) {
+ boolean passesAsmChecks = true;
+ Task sourceTask = sourceRecord.getTask();
+
+ // Allow launching into a new task (or a task matching the launched activity's
+ // affinity) only if the current task is foreground or mutating its own task.
+ // The latter can happen eg. if caller uses NEW_TASK flag and the activity being
+ // launched matches affinity of source task.
+ if (taskToFront) {
+ passesAsmChecks = sourceTask != null
+ && (sourceTask.isVisible() || sourceTask == targetTask);
+ }
+
+ if (passesAsmChecks) {
+ Task taskToCheck = taskToFront ? sourceTask : targetTask;
+ bas = isTopActivityMatchingUidAbsentForAsm(taskToCheck, sourceRecord.getUid(),
+ sourceRecord);
+ }
+ } else if (!taskToFront) {
+ // We don't have a sourceRecord, and we're launching into an existing task.
+ // Allow if callingUid is top of stack.
+ bas = isTopActivityMatchingUidAbsentForAsm(targetTask, callingUid,
+ /*sourceRecord*/null);
+ }
+
+ if (bas != null && !bas.mWouldBlockActivityStartIgnoringFlag) {
+ return true;
+ }
+
+ // ASM rules have failed. Log why
+ return logAsmFailureAndCheckFeatureEnabled(sourceRecord, callingUid, realCallingUid,
+ newTask, targetTask, targetRecord, balCode, launchFlags, bas, taskToFront);
+ }
+
+ private boolean logAsmFailureAndCheckFeatureEnabled(ActivityRecord sourceRecord, int callingUid,
+ int realCallingUid, boolean newTask, Task targetTask, ActivityRecord targetRecord,
+ @BalCode int balCode, int launchFlags, BlockActivityStart bas, boolean taskToFront) {
+
+ // ASM rules have failed. Log why
+ ActivityRecord targetTopActivity = targetTask == null ? null
+ : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
+
+ int action = newTask || sourceRecord == null
+ ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK
+ : (sourceRecord.getTask().equals(targetTask)
+ ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
+ : FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
+
+ boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags
+ .shouldRestrictActivitySwitch(callingUid)
+ && (bas == null || bas.mBlockActivityStartIfFlagEnabled);
+
+ String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", sourceRecord,
+ targetRecord, targetTask, targetTopActivity, realCallingUid, balCode,
+ blockActivityStartAndFeatureEnabled, taskToFront);
+
+ FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+ /* caller_uid */
+ sourceRecord != null ? sourceRecord.getUid() : callingUid,
+ /* caller_activity_class_name */
+ sourceRecord != null ? sourceRecord.info.name : null,
+ /* target_task_top_activity_uid */
+ targetTopActivity != null ? targetTopActivity.getUid() : -1,
+ /* target_task_top_activity_class_name */
+ targetTopActivity != null ? targetTopActivity.info.name : null,
+ /* target_task_is_different */
+ newTask || sourceRecord == null || targetTask == null
+ || !targetTask.equals(sourceRecord.getTask()),
+ /* target_activity_uid */
+ targetRecord.getUid(),
+ /* target_activity_class_name */
+ targetRecord.info.name,
+ /* target_intent_action */
+ targetRecord.intent.getAction(),
+ /* target_intent_flags */
+ launchFlags,
+ /* action */
+ action,
+ /* version */
+ ActivitySecurityModelFeatureFlags.ASM_VERSION,
+ /* multi_window - we have our source not in the target task, but both are visible */
+ targetTask != null && sourceRecord != null
+ && !targetTask.equals(sourceRecord.getTask()) && targetTask.isVisible(),
+ /* bal_code */
+ balCode,
+ /* debug_info */
+ asmDebugInfo
+ );
+
+ String launchedFromPackageName = targetRecord.launchedFromPackage;
+ if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
+ String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
+ + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
+ + getApplicationLabel(mService.mContext.getPackageManager(),
+ launchedFromPackageName);
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+ toastText, Toast.LENGTH_LONG).show());
+
+ Slog.i(TAG, asmDebugInfo);
+ }
+
+ if (blockActivityStartAndFeatureEnabled) {
+ Slog.e(TAG, "[ASM] Abort Launching r: " + targetRecord
+ + " as source: "
+ + (sourceRecord != null ? sourceRecord : launchedFromPackageName)
+ + " is in background. New task: " + newTask
+ + ". Top activity: " + targetTopActivity
+ + ". BAL Code: " + balCodeToString(balCode));
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * If the top activity uid does not match the launching or launched activity, and the launch was
+ * not requested from the top uid, we want to clear out all non matching activities to prevent
+ * the top activity being sandwiched.
+ * Both creator and sender UID are considered for the launching activity.
+ */
+ void clearTopIfNeeded(@NonNull Task targetTask, @Nullable ActivityRecord sourceRecord,
+ @NonNull ActivityRecord targetRecord, int callingUid, int realCallingUid,
+ int launchFlags, @BalCode int balCode) {
+ if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK
+ || balCode == BAL_ALLOW_ALLOWLISTED_UID) {
+ // Launch is from the same task, (a top or privileged UID), or is directly privileged.
+ return;
+ }
+
+ int startingUid = targetRecord.getUid();
+ Predicate<ActivityRecord> isLaunchingOrLaunched = ar ->
+ ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid);
+
+ // Return early if we know for sure we won't need to clear any activities by just checking
+ // the top activity.
+ ActivityRecord targetTaskTop = targetTask.getTopMostActivity();
+ if (targetTaskTop == null || isLaunchingOrLaunched.test(targetTaskTop)) {
+ return;
+ }
+
+ // Find the first activity which matches a safe UID and is not finishing. Clear everything
+ // above it
+ boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags
+ .shouldRestrictActivitySwitch(callingUid);
+ int[] finishCount = new int[0];
+ if (shouldBlockActivityStart) {
+ ActivityRecord activity = targetTask.getActivity(isLaunchingOrLaunched);
+ if (activity == null) {
+ // mStartActivity is not in task, so clear everything
+ activity = targetRecord;
+ }
+
+ finishCount = new int[1];
+ targetTask.performClearTop(activity, launchFlags, finishCount);
+ if (finishCount[0] > 0) {
+ Slog.w(TAG, "Cleared top n: " + finishCount[0] + " activities from task t: "
+ + targetTask + " not matching top uid: " + callingUid);
+ }
+ }
+
+ if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)
+ && (!shouldBlockActivityStart || finishCount[0] > 0)) {
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+ (shouldBlockActivityStart
+ ? "Top activities cleared by "
+ : "Top activities would be cleared by ")
+ + ActivitySecurityModelFeatureFlags.DOC_LINK,
+ Toast.LENGTH_LONG).show());
+
+ Slog.i(TAG, getDebugInfoForActivitySecurity("Clear Top", sourceRecord, targetRecord,
+ targetTask, targetTaskTop, realCallingUid, balCode, shouldBlockActivityStart,
+ /* taskToFront */ true));
+ }
+ }
+
+ /**
+ * Returns home if the passed in callingUid is not top of the stack, rather than returning to
+ * previous task.
+ */
+ void checkActivityAllowedToClearTask(@NonNull Task task, int callingUid,
+ @NonNull String callerActivityClassName) {
+ // We may have already checked that the callingUid has additional clearTask privileges, and
+ // cleared the calling identify. If so, we infer we do not need further restrictions here.
+ if (callingUid == SYSTEM_UID || !task.isVisible() || task.inMultiWindowMode()) {
+ return;
+ }
+
+ TaskDisplayArea displayArea = task.getTaskDisplayArea();
+ if (displayArea == null) {
+ // If there is no associated display area, we can not return home.
+ return;
+ }
+
+ BlockActivityStart bas = isTopActivityMatchingUidAbsentForAsm(task, callingUid, null);
+ if (!bas.mWouldBlockActivityStartIgnoringFlag) {
+ return;
+ }
+
+ ActivityRecord topActivity = task.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
+ FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+ /* caller_uid */
+ callingUid,
+ /* caller_activity_class_name */
+ callerActivityClassName,
+ /* target_task_top_activity_uid */
+ topActivity == null ? -1 : topActivity.getUid(),
+ /* target_task_top_activity_class_name */
+ topActivity == null ? null : topActivity.info.name,
+ /* target_task_is_different */
+ false,
+ /* target_activity_uid */
+ -1,
+ /* target_activity_class_name */
+ null,
+ /* target_intent_action */
+ null,
+ /* target_intent_flags */
+ 0,
+ /* action */
+ FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
+ /* version */
+ ActivitySecurityModelFeatureFlags.ASM_VERSION,
+ /* multi_window */
+ false,
+ /* bal_code */
+ -1,
+ /* debug_info */
+ null
+ );
+
+ boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
+ .shouldRestrictActivitySwitch(callingUid)
+ && bas.mBlockActivityStartIfFlagEnabled;
+
+ PackageManager pm = mService.mContext.getPackageManager();
+ String callingPackage = pm.getNameForUid(callingUid);
+ final CharSequence callingLabel;
+ if (callingPackage == null) {
+ callingPackage = String.valueOf(callingUid);
+ callingLabel = callingPackage;
+ } else {
+ callingLabel = getApplicationLabel(pm, callingPackage);
+ }
+
+ if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+ (ActivitySecurityModelFeatureFlags.DOC_LINK
+ + (restrictActivitySwitch ? " returned home due to "
+ : " would return home due to ")
+ + callingLabel), Toast.LENGTH_LONG).show());
+ }
+
+ // If the activity switch should be restricted, return home rather than the
+ // previously top task, to prevent users from being confused which app they're
+ // viewing
+ if (restrictActivitySwitch) {
+ Slog.w(TAG, "[ASM] Return to home as source: " + callingPackage
+ + " is not on top of task t: " + task);
+ displayArea.moveHomeActivityToTop("taskRemoved");
+ } else {
+ Slog.i(TAG, "[ASM] Would return to home as source: " + callingPackage
+ + " is not on top of task t: " + task);
+ }
+ }
+
+ /**
+ * For the purpose of ASM, ‘Top UID” for a task is defined as an activity UID
+ * 1. Which is top of the stack in z-order
+ * a. Excluding any activities with the flag ‘isAlwaysOnTop’ and
+ * b. Excluding any activities which are `finishing`
+ * 2. Or top of an adjacent task fragment to (1)
+ *
+ * The 'sourceRecord' can be considered top even if it is 'finishing'
+ *
+ * Returns a class where the elements are:
+ * <pre>
+ * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
+ * consideration feature flag and targetSdk).
+ * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via
+ * toasts. This happens if the transition would be blocked in case both the app was targeting V+
+ * and the feature was enabled.
+ * </pre>
+ */
+ private BlockActivityStart isTopActivityMatchingUidAbsentForAsm(@NonNull Task task,
+ int uid, @Nullable ActivityRecord sourceRecord) {
+ // If the source is visible, consider it 'top'.
+ if (sourceRecord != null && sourceRecord.isVisible()) {
+ return new BlockActivityStart(false, false);
+ }
+
+ // Always allow actual top activity to clear task
+ ActivityRecord topActivity = task.getTopMostActivity();
+ if (topActivity != null && topActivity.isUid(uid)) {
+ return new BlockActivityStart(false, false);
+ }
+
+ // Consider the source activity, whether or not it is finishing. Do not consider any other
+ // finishing activity.
+ Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
+ || (!ar.finishing && !ar.isAlwaysOnTop());
+
+ // Check top of stack (or the first task fragment for embedding).
+ topActivity = task.getActivity(topOfStackPredicate);
+ if (topActivity == null) {
+ return new BlockActivityStart(true, true);
+ }
+
+ BlockActivityStart pair = blockCrossUidActivitySwitchFromBelow(topActivity, uid);
+ if (!pair.mBlockActivityStartIfFlagEnabled) {
+ return pair;
+ }
+
+ // Even if the top activity is not a match, we may be in an embedded activity scenario with
+ // an adjacent task fragment. Get the second fragment.
+ TaskFragment taskFragment = topActivity.getTaskFragment();
+ if (taskFragment == null) {
+ return pair;
+ }
+
+ TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ if (adjacentTaskFragment == null) {
+ return pair;
+ }
+
+ // Check the second fragment.
+ topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
+ if (topActivity == null) {
+ return new BlockActivityStart(true, true);
+ }
+
+ return blockCrossUidActivitySwitchFromBelow(topActivity, uid);
+ }
+
+ /**
+ * Determines if a source is allowed to add or remove activities from the task,
+ * if the current ActivityRecord is above it in the stack
+ *
+ * A transition is blocked ({@code false} returned) if all of the following are met:
+ * <pre>
+ * 1. The source activity and the current activity record belong to different apps
+ * (i.e, have different UIDs).
+ * 2. Both the source activity and the current activity target U+
+ * 3. The current activity has not set
+ * {@link ActivityRecord#setAllowCrossUidActivitySwitchFromBelow(boolean)} to {@code true}
+ * </pre>
+ *
+ * Returns a class where the elements are:
+ * <pre>
+ * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
+ * consideration feature flag and targetSdk).
+ * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via
+ * toasts. This happens if the transition would be blocked in case both the app was targeting V+
+ * and the feature was enabled.
+ * </pre>
+ *
+ * @param sourceUid The source (s) activity performing the state change
+ */
+ private BlockActivityStart blockCrossUidActivitySwitchFromBelow(ActivityRecord ar,
+ int sourceUid) {
+ if (ar.isUid(sourceUid)) {
+ return new BlockActivityStart(false, false);
+ }
+
+ // If mAllowCrossUidActivitySwitchFromBelow is set, honor it.
+ if (ar.mAllowCrossUidActivitySwitchFromBelow) {
+ return new BlockActivityStart(false, false);
+ }
+
+ // At this point, we would block if the feature is launched and both apps were V+
+ // Since we have a feature flag, we need to check that too
+ // TODO(b/258792202) Replace with CompatChanges and replace Pair with boolean once feature
+ // flag is removed
+ boolean restrictActivitySwitch =
+ ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(ar.getUid())
+ && ActivitySecurityModelFeatureFlags
+ .shouldRestrictActivitySwitch(sourceUid);
+ return new BackgroundActivityStartController
+ .BlockActivityStart(restrictActivitySwitch, true);
+ }
+
+ /** Only called when an activity launch may be blocked, which should happen very rarely */
+ private String getDebugInfoForActivitySecurity(@NonNull String action,
+ @Nullable ActivityRecord sourceRecord, @NonNull ActivityRecord targetRecord,
+ @Nullable Task targetTask, @Nullable ActivityRecord targetTopActivity,
+ int realCallingUid, @BalCode int balCode,
+ boolean blockActivityStartAndFeatureEnabled, boolean taskToFront) {
+ final String prefix = "[ASM] ";
+ Function<ActivityRecord, String> recordToString = (ar) -> {
+ if (ar == null) {
+ return null;
+ }
+ return (ar == sourceRecord ? " [source]=> "
+ : ar == targetTopActivity ? " [ top ]=> "
+ : ar == targetRecord ? " [target]=> "
+ : " => ")
+ + ar
+ + " :: visible=" + ar.isVisible()
+ + ", finishing=" + ar.isFinishing()
+ + ", alwaysOnTop=" + ar.isAlwaysOnTop()
+ + ", taskFragment=" + ar.getTaskFragment();
+ };
+
+ StringJoiner joiner = new StringJoiner("\n");
+ joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
+ joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
+ joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
+
+ boolean targetTaskMatchesSourceTask = targetTask != null
+ && sourceRecord != null && sourceRecord.getTask() == targetTask;
+
+ if (sourceRecord == null) {
+ joiner.add(prefix + "Source Package: " + targetRecord.launchedFromPackage);
+ String realCallingPackage = mService.mContext.getPackageManager().getNameForUid(
+ realCallingUid);
+ joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage);
+ } else {
+ joiner.add(prefix + "Source Record: " + recordToString.apply(sourceRecord));
+ if (targetTaskMatchesSourceTask) {
+ joiner.add(prefix + "Source/Target Task: " + sourceRecord.getTask());
+ joiner.add(prefix + "Source/Target Task Stack: ");
+ } else {
+ joiner.add(prefix + "Source Task: " + sourceRecord.getTask());
+ joiner.add(prefix + "Source Task Stack: ");
+ }
+ sourceRecord.getTask().forAllActivities((Consumer<ActivityRecord>)
+ ar -> joiner.add(prefix + recordToString.apply(ar)));
+ }
+
+ joiner.add(prefix + "Target Task Top: " + recordToString.apply(targetTopActivity));
+ if (!targetTaskMatchesSourceTask) {
+ joiner.add(prefix + "Target Task: " + targetTask);
+ if (targetTask != null) {
+ joiner.add(prefix + "Target Task Stack: ");
+ targetTask.forAllActivities((Consumer<ActivityRecord>)
+ ar -> joiner.add(prefix + recordToString.apply(ar)));
+ }
+ }
+
+ joiner.add(prefix + "Target Record: " + recordToString.apply(targetRecord));
+ joiner.add(prefix + "Intent: " + targetRecord.intent);
+ joiner.add(prefix + "TaskToFront: " + taskToFront);
+ joiner.add(prefix + "BalCode: " + balCodeToString(balCode));
+
+ joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------");
+ return joiner.toString();
+ }
+
static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background,
int callingUid, int realCallingUid, Intent intent, int pid, String msg) {
return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, intent,
@@ -653,4 +1143,18 @@ public class BackgroundActivityStartController {
realCallingUid);
}
}
+
+ static class BlockActivityStart {
+ // We should block if feature flag is enabled
+ private final boolean mBlockActivityStartIfFlagEnabled;
+ // Used for logging/toasts. Would we block if target sdk was V and feature was
+ // enabled?
+ private final boolean mWouldBlockActivityStartIgnoringFlag;
+
+ BlockActivityStart(boolean shouldBlockActivityStart,
+ boolean wouldBlockActivityStartIgnoringFlags) {
+ this.mBlockActivityStartIfFlagEnabled = shouldBlockActivityStart;
+ this.mWouldBlockActivityStartIgnoringFlag = wouldBlockActivityStartIgnoringFlags;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 26abe51c8c34..458786ffdbc0 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -18,4 +18,4 @@ rgl@google.com
yunfanc@google.com
per-file BackgroundActivityStartController.java = set noparent
-per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
+per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com \ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index ae87e38d8faf..e2bb115d5fbd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -174,7 +174,7 @@ public class ActivityStarterTests extends WindowTestsBase {
mController = mock(ActivityStartController.class);
BackgroundActivityStartController balController =
new BackgroundActivityStartController(mAtm, mSupervisor);
- doReturn(balController).when(mController).getBackgroundActivityLaunchController();
+ doReturn(balController).when(mAtm.mTaskSupervisor).getBackgroundActivityLaunchController();
mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
clearInvocations(mActivityMetricsLogger);
mAppOpsManager = mAtm.getAppOpsManager();