Merge changes If05d11a0,Ie67f0141 into main
* changes:
Polishing for migration to disable Weaver
Support disabling Weaver on unsecured users
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 00be9fe..376118c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1529,6 +1529,11 @@
factory reset. -->
<bool name="config_enableCredentialFactoryResetProtection">true</bool>
+ <!-- If true, then work around broken Weaver HALs that don't work reliably before the device has
+ fully booted. Setting this to true weakens a security feature; it should be done only when
+ necessary, though it is still better than not using Weaver at all. -->
+ <bool name="config_disableWeaverOnUnsecuredUsers">false</bool>
+
<!-- Control the behavior when the user long presses the home button.
0 - Nothing
1 - Launch all apps intent
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 06a4d55..9ab8ad8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3958,6 +3958,7 @@
<java-symbol type="string" name="foreground_service_multiple_separator" />
<java-symbol type="bool" name="config_enableCredentialFactoryResetProtection" />
+ <java-symbol type="bool" name="config_disableWeaverOnUnsecuredUsers" />
<!-- ETWS primary messages -->
<java-symbol type="string" name="etws_primary_default_message_earthquake" />
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 3780fbd..bbdac56 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -99,6 +99,7 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -126,6 +127,7 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -253,6 +255,8 @@
private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
+ private static final String MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS =
+ "migrated_weaver_disabled_on_unsecured_users";
// Duration that LockSettingsService will store the gatekeeper password for. This allows
// multiple biometric enrollments without prompting the user to enter their password via
@@ -309,6 +313,10 @@
@GuardedBy("mUserCreationAndRemovalLock")
private boolean mThirdPartyAppsStarted;
+ // This list contains the (protectorId, userId) of any protectors that were by replaced by a
+ // migration and should be destroyed once rollback to the old build is no longer possible.
+ private ArrayList<Pair<Long, Integer>> mProtectorsToDestroyOnBootCompleted = new ArrayList<>();
+
// Current password metrics for all secured users on the device. Updated when user unlocks the
// device or changes password. Removed if user is stopped with its CE key evicted.
@GuardedBy("this")
@@ -363,6 +371,10 @@
mLockSettingsService.migrateOldDataAfterSystemReady();
mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
} else if (phase == PHASE_BOOT_COMPLETED) {
+ // In the case of an upgrade, PHASE_BOOT_COMPLETED means that a rollback to the old
+ // build can no longer occur. This is the time to destroy any migrated protectors.
+ mLockSettingsService.destroyMigratedProtectors();
+
mLockSettingsService.loadEscrowData();
}
}
@@ -1076,6 +1088,11 @@
mStorage.deleteRepairModePersistentData();
}
+ private boolean isWeaverDisabledOnUnsecuredUsers() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers);
+ }
+
// This is called when Weaver is guaranteed to be available (if the device supports Weaver).
// It does any synthetic password related work that was delayed from earlier in the boot.
private void onThirdPartyAppsStarted() {
@@ -1114,13 +1131,20 @@
//
// - Upgrading from Android 14, where unsecured users didn't have Keystore super keys.
//
+ // - Upgrading from a build with config_disableWeaverOnUnsecuredUsers=false to one with
+ // config_disableWeaverOnUnsecuredUsers=true. (We don't bother to proactively add
+ // Weaver for the reverse update to false, as it's too late to help in that case.)
+ //
// The end result is that all users, regardless of whether they are secured or not, have
- // a synthetic password with all keys initialized and protected by it.
+ // a synthetic password with all keys initialized and protected by it, and honoring
+ // config_disableWeaverOnUnsecuredUsers=true when applicable.
//
// Note: if this migration gets interrupted (e.g. by the device powering off), there
// shouldn't be a problem since this will run again on the next boot, and
// setCeStorageProtection() and initKeystoreSuperKeys(..., true) are idempotent.
- if (!getBoolean(MIGRATED_SP_FULL, false, 0)) {
+ if (!getBoolean(MIGRATED_SP_FULL, false, 0)
+ || (isWeaverDisabledOnUnsecuredUsers()
+ && !getBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, false, 0))) {
for (UserInfo user : mUserManager.getAliveUsers()) {
removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
synchronized (mSpManager) {
@@ -1128,6 +1152,9 @@
}
}
setBoolean(MIGRATED_SP_FULL, true, 0);
+ if (isWeaverDisabledOnUnsecuredUsers()) {
+ setBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, true, 0);
+ }
}
mThirdPartyAppsStarted = true;
@@ -1151,13 +1178,61 @@
getGateKeeperService(), protectorId, LockscreenCredential.createNone(), userId,
null);
SyntheticPassword sp = result.syntheticPassword;
- if (sp == null) {
+ if (isWeaverDisabledOnUnsecuredUsers()) {
+ Slog.i(TAG, "config_disableWeaverOnUnsecuredUsers=true");
+
+ // If config_disableWeaverOnUnsecuredUsers=true, then the Weaver HAL may be buggy and
+ // need multiple retries before it works here to unwrap the SP, if the SP was already
+ // protected by Weaver. Note that the problematic HAL can also deadlock if called with
+ // the ActivityManagerService lock held, but that should not be a problem here since
+ // that lock isn't held here, unlike unlockUserKeyIfUnsecured() where it is.
+ for (int i = 0; i < 12 && sp == null; i++) {
+ Slog.e(TAG, "Failed to unwrap synthetic password. Waiting 5 seconds to retry.");
+ SystemClock.sleep(5000);
+ result = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
+ LockscreenCredential.createNone(), userId, null);
+ sp = result.syntheticPassword;
+ }
+ if (sp == null) {
+ throw new IllegalStateException(
+ "Failed to unwrap synthetic password for unsecured user");
+ }
+ // If the SP is protected by Weaver, then remove the Weaver protection in order to make
+ // config_disableWeaverOnUnsecuredUsers=true take effect.
+ if (result.usedWeaver) {
+ Slog.i(TAG, "Removing Weaver protection from the synthetic password");
+ // Create a new protector, which will not use Weaver.
+ long newProtectorId = mSpManager.createLskfBasedProtector(
+ getGateKeeperService(), LockscreenCredential.createNone(), sp, userId);
+
+ // Out of paranoia, make sure the new protector really works.
+ result = mSpManager.unlockLskfBasedProtector(getGateKeeperService(),
+ newProtectorId, LockscreenCredential.createNone(), userId, null);
+ sp = result.syntheticPassword;
+ if (sp == null) {
+ throw new IllegalStateException("New SP protector does not work");
+ }
+
+ // Replace the protector. Wait until PHASE_BOOT_COMPLETED to destroy the old
+ // protector, since the Weaver slot erasure and freeing cannot be rolled back.
+ setCurrentLskfBasedProtectorId(newProtectorId, userId);
+ mProtectorsToDestroyOnBootCompleted.add(new Pair(protectorId, userId));
+ } else {
+ Slog.i(TAG, "Synthetic password is already not protected by Weaver");
+ }
+ } else if (sp == null) {
Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
return;
}
- // While setCeStorageProtection() is idempotent, it does log some error messages when called
- // again. Skip it if we know it was already handled by an earlier upgrade to Android 14.
- if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) {
+
+ // Call setCeStorageProtection(), to re-encrypt the CE key with the SP if it's currently
+ // encrypted by an empty secret. Skip this if it was definitely already done as part of the
+ // upgrade to Android 14, since while setCeStorageProtection() is idempotent it does log
+ // some error messages when called again. Do not skip this if
+ // config_disableWeaverOnUnsecuredUsers=true, since in that case we'd like to recover from
+ // the case where an earlier upgrade to Android 14 incorrectly skipped this step.
+ if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null
+ || isWeaverDisabledOnUnsecuredUsers()) {
Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
setCeStorageProtection(userId, sp);
}
@@ -1165,6 +1240,17 @@
initKeystoreSuperKeys(userId, sp, /* allowExisting= */ true);
}
+ private void destroyMigratedProtectors() {
+ if (!mProtectorsToDestroyOnBootCompleted.isEmpty()) {
+ synchronized (mSpManager) {
+ for (Pair<Long, Integer> pair : mProtectorsToDestroyOnBootCompleted) {
+ mSpManager.destroyLskfBasedProtector(pair.first, pair.second);
+ }
+ }
+ }
+ mProtectorsToDestroyOnBootCompleted = null; // The list is no longer needed.
+ }
+
/**
* Returns the lowest password quality that still presents the same UI for entering it.
*
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3a429b0..47788f2 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -195,6 +195,8 @@
// ERROR: password / token fails verification
// RETRY: password / token verification is throttled at the moment.
@Nullable public VerifyCredentialResponse gkResponse;
+ // For unlockLskfBasedProtector() this is set to true if the protector uses Weaver.
+ public boolean usedWeaver;
}
/**
@@ -532,6 +534,11 @@
Settings.Global.DEVICE_PROVISIONED, 0) != 0;
}
+ private boolean isWeaverDisabledOnUnsecuredUsers() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers);
+ }
+
@VisibleForTesting
protected android.hardware.weaver.V1_0.IWeaver getWeaverHidlService() throws RemoteException {
try {
@@ -1011,7 +1018,13 @@
Slogf.i(TAG, "Creating LSKF-based protector %016x for user %d", protectorId, userId);
- final IWeaver weaver = getWeaverService();
+ final IWeaver weaver;
+ if (credential.isNone() && isWeaverDisabledOnUnsecuredUsers()) {
+ weaver = null;
+ Slog.w(TAG, "Not using Weaver for unsecured user (disabled by config)");
+ } else {
+ weaver = getWeaverService();
+ }
if (weaver != null) {
// Weaver is available, so make the protector use it to verify the LSKF. Do this even
// if the LSKF is empty, as that gives us support for securely deleting the protector.
@@ -1404,6 +1417,7 @@
int weaverSlot = loadWeaverSlot(protectorId, userId);
if (weaverSlot != INVALID_WEAVER_SLOT) {
// Protector uses Weaver to verify the LSKF
+ result.usedWeaver = true;
final IWeaver weaver = getWeaverService();
if (weaver == null) {
Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable");
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index 50f3a88..5bcddc4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -1,6 +1,10 @@
package com.android.server.locksettings;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
import android.platform.test.annotations.Presubmit;
@@ -56,4 +60,44 @@
mService.initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
assertEquals(Sets.newHashSet(0), mPasswordSlotManager.getUsedSlots());
}
+
+ private int getNumUsedWeaverSlots() {
+ return mPasswordSlotManager.getUsedSlots().size();
+ }
+
+ @Test
+ public void testDisableWeaverOnUnsecuredUsers_false() {
+ final int userId = PRIMARY_USER_ID;
+ when(mResources.getBoolean(eq(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers)))
+ .thenReturn(false);
+ assertEquals(0, getNumUsedWeaverSlots());
+ mService.initializeSyntheticPassword(userId);
+ assertEquals(1, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(newPassword("password"), nonePassword(), userId));
+ assertEquals(1, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"), userId));
+ assertEquals(1, getNumUsedWeaverSlots());
+ }
+
+ @Test
+ public void testDisableWeaverOnUnsecuredUsers_true() {
+ final int userId = PRIMARY_USER_ID;
+ when(mResources.getBoolean(eq(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers)))
+ .thenReturn(true);
+ assertEquals(0, getNumUsedWeaverSlots());
+ mService.initializeSyntheticPassword(userId);
+ assertEquals(0, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(newPassword("password"), nonePassword(), userId));
+ assertEquals(1, getNumUsedWeaverSlots());
+ assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"), userId));
+ assertEquals(0, getNumUsedWeaverSlots());
+ }
+
+ @Test
+ public void testDisableWeaverOnUnsecuredUsers_defaultsToFalse() {
+ assertFalse(mResources.getBoolean(
+ com.android.internal.R.bool.config_disableWeaverOnUnsecuredUsers));
+ }
}