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

Commit 284075a5 authored by Veena Arvind's avatar Veena Arvind Committed by Gerrit Code Review
Browse files

Merge "Don't unlock other users if user 0 or parent user does not contain escrow data." into main

parents 67366767 9cb7c7af
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -581,7 +581,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks,
                LockSettingsStorage storage) {
            return new RebootEscrowManager(mContext, callbacks, storage,
                    getHandler(getServiceThread()));
                    getHandler(getServiceThread()), getUserManagerInternal());
        }

        public int binderGetCallingUid() {
+54 −7
Original line number Diff line number Diff line
@@ -51,16 +51,20 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.widget.RebootEscrowListener;
import com.android.server.pm.UserManagerInternal;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;

import javax.crypto.SecretKey;

@@ -138,6 +142,7 @@ class RebootEscrowManager {
            ERROR_KEYSTORE_FAILURE,
            ERROR_NO_NETWORK,
            ERROR_TIMEOUT_EXHAUSTED,
            ERROR_NO_REBOOT_ESCROW_DATA,
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface RebootEscrowErrorCode {
@@ -153,6 +158,7 @@ class RebootEscrowManager {
    static final int ERROR_KEYSTORE_FAILURE = 7;
    static final int ERROR_NO_NETWORK = 8;
    static final int ERROR_TIMEOUT_EXHAUSTED = 9;
    static final int ERROR_NO_REBOOT_ESCROW_DATA = 10;

    private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE;

@@ -222,11 +228,16 @@ class RebootEscrowManager {
        private final RebootEscrowKeyStoreManager mKeyStoreManager;
        private final LockSettingsStorage mStorage;
        private RebootEscrowProviderInterface mRebootEscrowProvider;
        private final UserManagerInternal mUserManagerInternal;

        Injector(Context context, LockSettingsStorage storage) {
        Injector(
                Context context,
                LockSettingsStorage storage,
                UserManagerInternal userManagerInternal) {
            mContext = context;
            mStorage = storage;
            mKeyStoreManager = new RebootEscrowKeyStoreManager();
            mUserManagerInternal = userManagerInternal;
        }

        private RebootEscrowProviderInterface createRebootEscrowProvider() {
@@ -326,6 +337,10 @@ class RebootEscrowManager {
            return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
        }

        public UserManagerInternal getUserManagerInternal() {
            return mUserManagerInternal;
        }

        public RebootEscrowKeyStoreManager getKeyStoreManager() {
            return mKeyStoreManager;
        }
@@ -402,8 +417,8 @@ class RebootEscrowManager {
    }

    RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage,
            Handler handler) {
        this(new Injector(context, storage), callbacks, storage, handler);
                        Handler handler, UserManagerInternal userManagerInternal) {
        this(new Injector(context, storage, userManagerInternal), callbacks, storage, handler);
    }

    @VisibleForTesting
@@ -451,18 +466,50 @@ class RebootEscrowManager {
        onEscrowRestoreComplete(false, attemptCount, retryHandler);
    }

    void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
        List<UserInfo> users = mUserManager.getUsers();
    private List<UserInfo> getUsersToUnlock(List<UserInfo> users) {
        // System user must be unlocked to unlock any other user
        if (mCallbacks.isUserSecure(USER_SYSTEM) && !mStorage.hasRebootEscrow(USER_SYSTEM)) {
            Slog.i(TAG, "No reboot escrow data found for system user");
            return Collections.emptyList();
        }

        Set<Integer> noEscrowDataUsers = new HashSet<>();
        for (UserInfo user : users) {
            if (mCallbacks.isUserSecure(user.id)
                    && !mStorage.hasRebootEscrow(user.id)) {
                Slog.d(TAG, "No reboot escrow data found for user " + user);
                noEscrowDataUsers.add(user.id);
            }
        }

        List<UserInfo> rebootEscrowUsers = new ArrayList<>();
        for (UserInfo user : users) {
            if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) {
            // No lskf, no need to unlock.
            if (!mCallbacks.isUserSecure(user.id)) {
                continue;
            }
            // Don't unlock if user or user's parent does not have reboot data
            int userId = user.id;
            if (noEscrowDataUsers.contains(userId)
                    || noEscrowDataUsers.contains(
                            mInjector.getUserManagerInternal().getProfileParentId(userId))) {
                continue;
            }
            rebootEscrowUsers.add(user);
        }
        return rebootEscrowUsers;
    }

    void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
        List<UserInfo> users = mUserManager.getUsers();
        List<UserInfo> rebootEscrowUsers = getUsersToUnlock(users);

        if (rebootEscrowUsers.isEmpty()) {
            Slog.i(TAG, "No reboot escrow data found for users,"
                    + " skipping loading escrow data");
            setLoadEscrowDataErrorCode(ERROR_NO_REBOOT_ESCROW_DATA, retryHandler);
            reportMetricOnRestoreComplete(
                    /* success= */ false, /* attemptCount= */ 1, retryHandler);
            clearMetricsStorage();
            return;
        }
+224 −16
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.locksettings;
import static android.content.pm.UserInfo.FLAG_FULL;
import static android.content.pm.UserInfo.FLAG_PRIMARY;
import static android.content.pm.UserInfo.FLAG_PROFILE;
import static android.content.pm.UserInfo.NO_PROFILE_GROUP_ID;
import static android.os.UserHandle.USER_SYSTEM;

import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_ESCROW_NOT_READY;
@@ -32,6 +33,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyByte;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -65,6 +67,7 @@ import androidx.test.runner.AndroidJUnit4;

import com.android.internal.widget.RebootEscrowListener;
import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection;
import com.android.server.pm.UserManagerInternal;

import org.junit.Before;
import org.junit.Test;
@@ -107,6 +110,7 @@ public class RebootEscrowManagerTests {

    private Context mContext;
    private UserManager mUserManager;
    private UserManagerInternal mUserManagerInternal;
    private RebootEscrowManager.Callbacks mCallbacks;
    private IRebootEscrow mRebootEscrow;
    private ResumeOnRebootServiceConnection mServiceConnection;
@@ -126,13 +130,15 @@ public class RebootEscrowManagerTests {
        long getCurrentTimeMillis();

        void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
                int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete);
                          int escrowDurationInSeconds, int vbmetaDigestStatus,
                          int durationSinceBootComplete);
    }

    static class MockInjector extends RebootEscrowManager.Injector {
        private final IRebootEscrow mRebootEscrow;
        private final RebootEscrowProviderInterface mDefaultRebootEscrowProvider;
        private final UserManager mUserManager;
        private final UserManagerInternal mUserManagerInternal;
        private final MockableRebootEscrowInjected mInjected;
        private final RebootEscrowKeyStoreManager mKeyStoreManager;
        private boolean mServerBased;
@@ -141,12 +147,16 @@ public class RebootEscrowManagerTests {
        private Consumer<ConnectivityManager.NetworkCallback> mNetworkConsumer;
        private boolean mWaitForInternet;

        MockInjector(Context context, UserManager userManager,
        MockInjector(
                Context context,
                UserManager userManager,
                UserManagerInternal userManagerInternal,
                IRebootEscrow rebootEscrow,
                RebootEscrowKeyStoreManager keyStoreManager,
                LockSettingsStorageTestable storage,
                MockableRebootEscrowInjected injected) {
            super(context, storage);
            // TODO: change this
            super(context, storage, userManagerInternal);
            mRebootEscrow = rebootEscrow;
            mServerBased = false;
            mWaitForInternet = false;
@@ -159,16 +169,20 @@ public class RebootEscrowManagerTests {
                    };
            mDefaultRebootEscrowProvider = new RebootEscrowProviderHalImpl(halInjector);
            mUserManager = userManager;
            mUserManagerInternal = userManagerInternal;
            mKeyStoreManager = keyStoreManager;
            mInjected = injected;
        }

        MockInjector(Context context, UserManager userManager,
        MockInjector(
                Context context,
                UserManager userManager,
                UserManagerInternal userManagerInternal,
                ResumeOnRebootServiceConnection serviceConnection,
                RebootEscrowKeyStoreManager keyStoreManager,
                LockSettingsStorageTestable storage,
                MockableRebootEscrowInjected injected) {
            super(context, storage);
            super(context, storage, userManagerInternal);
            mRebootEscrow = null;
            mServerBased = true;
            mWaitForInternet = false;
@@ -187,6 +201,7 @@ public class RebootEscrowManagerTests {
            mDefaultRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(
                    storage, injector);
            mUserManager = userManager;
            mUserManagerInternal = userManagerInternal;
            mKeyStoreManager = keyStoreManager;
            mInjected = injected;
        }
@@ -201,6 +216,11 @@ public class RebootEscrowManagerTests {
            return mUserManager;
        }

        @Override
        public UserManagerInternal getUserManagerInternal() {
            return mUserManagerInternal;
        }

        @Override
        public boolean serverBasedResumeOnReboot() {
            return mServerBased;
@@ -301,6 +321,7 @@ public class RebootEscrowManagerTests {
    public void setUp_baseServices() throws Exception {
        mContext = new ContextWrapper(InstrumentationRegistry.getContext());
        mUserManager = mock(UserManager.class);
        mUserManagerInternal = mock(UserManagerInternal.class);
        mCallbacks = mock(RebootEscrowManager.Callbacks.class);
        mRebootEscrow = mock(IRebootEscrow.class);
        mServiceConnection = mock(ResumeOnRebootServiceConnection.class);
@@ -314,28 +335,43 @@ public class RebootEscrowManagerTests {
                new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));

        ArrayList<UserInfo> users = new ArrayList<>();
        users.add(new UserInfo(PRIMARY_USER_ID, "primary", FLAG_PRIMARY));
        users.add(new UserInfo(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE));
        users.add(new UserInfo(NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL));
        users.add(new UserInfo(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL));
        users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, PRIMARY_USER_ID));
        users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, PRIMARY_USER_ID));
        users.add(
                createUser(
                        NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL, NO_PROFILE_GROUP_ID));
        users.add(createUser(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, NO_PROFILE_GROUP_ID));
        when(mUserManager.getUsers()).thenReturn(users);
        when(mCallbacks.isUserSecure(PRIMARY_USER_ID)).thenReturn(true);
        when(mCallbacks.isUserSecure(WORK_PROFILE_USER_ID)).thenReturn(true);
        when(mCallbacks.isUserSecure(NONSECURE_SECONDARY_USER_ID)).thenReturn(false);
        when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
        mInjected = mock(MockableRebootEscrowInjected.class);
        mMockInjector = new MockInjector(mContext, mUserManager, mRebootEscrow,
                mKeyStoreManager, mStorage, mInjected);
        mMockInjector =
                new MockInjector(
                        mContext,
                        mUserManager,
                        mUserManagerInternal,
                        mRebootEscrow,
                        mKeyStoreManager,
                        mStorage,
                        mInjected);
        HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
        thread.start();
        mHandler = new Handler(thread.getLooper());
        mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);

    }

    private void setServerBasedRebootEscrowProvider() throws Exception {
        mMockInjector = new MockInjector(mContext, mUserManager, mServiceConnection,
                mKeyStoreManager, mStorage, mInjected);
        mMockInjector =
                new MockInjector(
                        mContext,
                        mUserManager,
                        mUserManagerInternal,
                        mServiceConnection,
                        mKeyStoreManager,
                        mStorage,
                        mInjected);
        mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
    }

@@ -352,6 +388,12 @@ public class RebootEscrowManagerTests {
        waitForHandler();
    }

    private UserInfo createUser(int id, String name, int flag, int profileGroupId) {
        UserInfo user = new UserInfo(id, name, flag);
        when(mUserManagerInternal.getProfileParentId(eq(id))).thenReturn(profileGroupId);
        return user;
    }

    @Test
    public void prepareRebootEscrow_Success() throws Exception {
        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -558,6 +600,172 @@ public class RebootEscrowManagerTests {
                -1, USER_SYSTEM), -1);
    }

    @Test
    public void loadRebootEscrowDataIfAvailable_noDataPrimaryUser_Failure() throws Exception {
        setServerBasedRebootEscrowProvider();
        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
        mService.setRebootEscrowListener(mockListener);
        mService.prepareRebootEscrow();

        clearInvocations(mServiceConnection);

        // escrow secondary user, don't escrow primary user
        callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID);
        verify(mockListener).onPreparedForReboot(eq(true));
        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());

        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
                .thenAnswer(invocation -> invocation.getArgument(0));
        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());

        assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
        assertFalse(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
        assertTrue(mStorage.hasRebootEscrowServerBlob());

        // pretend reboot happens here
        when(mInjected.getBootCount()).thenReturn(1);
        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
        doNothing()
                .when(mInjected)
                .reportMetric(
                        metricsSuccessCaptor.capture(),
                        metricsErrorCodeCaptor.capture(),
                        eq(2) /* Server based */,
                        eq(1) /* attempt count */,
                        anyInt(),
                        eq(0) /* vbmeta status */,
                        anyInt());
        mService.loadRebootEscrowDataIfAvailable(null);
        verify(mServiceConnection, never()).unwrap(any(), anyLong());
        verify(mCallbacks, never()).onRebootEscrowRestored(anyByte(), any(), anyInt());
        assertFalse(metricsSuccessCaptor.getValue());
        assertEquals(
                Integer.valueOf(RebootEscrowManager.ERROR_NO_REBOOT_ESCROW_DATA),
                metricsErrorCodeCaptor.getValue());
    }

    @Test
    public void loadRebootEscrowDataIfAvailable_noDataSecondaryUser_Success() throws Exception {
        setServerBasedRebootEscrowProvider();
        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
        mService.setRebootEscrowListener(mockListener);
        mService.prepareRebootEscrow();

        clearInvocations(mServiceConnection);

        // Setup work profile with secondary user as parent.
        ArrayList<UserInfo> users = new ArrayList<>();
        users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, NO_PROFILE_GROUP_ID));
        users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, SECURE_SECONDARY_USER_ID));
        users.add(
                createUser(
                        SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, SECURE_SECONDARY_USER_ID));
        when(mUserManager.getUsers()).thenReturn(users);

        // escrow primary user and work profile, don't escrow secondary user
        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
        verify(mockListener).onPreparedForReboot(eq(true));
        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
        callToRebootEscrowIfNeededAndWait(WORK_PROFILE_USER_ID);
        verify(mockListener).onPreparedForReboot(eq(true));
        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());

        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
                .thenAnswer(invocation -> invocation.getArgument(0));
        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());

        assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
        assertFalse(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
        assertTrue(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID));
        assertTrue(mStorage.hasRebootEscrowServerBlob());

        // pretend reboot happens here
        when(mInjected.getBootCount()).thenReturn(1);
        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
        doNothing()
                .when(mInjected)
                .reportMetric(
                        metricsSuccessCaptor.capture(),
                        eq(0) /* error code */,
                        eq(2) /* Server based */,
                        eq(1) /* attempt count */,
                        anyInt(),
                        eq(0) /* vbmeta status */,
                        anyInt());
        when(mServiceConnection.unwrap(any(), anyLong()))
                .thenAnswer(invocation -> invocation.getArgument(0));

        mService.loadRebootEscrowDataIfAvailable(null);

        verify(mServiceConnection).unwrap(any(), anyLong());
        verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID));
        verify(mCallbacks, never())
                .onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID));
        verify(mCallbacks, never())
                .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID));
        verify(mCallbacks, never())
                .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID));
        assertTrue(metricsSuccessCaptor.getValue());
    }

    @Test
    public void loadRebootEscrowDataIfAvailable_noDataWorkProfile_Success() throws Exception {
        setServerBasedRebootEscrowProvider();
        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
        mService.setRebootEscrowListener(mockListener);
        mService.prepareRebootEscrow();

        clearInvocations(mServiceConnection);

        // escrow primary user and secondary user, don't escrow work profile
        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
        verify(mockListener).onPreparedForReboot(eq(true));
        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
        callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID);
        verify(mockListener).onPreparedForReboot(eq(true));
        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());

        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
                .thenAnswer(invocation -> invocation.getArgument(0));
        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());

        assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
        assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
        assertFalse(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID));
        assertTrue(mStorage.hasRebootEscrowServerBlob());

        // pretend reboot happens here
        when(mInjected.getBootCount()).thenReturn(1);
        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
        doNothing()
                .when(mInjected)
                .reportMetric(
                        metricsSuccessCaptor.capture(),
                        eq(0) /* error code */,
                        eq(2) /* Server based */,
                        eq(1) /* attempt count */,
                        anyInt(),
                        eq(0) /* vbmeta status */,
                        anyInt());
        when(mServiceConnection.unwrap(any(), anyLong()))
                .thenAnswer(invocation -> invocation.getArgument(0));

        mService.loadRebootEscrowDataIfAvailable(null);

        verify(mServiceConnection).unwrap(any(), anyLong());
        verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID));
        verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID));
        verify(mCallbacks, never())
                .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID));
        verify(mCallbacks, never())
                .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID));
        assertTrue(metricsSuccessCaptor.getValue());
    }

    @Test
    public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception {
        setServerBasedRebootEscrowProvider();