diff options
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(); |