diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java index 64af9dd755ed9..47e78f0c6e9bc 100644 --- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java +++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java @@ -72,7 +72,7 @@ class ActivitySecurityModelFeatureFlags { } @GuardedBy("ActivityTaskManagerService.mGlobalLock") - static boolean shouldBlockActivityStart(int uid) { + static boolean shouldRestrictActivitySwitch(int uid) { return flagEnabledForUid(sAsmRestrictionsEnabled, uid); } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index d88f719907e32..d6d3dc7b6ee28 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1972,7 +1972,7 @@ class ActivityStarter { ); boolean shouldBlockActivityStart = - ActivitySecurityModelFeatureFlags.shouldBlockActivityStart(mCallingUid); + ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(mCallingUid); if (ActivitySecurityModelFeatureFlags.shouldShowToast(mCallingUid)) { UiThread.getHandler().post(() -> Toast.makeText(mService.mContext, @@ -2080,6 +2080,7 @@ class ActivityStarter { reusedTask != null ? reusedTask.getTopNonFinishingActivity() : null, intentGrants); if (mAddingToTask) { + clearTopIfNeeded(targetTask, mCallingUid, mStartActivity.getUid(), mLaunchFlags); return START_SUCCESS; } @@ -2111,6 +2112,55 @@ class ActivityStarter { return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP; } + /** + * If the top activity uid does not match the 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. + */ + private void clearTopIfNeeded(@NonNull Task targetTask, int callingUid, int startingUid, + int launchFlags) { + if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK) { + // Launch is from the same task, so must be a top or privileged UID + return; + } + + ActivityRecord targetTaskTop = targetTask.getTopNonFinishingActivity(); + if (targetTaskTop != null && targetTaskTop.getUid() != startingUid) { + boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags + .shouldRestrictActivitySwitch(callingUid); + int[] finishCount = new int[0]; + if (shouldBlockActivityStart) { + ActivityRecord activity = targetTask.getActivity( + ar -> !ar.finishing && ar.isUid(startingUid)); + + if (activity == null) { + // mStartActivity is not in task, so clear everything + activity = mStartActivity; + } + + finishCount = new int[1]; + if (activity != null) { + targetTask.performClearTop(activity, launchFlags, finishCount); + } + + if (finishCount[0] > 0) { + Slog.w(TAG, "Clearing 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_SHORT).show()); + } + } + } + /** * 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/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 8149e1c0a2a31..0f1f51f375e9e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -137,6 +137,7 @@ 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,6 +148,7 @@ 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; @@ -1628,16 +1630,16 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Prevent recursion. return; } + boolean passesAsmChecks = true; // 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. // TODO(b/263368846) Move to live with the rest of the ASM logic. if (callingUid != SYSTEM_UID) { - boolean passesAsmChecks = doesTopActivityMatchingUidExistForAsm(task, callingUid, + passesAsmChecks = doesTopActivityMatchingUidExistForAsm(task, callingUid, null); if (!passesAsmChecks) { ActivityRecord topActivity = task.getActivity(ar -> !ar.isState(FINISHING) && !ar.isAlwaysOnTop()); - Slog.i(TAG, "Finishing task from background. t: " + task); FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED, /* caller_uid */ callingUid, @@ -1676,6 +1678,28 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { if (task.isPersistable) { mService.notifyTaskPersisterLocked(null, true); } + if (!passesAsmChecks) { + boolean shouldRestrictActivitySwitch = + ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(callingUid); + + if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) { + UiThread.getHandler().post(() -> Toast.makeText(mService.mContext, + (shouldRestrictActivitySwitch + ? "Returning home due to " + : "Would return home due to ") + + ActivitySecurityModelFeatureFlags.DOC_LINK, + Toast.LENGTH_SHORT).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 (shouldRestrictActivitySwitch) { + Slog.w(TAG, "Return to home as source uid: " + callingUid + + "is not on top of task t: " + task); + task.getTaskDisplayArea().moveHomeActivityToTop("taskRemoved"); + } + } } finally { task.mInRemoveTask = false; }