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

Commit 5f5c13ed authored by Lakshman Annadorai's avatar Lakshman Annadorai Committed by Felipe Leme
Browse files

Delay user unlocking until boot complete on HSUM.

- When user 0 and user 10+ are unlocked early in the boot process on
  devices that use headless system user mode (HSUM), vold creates and
  mounts emulated volumes for these users before boot completed. On
  boot complete, vold is reset by StorageManagerService, causing vold
  to unmount previously mounted emulated volumes. This leads to vold
  killing apps/services that use these volumes. On some occasions,
  these killings have caused the system to become unstable and crash,
  or some apps to crash. This creates bad user experience on HSUM.
- In order to avoid this issue, delay user unlocking until boot
  complete on HSUM. Thus, emulated volume creation + mount and vold
  reset will happen after boot complete.
- To mitigate the risk of this CL impacting non-automotive builds, it
  is explicitly checking for HSUM in a way that minimize the code
  changes; a more robust solution be provided on master
  (commit 3d3a13419fd34ae460fffd68de97c13bc47295d7will).

Test: atest --rerun-until-failure 10 AtsCarWatchdogHostTests
  on tm-qpr-dev build id 8877634.
  AtsCarWatchdogHostTests has high failure rate on this build. So,
  tested the fix on this build to ensure the fix works.
Test: atest UserControllerTest

Bug: 240112925
Bug: 241929666

Change-Id: I2c1f2db625177511eaeeba56fba9d500d16b92d7
parent f28ee205
Loading
Loading
Loading
Loading
+65 −0
Original line number Diff line number Diff line
@@ -106,6 +106,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FactoryResetter;
import com.android.server.FgThread;
@@ -367,6 +368,11 @@ class UserController implements Handler.Callback {
    @GuardedBy("mLock")
    private boolean mDelayUserDataLocking;

    /**
     * Users are only allowed to be unlocked after boot complete.
     */
    private volatile boolean mAllowUserUnlocking;

    /**
     * Keep track of last active users for mDelayUserDataLocking.
     * The latest stopped user is placed in front while the least recently stopped user in back.
@@ -426,6 +432,11 @@ class UserController implements Handler.Callback {
        mUserLru.add(UserHandle.USER_SYSTEM);
        mLockPatternUtils = mInjector.getLockPatternUtils();
        updateStartedUserArrayLU();

        // TODO(b/232452368): currently mAllowUserUnlocking is only used on devices with HSUM
        // (Headless System User Mode), but on master it will be used by all devices (and hence this
        // initial assignment should be removed).
        mAllowUserUnlocking = !UserManager.isHeadlessSystemUserMode();
    }

    void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
@@ -1742,6 +1753,22 @@ class UserController implements Handler.Callback {

    private boolean unlockUserCleared(final @UserIdInt int userId, byte[] secret,
            IProgressListener listener) {
        // Delay user unlocking for headless system user mode until the system boot
        // completes. When the system boot completes, the {@link #onBootCompleted()}
        // method unlocks all started users for headless system user mode. This is done
        // to prevent unlocking the users too early during the system boot up.
        // Otherwise, emulated volumes are mounted too early during the system
        // boot up. When vold is reset on boot complete, vold kills all apps/services
        // (that use these emulated volumes) before unmounting the volumes(b/241929666).
        // In the past, these killings have caused the system to become too unstable on
        // some occasions.
        // Any unlocks that get delayed by this will be done by onBootComplete() instead.
        if (!mAllowUserUnlocking) {
            Slogf.i(TAG, "Not unlocking user %d yet because boot hasn't completed", userId);
            notifyFinished(userId, listener);
            return false;
        }

        UserState uss;
        if (!StorageManager.isUserKeyUnlocked(userId)) {
            final UserInfo userInfo = getUserInfo(userId);
@@ -2331,7 +2358,44 @@ class UserController implements Handler.Callback {
        }
    }

    @VisibleForTesting
    void setAllowUserUnlocking(boolean allowed) {
        mAllowUserUnlocking = allowed;
        if (DEBUG_MU) {
            // TODO(b/245335748): use Slogf.d instead
            // Slogf.d(TAG, new Exception(), "setAllowUserUnlocking(%b)", allowed);
            android.util.Slog.d(TAG, "setAllowUserUnlocking():" + allowed, new Exception());
        }
    }

    /**
     * @deprecated TODO(b/232452368): this logic will be merged into sendBootCompleted
     */
    @Deprecated
    private void onBootCompletedOnHeadlessSystemUserModeDevices() {
        setAllowUserUnlocking(true);

        // Get a copy of mStartedUsers to use outside of lock.
        SparseArray<UserState> startedUsers;
        synchronized (mLock) {
            startedUsers = mStartedUsers.clone();
        }
        // USER_SYSTEM must be processed first.  It will be first in the array, as its ID is lowest.
        Preconditions.checkArgument(startedUsers.keyAt(0) == UserHandle.USER_SYSTEM);
        for (int i = 0; i < startedUsers.size(); i++) {
            UserState uss = startedUsers.valueAt(i);
            int userId = uss.mHandle.getIdentifier();
            Slogf.i(TAG, "Attempting to unlock user %d on boot complete", userId);
            maybeUnlockUser(userId);
        }
    }

    void sendBootCompleted(IIntentReceiver resultTo) {
        if (UserManager.isHeadlessSystemUserMode()) {
            // Unlocking users is delayed until boot complete for headless system user mode.
            onBootCompletedOnHeadlessSystemUserModeDevices();
        }

        // Get a copy of mStartedUsers to use outside of lock
        SparseArray<UserState> startedUsers;
        synchronized (mLock) {
@@ -2773,6 +2837,7 @@ class UserController implements Handler.Callback {
            pw.println("  mTargetUserId:" + mTargetUserId);
            pw.println("  mLastActiveUsers:" + mLastActiveUsers);
            pw.println("  mDelayUserDataLocking:" + mDelayUserDataLocking);
            pw.println("  mAllowUserUnlocking:" + mAllowUserUnlocking);
            pw.println("  shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
            pw.println("  mStopUserOnSwitch:" + mStopUserOnSwitch);
            pw.println("  mMaxRunningUsers:" + mMaxRunningUsers);
+15 −0
Original line number Diff line number Diff line
@@ -179,6 +179,11 @@ public class UserControllerTest {
            doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt());
            // All UserController params are set to default.
            mUserController = new UserController(mInjector);

            // TODO(b/232452368): need to explicitly call setAllowUserUnlocking(), otherwise most
            // tests would fail. But we might need to disable it for the onBootComplete() test (i.e,
            // to make sure the users are unlocked at the right time)
            mUserController.setAllowUserUnlocking(true);
            setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
            setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated=*/ true, null);
        });
@@ -599,6 +604,16 @@ public class UserControllerTest {
                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
    }

    @Test
    public void testUserNotUnlockedBeforeAllowed() throws Exception {
        mUserController.setAllowUserUnlocking(false);

        mUserController.startUser(TEST_USER_ID, /* foreground= */ false);

        verify(mInjector.mStorageManagerMock, never())
                .unlockUserKey(eq(TEST_USER_ID), anyInt(), any());
    }

    @Test
    public void testStartProfile_fullUserFails() {
        setUpUser(TEST_USER_ID1, 0);