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
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index a42448c..b474d7c 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -169,8 +169,9 @@
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.
+ *
+ * <p> 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 f5df3ab..940979d 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 @@
* 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 f96fa09..14d9d85 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 @@
}
@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 76ecc1a..c01523a 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 @@
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 @@
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<UserInfo> users = mUserManager.getUsers();
for (UserInfo user : users) {
@@ -596,20 +603,30 @@
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 @@
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 @@
}
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 6ea030f..81a51e2 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.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.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 @@
*/
@IntDef({ ROR_NEED_PREPARATION,
ROR_SKIP_PREPARATION_AND_NOTIFY,
- ROR_SKIP_PREPARATION_NOT_NOTIFY})
+ ROR_SKIP_PREPARATION_NOT_NOTIFY })
private @interface ResumeOnRebootActionsOnRequest {}
/**
@@ -151,10 +154,43 @@
*/
@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<Integer> 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.
*/
public static class PreferencesManager {
@@ -709,34 +745,40 @@
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 @@
}
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 @@
+ " 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 691d174..f2bb1d6 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -87,6 +87,11 @@
}
@Override
+ String getRebootEscrowFile(int userId) {
+ return makeDirs(mStorageDir, super.getRebootEscrowFile(userId)).getAbsolutePath();
+ }
+
+ @Override
protected File getSyntheticPasswordDirectoryForUser(int userId) {
return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser(
userId).getAbsolutePath());
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 8c08226..49a54ec 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_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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
verify(mRebootEscrow, never()).storeKey(any());
ArgumentCaptor<byte[]> 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
verify(mRebootEscrow, never()).storeKey(any());
ArgumentCaptor<byte[]> 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 @@
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 @@
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 2b358ea..b64810b 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 @@
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.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 @@
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 @@
}
// 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 */);
+ }
}