diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index c9e356ec14703..351dd6ed3d3d5 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -16,6 +16,8 @@ package com.android.server.locksettings; +import static android.os.UserHandle.USER_SYSTEM; + import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; @@ -24,6 +26,7 @@ import android.hardware.rebootescrow.IRebootEscrow; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserManager; +import android.provider.Settings; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -39,6 +42,26 @@ import java.util.NoSuchElementException; class RebootEscrowManager { private static final String TAG = "RebootEscrowManager"; + /** + * Used in the database storage to indicate the boot count at which the reboot escrow was + * previously armed. + */ + @VisibleForTesting + public static final String REBOOT_ESCROW_ARMED_KEY = "reboot_escrow_armed_count"; + + /** + * Number of boots until we consider the escrow data to be stale for the purposes of metrics. + *
+ * If the delta between the current boot number and the boot number stored when the mechanism + * was armed is under this number and the escrow mechanism fails, we report it as a failure of + * the mechanism. + *
+ * If the delta over this number and escrow fails, we will not report the metric as failed
+ * since there most likely was some other issue if the device rebooted several times before
+ * getting to the escrow restore code.
+ */
+ private static final int BOOT_COUNT_TOLERANCE = 5;
+
/**
* Used to track when the reboot escrow is wanted. Should stay true once escrow is requested
* unless clearRebootEscrow is called. This will allow all the active users to be unlocked
@@ -74,6 +97,7 @@ class RebootEscrowManager {
interface Callbacks {
boolean isUserSecure(int userId);
+
void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId);
}
@@ -92,7 +116,8 @@ class RebootEscrowManager {
return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
}
- public @Nullable IRebootEscrow getRebootEscrow() {
+ @Nullable
+ public IRebootEscrow getRebootEscrow() {
try {
return IRebootEscrow.Stub.asInterface(ServiceManager.getService(
"android.hardware.rebootescrow.IRebootEscrow/default"));
@@ -101,6 +126,15 @@ class RebootEscrowManager {
}
return null;
}
+
+ public int getBootCount() {
+ return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT,
+ 0);
+ }
+
+ public void reportMetric(boolean success) {
+ FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, success);
+ }
}
RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
@@ -135,7 +169,7 @@ class RebootEscrowManager {
for (UserInfo user : users) {
mStorage.removeRebootEscrow(user.id);
}
- FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false);
+ onEscrowRestoreComplete(false);
return;
}
@@ -143,8 +177,19 @@ class RebootEscrowManager {
for (UserInfo user : rebootEscrowUsers) {
allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey);
}
- FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED,
- allUsersUnlocked);
+ onEscrowRestoreComplete(allUsersUnlocked);
+ }
+
+ private void onEscrowRestoreComplete(boolean success) {
+ int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, 0, USER_SYSTEM);
+ mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
+
+ int bootCountDelta = mInjector.getBootCount() - previousBootCount;
+ if (bootCountDelta > BOOT_COUNT_TOLERANCE) {
+ return;
+ }
+
+ mInjector.reportMetric(success);
}
private RebootEscrowKey getAndClearRebootEscrowKey() {
@@ -267,6 +312,8 @@ class RebootEscrowManager {
return;
}
+ mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
+
try {
rebootEscrow.storeKey(new byte[32]);
} catch (RemoteException e) {
@@ -308,6 +355,11 @@ class RebootEscrowManager {
} catch (RemoteException e) {
Slog.e(TAG, "Failed escrow secret to RebootEscrow HAL", e);
}
+
+ if (armedRebootEscrow) {
+ mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM);
+ }
+
return armedRebootEscrow;
}
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 44d88d2b44d32..1cf8525eecdd3 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -19,12 +19,16 @@ 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.os.UserHandle.USER_SYSTEM;
import static org.junit.Assert.assertFalse;
+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.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -32,8 +36,10 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertNull;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.pm.UserInfo;
import android.hardware.rebootescrow.IRebootEscrow;
import android.os.RemoteException;
@@ -49,6 +55,7 @@ import com.android.internal.widget.RebootEscrowListener;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.io.File;
import java.util.ArrayList;
@@ -76,16 +83,26 @@ public class RebootEscrowManagerTests {
LockSettingsStorageTestable mStorage;
+ private MockableRebootEscrowInjected mInjected;
private RebootEscrowManager mService;
+ public interface MockableRebootEscrowInjected {
+ int getBootCount();
+
+ void reportMetric(boolean success);
+ }
+
static class MockInjector extends RebootEscrowManager.Injector {
private final IRebootEscrow mRebootEscrow;
private final UserManager mUserManager;
+ private final MockableRebootEscrowInjected mInjected;
- MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow) {
+ MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow,
+ MockableRebootEscrowInjected injected) {
super(context);
mRebootEscrow = rebootEscrow;
mUserManager = userManager;
+ mInjected = injected;
}
@Override
@@ -97,11 +114,21 @@ public class RebootEscrowManagerTests {
public IRebootEscrow getRebootEscrow() {
return mRebootEscrow;
}
+
+ @Override
+ public int getBootCount() {
+ return mInjected.getBootCount();
+ }
+
+ @Override
+ public void reportMetric(boolean success) {
+ mInjected.reportMetric(success);
+ }
}
@Before
public void setUp_baseServices() throws Exception {
- mContext = mock(Context.class);
+ mContext = new ContextWrapper(InstrumentationRegistry.getContext());
mUserManager = mock(UserManager.class);
mCallbacks = mock(RebootEscrowManager.Callbacks.class);
mRebootEscrow = mock(IRebootEscrow.class);
@@ -119,8 +146,9 @@ public class RebootEscrowManagerTests {
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);
- mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow),
- mCallbacks, mStorage);
+ mInjected = mock(MockableRebootEscrowInjected.class);
+ mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow,
+ mInjected), mCallbacks, mStorage);
}
@Test
@@ -160,7 +188,11 @@ public class RebootEscrowManagerTests {
verify(mockListener).onPreparedForReboot(eq(true));
verify(mRebootEscrow, never()).storeKey(any());
+ assertNull(
+ mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
assertTrue(mService.armRebootEscrowIfNeeded());
+ assertNotNull(
+ mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
verify(mRebootEscrow).storeKey(any());
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
@@ -180,7 +212,15 @@ public class RebootEscrowManagerTests {
FAKE_AUTH_TOKEN);
verify(mRebootEscrow, never()).storeKey(any());
+ assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
+ assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
+ assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
+
+ assertNull(
+ mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
assertTrue(mService.armRebootEscrowIfNeeded());
+ assertNotNull(
+ mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
verify(mRebootEscrow, times(1)).storeKey(any());
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
@@ -200,4 +240,105 @@ public class RebootEscrowManagerTests {
assertFalse(mService.armRebootEscrowIfNeeded());
verifyNoMoreInteractions(mRebootEscrow);
}
+
+ @Test
+ public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception {
+ mService.loadRebootEscrowDataIfAvailable();
+ }
+
+ @Test
+ public void loadRebootEscrowDataIfAvailable_Success() throws Exception {
+ when(mInjected.getBootCount()).thenReturn(0);
+
+ 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());
+
+ ArgumentCaptor