Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bcb8162d authored by Hani Kazmi's avatar Hani Kazmi
Browse files

Move ASM code to shared class

This change:
1. Moves ASM and BAL code into the same class
2. Updates ASM's minimum tSDK to V
3. Inverts some boolean checks to make the flow easier to follow

Test: atest ActivitySecurityModelTest
Test: ActivitySecurityModelEmbeddingTest BackgroundActivityLaunchTest
Bug: 263368846
Change-Id: I945e8d53f0977479a12473a824956d5acdfe1a14
parent 37324026
Loading
Loading
Loading
Loading
+2 −47
Original line number Diff line number Diff line
@@ -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();
    }
+2 −2
Original line number Diff line number Diff line
@@ -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) {
+0 −7
Original line number Diff line number Diff line
@@ -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;
    }
}
+8 −300
Original line number Diff line number Diff line
@@ -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,233 +1957,15 @@ class ActivityStarter {
            }
        }

        if (!checkActivitySecurityModel(r, newTask, targetTask)) {
        if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart(
                mSourceRecord, r, newTask, targetTask, mLaunchFlags, mBalCode, mCallingUid,
                mRealCallingUid)) {
            return START_ABORTED;
        }

        return START_SUCCESS;
    }

    /**
     * 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.
     *
@@ -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;
        }

@@ -2317,65 +2084,6 @@ class ActivityStarter {
        return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
    }

    /**
     * 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.
+1 −1
Original line number Diff line number Diff line
@@ -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,
Loading