From 6066898f4240056a39420e12168f6d08ef5c9c03 Mon Sep 17 00:00:00 2001 From: Tianjie Date: Mon, 22 Mar 2021 22:17:59 -0700 Subject: [PATCH] Improve the error handling for armRebootEscrow The change including the following items 1. Add error code for armRebootEscrow 2. Fail the armRebootEscrow for mismatching ror providers. 3. clear the escrow data for fatal armRebootEscrow 4. clear the RoR preparation state in recovery system for fatal armRebootEscrow Bug: 183140900 Test: atest FrameworksServicesTests:RebootEscrowManagerTests; atest FrameworksServicesTests:RecoverySystemServiceTest; Change provider before reboot, reboot-and-apply failed; Change-Id: I266ea6f2e1c77994d5cc8c2713106c527c4e0722 --- core/java/android/os/RecoverySystem.java | 5 +- .../internal/widget/LockSettingsInternal.java | 30 +++++- .../locksettings/LockSettingsService.java | 2 +- .../locksettings/RebootEscrowManager.java | 79 ++++++++++------ .../recoverysystem/RecoverySystemService.java | 92 +++++++++++++++---- .../LockSettingsStorageTestable.java | 5 + .../RebootEscrowManagerTests.java | 69 ++++++++++---- .../RecoverySystemServiceTest.java | 30 +++++- 8 files changed, 244 insertions(+), 68 deletions(-) diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index a42448c261d48..b474d7c959968 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -169,8 +169,9 @@ public class RecoverySystem { public @interface ResumeOnRebootRebootErrorCode {} /** - * The preparation of resume on reboot succeeds. Don't expose it because a successful reboot - * should just reboot the device. + * The preparation of resume on reboot succeeds. + * + *

Don't expose it because a successful reboot should just reboot the device. * @hide */ public static final int RESUME_ON_REBOOT_REBOOT_ERROR_NONE = 0; diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java index f5df3abad658f..940979d7cd1f8 100644 --- a/core/java/com/android/internal/widget/LockSettingsInternal.java +++ b/core/java/com/android/internal/widget/LockSettingsInternal.java @@ -16,15 +16,41 @@ package com.android.internal.widget; +import android.annotation.IntDef; import android.annotation.Nullable; import android.app.admin.PasswordMetrics; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * LockSettingsService local system service interface. * * @hide Only for use within the system server. */ public abstract class LockSettingsInternal { + /** ErrorCode for armRebootEscrow failures. **/ + @IntDef(prefix = {"ARM_REBOOT_ERROR_"}, value = { + ARM_REBOOT_ERROR_NONE, + ARM_REBOOT_ERROR_UNSPECIFIED, + ARM_REBOOT_ERROR_ESCROW_NOT_READY, + ARM_REBOOT_ERROR_NO_PROVIDER, + ARM_REBOOT_ERROR_PROVIDER_MISMATCH, + ARM_REBOOT_ERROR_NO_ESCROW_KEY, + ARM_REBOOT_ERROR_KEYSTORE_FAILURE, + ARM_REBOOT_ERROR_STORE_ESCROW_KEY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ArmRebootEscrowErrorCode {} + + public static final int ARM_REBOOT_ERROR_NONE = 0; + public static final int ARM_REBOOT_ERROR_UNSPECIFIED = 1; + public static final int ARM_REBOOT_ERROR_ESCROW_NOT_READY = 2; + public static final int ARM_REBOOT_ERROR_NO_PROVIDER = 3; + public static final int ARM_REBOOT_ERROR_PROVIDER_MISMATCH = 4; + public static final int ARM_REBOOT_ERROR_NO_ESCROW_KEY = 5; + public static final int ARM_REBOOT_ERROR_KEYSTORE_FAILURE = 6; + public static final int ARM_REBOOT_ERROR_STORE_ESCROW_KEY = 7; + // TODO(b/183140900) split store escrow key errors into detailed ones. /** * Create an escrow token for the current user, which can later be used to unlock FBE @@ -104,9 +130,9 @@ public abstract class LockSettingsInternal { * Should be called immediately before rebooting for an update. This depends on {@link * #prepareRebootEscrow()} having been called and the escrow completing. * - * @return true if the arming worked + * @return ARM_ERROR_NONE if the arming worked */ - public abstract boolean armRebootEscrow(); + public abstract @ArmRebootEscrowErrorCode int armRebootEscrow(); /** diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index f96fa0969de9c..14d9d857d8746 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -3488,7 +3488,7 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public boolean armRebootEscrow() { + public @ArmRebootEscrowErrorCode int armRebootEscrow() { return mRebootEscrowManager.armRebootEscrowIfNeeded(); } diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 76ecc1acc7ac5..c01523a4bc404 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -18,6 +18,15 @@ package com.android.server.locksettings; import static android.os.UserHandle.USER_SYSTEM; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_ESCROW_NOT_READY; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_KEYSTORE_FAILURE; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NONE; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NO_ESCROW_KEY; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NO_PROVIDER; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_PROVIDER_MISMATCH; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_STORE_ESCROW_KEY; +import static com.android.internal.widget.LockSettingsInternal.ArmRebootEscrowErrorCode; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.UserIdInt; @@ -577,16 +586,14 @@ class RebootEscrowManager { mRebootEscrowWanted = false; setRebootEscrowReady(false); - RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider(); if (rebootEscrowProvider == null) { - Slog.w(TAG, - "Had reboot escrow data for users, but RebootEscrowProvider is unavailable"); - return; + Slog.w(TAG, "RebootEscrowProvider is unavailable for clear request"); + } else { + rebootEscrowProvider.clearRebootEscrowKey(); } clearMetricsStorage(); - rebootEscrowProvider.clearRebootEscrowKey(); List users = mUserManager.getUsers(); for (UserInfo user : users) { @@ -596,20 +603,30 @@ class RebootEscrowManager { mEventLog.addEntry(RebootEscrowEvent.CLEARED_LSKF_REQUEST); } - boolean armRebootEscrowIfNeeded() { + @ArmRebootEscrowErrorCode int armRebootEscrowIfNeeded() { if (!mRebootEscrowReady) { - return false; + return ARM_REBOOT_ERROR_ESCROW_NOT_READY; } RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider(); if (rebootEscrowProvider == null) { Slog.w(TAG, "Had reboot escrow data for users, but RebootEscrowProvider is unavailable"); - return false; + clearRebootEscrowIfNeeded(); + return ARM_REBOOT_ERROR_NO_PROVIDER; } + int expectedProviderType = mInjector.serverBasedResumeOnReboot() + ? RebootEscrowProviderInterface.TYPE_SERVER_BASED + : RebootEscrowProviderInterface.TYPE_HAL; int actualProviderType = rebootEscrowProvider.getType(); - // TODO(b/183140900) Fail the reboot if provider type mismatches. + if (expectedProviderType != actualProviderType) { + Slog.w(TAG, "Expect reboot escrow provider " + expectedProviderType + + ", but the RoR is prepared with " + actualProviderType + + ". Please prepare the RoR again."); + clearRebootEscrowIfNeeded(); + return ARM_REBOOT_ERROR_PROVIDER_MISMATCH; + } RebootEscrowKey escrowKey; synchronized (mKeyGenerationLock) { @@ -618,30 +635,38 @@ class RebootEscrowManager { if (escrowKey == null) { Slog.e(TAG, "Escrow key is null, but escrow was marked as ready"); - return false; + clearRebootEscrowIfNeeded(); + return ARM_REBOOT_ERROR_NO_ESCROW_KEY; } // We will use the same key from keystore to encrypt the escrow key and escrow data blob. SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey(); if (kk == null) { Slog.e(TAG, "Failed to get encryption key from keystore."); - return false; - } - boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk); - if (armedRebootEscrow) { - mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM); - mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, mInjector.getCurrentTimeMillis(), - USER_SYSTEM); - // Store the vbmeta digest of both slots. - mStorage.setString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, mInjector.getVbmetaDigest(false), - USER_SYSTEM); - mStorage.setString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, - mInjector.getVbmetaDigest(true), USER_SYSTEM); - mStorage.setInt(REBOOT_ESCROW_KEY_PROVIDER, actualProviderType, USER_SYSTEM); - mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS); + clearRebootEscrowIfNeeded(); + return ARM_REBOOT_ERROR_KEYSTORE_FAILURE; } - return armedRebootEscrow; + // TODO(b/183140900) design detailed errors for store escrow key errors. + // We don't clear rebootEscrow here, because some errors may be recoverable, e.g. network + // unavailable for server based provider. + boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk); + if (!armedRebootEscrow) { + return ARM_REBOOT_ERROR_STORE_ESCROW_KEY; + } + + mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM); + mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, mInjector.getCurrentTimeMillis(), + USER_SYSTEM); + // Store the vbmeta digest of both slots. + mStorage.setString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, mInjector.getVbmetaDigest(false), + USER_SYSTEM); + mStorage.setString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, + mInjector.getVbmetaDigest(true), USER_SYSTEM); + mStorage.setInt(REBOOT_ESCROW_KEY_PROVIDER, actualProviderType, USER_SYSTEM); + mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS); + + return ARM_REBOOT_ERROR_NONE; } private void setRebootEscrowReady(boolean ready) { @@ -663,10 +688,6 @@ class RebootEscrowManager { } boolean clearRebootEscrow() { - if (mInjector.getRebootEscrowProvider() == null) { - return false; - } - clearRebootEscrowIfNeeded(); return true; } diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java index 6ea030f56d4d3..81a51e2906641 100644 --- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java +++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java @@ -25,6 +25,8 @@ import static android.os.RecoverySystem.RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIE import static android.os.RecoverySystem.ResumeOnRebootRebootErrorCode; import static android.os.UserHandle.USER_SYSTEM; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NONE; + import android.annotation.IntDef; import android.content.Context; import android.content.IntentSender; @@ -47,6 +49,7 @@ import android.os.SystemProperties; import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.FastImmutableArraySet; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -143,7 +146,7 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo */ @IntDef({ ROR_NEED_PREPARATION, ROR_SKIP_PREPARATION_AND_NOTIFY, - ROR_SKIP_PREPARATION_NOT_NOTIFY}) + ROR_SKIP_PREPARATION_NOT_NOTIFY }) private @interface ResumeOnRebootActionsOnRequest {} /** @@ -151,9 +154,42 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo */ @IntDef({ ROR_NOT_REQUESTED, ROR_REQUESTED_NEED_CLEAR, - ROR_REQUESTED_SKIP_CLEAR}) + ROR_REQUESTED_SKIP_CLEAR }) private @interface ResumeOnRebootActionsOnClear {} + /** + * Fatal arm escrow errors from lock settings that means the RoR is in a bad state. So clients + * need to prepare RoR again. + */ + static final FastImmutableArraySet FATAL_ARM_ESCROW_ERRORS = + new FastImmutableArraySet<>(new Integer[]{ + LockSettingsInternal.ARM_REBOOT_ERROR_ESCROW_NOT_READY, + LockSettingsInternal.ARM_REBOOT_ERROR_NO_PROVIDER, + LockSettingsInternal.ARM_REBOOT_ERROR_PROVIDER_MISMATCH, + LockSettingsInternal.ARM_REBOOT_ERROR_NO_ESCROW_KEY, + LockSettingsInternal.ARM_REBOOT_ERROR_KEYSTORE_FAILURE, + }); + + /** + * The error details for ArmRebootEscrow. It contains error codes from RecoverySystemService + * and LockSettingsService. + */ + static class RebootPreparationError { + final @ResumeOnRebootRebootErrorCode int mRebootErrorCode; + final int mProviderErrorCode; // The supplemental error code from lock settings + + RebootPreparationError(int rebootErrorCode, int providerErrorCode) { + mRebootErrorCode = rebootErrorCode; + mProviderErrorCode = providerErrorCode; + } + + int getErrorCodeForMetrics() { + // The ResumeOnRebootRebootErrorCode are aligned with 1000; so it's safe to add them + // for metrics purpose. + return mRebootErrorCode + mProviderErrorCode; + } + } + /** * Manages shared preference, i.e. the storage used for metrics reporting. */ @@ -709,34 +745,40 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return true; } - private @ResumeOnRebootRebootErrorCode int armRebootEscrow(String packageName, + private RebootPreparationError armRebootEscrow(String packageName, boolean slotSwitch) { if (packageName == null) { Slog.w(TAG, "Missing packageName when rebooting with lskf."); - return RESUME_ON_REBOOT_REBOOT_ERROR_INVALID_PACKAGE_NAME; + return new RebootPreparationError( + RESUME_ON_REBOOT_REBOOT_ERROR_INVALID_PACKAGE_NAME, ARM_REBOOT_ERROR_NONE); } if (!isLskfCaptured(packageName)) { - return RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED; + return new RebootPreparationError(RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED, + ARM_REBOOT_ERROR_NONE); } if (!verifySlotForNextBoot(slotSwitch)) { - return RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMATCH; + return new RebootPreparationError(RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMATCH, + ARM_REBOOT_ERROR_NONE); } final long origId = Binder.clearCallingIdentity(); - boolean result; + int providerErrorCode; try { - result = mInjector.getLockSettingsService().armRebootEscrow(); + providerErrorCode = mInjector.getLockSettingsService().armRebootEscrow(); } finally { Binder.restoreCallingIdentity(origId); } - if (!result) { - Slog.w(TAG, "Failure to escrow key for reboot"); - return RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE; + if (providerErrorCode != ARM_REBOOT_ERROR_NONE) { + Slog.w(TAG, "Failure to escrow key for reboot, providerErrorCode: " + + providerErrorCode); + return new RebootPreparationError( + RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE, providerErrorCode); } - return RESUME_ON_REBOOT_REBOOT_ERROR_NONE; + return new RebootPreparationError(RESUME_ON_REBOOT_REBOOT_ERROR_NONE, + ARM_REBOOT_ERROR_NONE); } private boolean useServerBasedRoR() { @@ -750,7 +792,7 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo } private void reportMetricsOnRebootWithLskf(String packageName, boolean slotSwitch, - @ResumeOnRebootRebootErrorCode int errorCode) { + RebootPreparationError escrowError) { int uid = mInjector.getUidFromPackageName(packageName); boolean serverBased = useServerBasedRoR(); int preparedClientCount; @@ -773,15 +815,31 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo + " request count %d, lskf captured count %d, duration since lskf captured" + " %d seconds.", packageName, preparedClientCount, requestCount, lskfCapturedCount, durationSeconds)); - mInjector.reportRebootEscrowRebootMetrics(errorCode, uid, preparedClientCount, - requestCount, slotSwitch, serverBased, durationSeconds, lskfCapturedCount); + mInjector.reportRebootEscrowRebootMetrics(escrowError.getErrorCodeForMetrics(), uid, + preparedClientCount, requestCount, slotSwitch, serverBased, durationSeconds, + lskfCapturedCount); + } + + private void clearRoRPreparationStateOnRebootFailure(RebootPreparationError escrowError) { + if (!FATAL_ARM_ESCROW_ERRORS.contains(escrowError.mProviderErrorCode)) { + return; + } + + Slog.w(TAG, "Clearing resume on reboot states for all clients on arm escrow error: " + + escrowError.mProviderErrorCode); + synchronized (this) { + mCallerPendingRequest.clear(); + mCallerPreparedForReboot.clear(); + } } private @ResumeOnRebootRebootErrorCode int rebootWithLskfImpl(String packageName, String reason, boolean slotSwitch) { - @ResumeOnRebootRebootErrorCode int errorCode = armRebootEscrow(packageName, slotSwitch); - reportMetricsOnRebootWithLskf(packageName, slotSwitch, errorCode); + RebootPreparationError escrowError = armRebootEscrow(packageName, slotSwitch); + reportMetricsOnRebootWithLskf(packageName, slotSwitch, escrowError); + clearRoRPreparationStateOnRebootFailure(escrowError); + @ResumeOnRebootRebootErrorCode int errorCode = escrowError.mRebootErrorCode; if (errorCode != RESUME_ON_REBOOT_REBOOT_ERROR_NONE) { return errorCode; } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java index 691d174f55f81..f2bb1d662ed9e 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java @@ -86,6 +86,11 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { return makeDirs(mStorageDir, super.getRebootEscrowServerBlob()).getAbsolutePath(); } + @Override + String getRebootEscrowFile(int userId) { + return makeDirs(mStorageDir, super.getRebootEscrowFile(userId)).getAbsolutePath(); + } + @Override protected File getSyntheticPasswordDirectoryForUser(int userId) { return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser( diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index 8c08226201a81..49a54ec1354b1 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -21,6 +21,11 @@ import static android.content.pm.UserInfo.FLAG_PRIMARY; import static android.content.pm.UserInfo.FLAG_PROFILE; import static android.os.UserHandle.USER_SYSTEM; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_ESCROW_NOT_READY; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NONE; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_PROVIDER_MISMATCH; +import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_STORE_ESCROW_KEY; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -327,7 +332,7 @@ public class RebootEscrowManagerTests { assertNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); assertNotNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); verify(mRebootEscrow).storeKey(any()); @@ -351,7 +356,7 @@ public class RebootEscrowManagerTests { when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); @@ -373,7 +378,7 @@ public class RebootEscrowManagerTests { assertNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); doThrow(ServiceSpecificException.class).when(mRebootEscrow).storeKey(any()); - assertFalse(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_STORE_ESCROW_KEY, mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(any()); } @@ -396,7 +401,7 @@ public class RebootEscrowManagerTests { assertNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); assertNotNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); verify(mRebootEscrow, times(1)).storeKey(any()); @@ -408,15 +413,24 @@ public class RebootEscrowManagerTests { @Test public void armService_NoInitialization_Failure() throws Exception { - assertFalse(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_ESCROW_NOT_READY, mService.armRebootEscrowIfNeeded()); verifyNoMoreInteractions(mRebootEscrow); } @Test public void armService_RebootEscrowServiceException_Failure() throws Exception { + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mRebootEscrow); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mRebootEscrow, never()).storeKey(any()); + doThrow(RemoteException.class).when(mRebootEscrow).storeKey(any()); - assertFalse(mService.armRebootEscrowIfNeeded()); - verifyNoMoreInteractions(mRebootEscrow); + assertEquals(ARM_REBOOT_ERROR_STORE_ESCROW_KEY, mService.armRebootEscrowIfNeeded()); + verify(mRebootEscrow).storeKey(any()); } @Test @@ -439,7 +453,7 @@ public class RebootEscrowManagerTests { verify(mRebootEscrow, never()).storeKey(any()); ArgumentCaptor keyByteCaptor = ArgumentCaptor.forClass(byte[].class); - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(keyByteCaptor.capture()); verify(mKeyStoreManager).getKeyStoreEncryptionKey(); @@ -483,7 +497,7 @@ public class RebootEscrowManagerTests { // Use x -> x for both wrap & unwrap functions. when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrowServerBlob()); @@ -520,7 +534,7 @@ public class RebootEscrowManagerTests { // Use x -> x for both wrap & unwrap functions. when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrowServerBlob()); @@ -557,7 +571,7 @@ public class RebootEscrowManagerTests { // Use x -> x for both wrap & unwrap functions. when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrowServerBlob()); @@ -597,7 +611,7 @@ public class RebootEscrowManagerTests { // Use x -> x for both wrap & unwrap functions. when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrowServerBlob()); @@ -635,7 +649,7 @@ public class RebootEscrowManagerTests { verify(mRebootEscrow, never()).storeKey(any()); - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(any()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); @@ -695,7 +709,7 @@ public class RebootEscrowManagerTests { verify(mRebootEscrow, never()).storeKey(any()); ArgumentCaptor keyByteCaptor = ArgumentCaptor.forClass(byte[].class); - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(keyByteCaptor.capture()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); @@ -732,8 +746,7 @@ public class RebootEscrowManagerTests { verify(mockListener).onPreparedForReboot(eq(true)); verify(mRebootEscrow, never()).storeKey(any()); - - assertTrue(mService.armRebootEscrowIfNeeded()); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(any()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); @@ -756,4 +769,28 @@ public class RebootEscrowManagerTests { assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY), metricsErrorCodeCaptor.getValue()); } + + @Test + public void armServiceProviderMismatch_Failure() throws Exception { + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mRebootEscrow); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); + verify(mRebootEscrow, never()).storeKey(any()); + + assertNull( + mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); + // Change the provider to server based, expect the reboot to fail + when(mInjected.forceServerBased()).thenReturn(true); + assertEquals(ARM_REBOOT_ERROR_PROVIDER_MISMATCH, mService.armRebootEscrowIfNeeded()); + assertNull( + mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); + // Verify that the escrow key & data have been cleared. + verify(mRebootEscrow).storeKey(eq(new byte[32])); + assertFalse(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); + } } diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java index 2b358ea5e1058..b64810b665421 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java @@ -18,11 +18,14 @@ package com.android.server.recoverysystem; import static android.os.RecoverySystem.RESUME_ON_REBOOT_REBOOT_ERROR_INVALID_PACKAGE_NAME; import static android.os.RecoverySystem.RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED; +import static android.os.RecoverySystem.RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE; import static android.os.RecoverySystem.RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMATCH; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -31,6 +34,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -91,7 +95,8 @@ public class RecoverySystemServiceTest { mUncryptUpdateFileWriter = mock(FileWriter.class); mLockSettingsInternal = mock(LockSettingsInternal.class); - when(mLockSettingsInternal.armRebootEscrow()).thenReturn(true); + doReturn(LockSettingsInternal.ARM_REBOOT_ERROR_NONE).when(mLockSettingsInternal) + .armRebootEscrow(); Looper looper = InstrumentationRegistry.getContext().getMainLooper(); mIPowerManager = mock(IPowerManager.class); @@ -489,4 +494,27 @@ public class RecoverySystemServiceTest { } // TODO(xunchang) add more multi client tests + + @Test + public void rebootWithLskf_armEscrowDataFatalError_Failure() throws Exception { + doReturn(LockSettingsInternal.ARM_REBOOT_ERROR_PROVIDER_MISMATCH) + .when(mLockSettingsInternal).armRebootEscrow(); + + assertTrue(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null)); + mRecoverySystemService.onPreparedForReboot(true); + assertTrue(mRecoverySystemService.isLskfCaptured(FAKE_OTA_PACKAGE_NAME)); + + when(mSharedPreferences.getInt(eq(FAKE_OTA_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(1); + when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF), + anyInt())).thenReturn(1); + assertEquals(RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE, + mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true)); + // Verify that the RoR preparation state has been cleared. + assertFalse(mRecoverySystemService.isLskfCaptured(FAKE_OTA_PACKAGE_NAME)); + verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(5004 /* provider mismatch */), + eq(1000), eq(1) /* client count */, eq(1) /* request count */, + eq(true) /* slot switch */, anyBoolean(), anyInt(), + eq(1) /* lskf capture count */); + } }