diff options
5 files changed, 157 insertions, 7 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 00be9fe564da..376118c8122c 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 06a4d550b204..9ab8ad83480e 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 3780fbd61e79..bbdac5636fa4 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.RemoteException; 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.ArrayMap; 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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { @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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { // // - 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 @@ public class LockSettingsService extends ILockSettings.Stub { } } setBoolean(MIGRATED_SP_FULL, true, 0); + if (isWeaverDisabledOnUnsecuredUsers()) { + setBoolean(MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS, true, 0); + } } mThirdPartyAppsStarted = true; @@ -1151,13 +1178,61 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 3a429b041b3c..47788f2e7d2f 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 @@ class SyntheticPasswordManager { // 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 @@ class SyntheticPasswordManager { 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 @@ class SyntheticPasswordManager { 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 @@ class SyntheticPasswordManager { 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 50f3a88cc3a2..5bcddc41eb87 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 @@ public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests { 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)); + } } |