Loading core/java/android/security/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,13 @@ flag { bug: "325129836" } flag { name: "software_ratelimiter" namespace: "security" description: "Enable support for SoftwareRateLimiter in LockSettingsService" bug: "395976735" } flag { name: "frp_enforcement" is_exported: true Loading core/java/com/android/internal/widget/VerifyCredentialResponse.java +10 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.util.Slog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.time.Duration; /** * Response object for a ILockSettings credential verification request. Loading Loading @@ -113,6 +114,11 @@ public final class VerifyCredentialResponse implements Parcelable { 0L /* gatekeeperPasswordHandle */); } /** Like {@link #fromTimeout(int)}, but takes a Duration instead of a raw milliseconds value. */ public static VerifyCredentialResponse fromTimeout(Duration timeout) { return fromTimeout((int) Math.min(timeout.toMillis(), (long) Integer.MAX_VALUE)); } /** * Since error (incorrect password) should never result in any of the other fields from * being populated, provide a default method to return a VerifyCredentialResponse. Loading Loading @@ -167,6 +173,10 @@ public final class VerifyCredentialResponse implements Parcelable { return mTimeout; } public Duration getTimeoutAsDuration() { return Duration.ofMillis(mTimeout); } public @ResponseCode int getResponseCode() { return mResponseCode; } Loading core/res/res/values/config.xml +8 −0 Original line number Diff line number Diff line Loading @@ -1546,6 +1546,14 @@ necessary, though it is still better than not using Weaver at all. --> <bool name="config_disableWeaverOnUnsecuredUsers">false</bool> <!-- If true, then the Android system server will rate-limit guesses of lock screen knowledge factors such as PINs. This does not replace the hardware (Gatekeeper or Weaver) rate-limiter, but rather applies concurrently to it. The software rate-limiter uses exponential backoff and allows up to 20 guesses in theory, but practically about 10-15 before the delays get very long. The software rate-limiter also does not count duplicate wrong guesses, improving usability. --> <bool name="config_softwareLskfRateLimiterEnforcing">false</bool> <!-- Control the behavior when the user long presses the home button. 0 - Nothing 1 - Launch all apps intent Loading core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -4149,6 +4149,7 @@ <java-symbol type="bool" name="config_enableCredentialFactoryResetProtection" /> <java-symbol type="bool" name="config_disableWeaverOnUnsecuredUsers" /> <java-symbol type="bool" name="config_softwareLskfRateLimiterEnforcing" /> <!-- ETWS primary messages --> <java-symbol type="string" name="etws_primary_default_message_earthquake" /> Loading services/core/java/com/android/server/locksettings/LockSettingsService.java +102 −4 Original line number Diff line number Diff line Loading @@ -171,6 +171,7 @@ import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.text.SimpleDateFormat; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; Loading Loading @@ -277,6 +278,7 @@ public class LockSettingsService extends ILockSettings.Stub { private final SynchronizedStrongAuthTracker mStrongAuthTracker; private final BiometricDeferredQueue mBiometricDeferredQueue; private final LongSparseArray<byte[]> mGatekeeperPasswords; private final SoftwareRateLimiter mSoftwareRateLimiter; private final NotificationManager mNotificationManager; protected final UserManager mUserManager; Loading Loading @@ -659,6 +661,34 @@ public class LockSettingsService extends ILockSettings.Stub { } } private class SoftwareRateLimiterInjector implements SoftwareRateLimiter.Injector { @Override public int readWrongGuessCounter(LskfIdentifier id) { return mSpManager.readWrongGuessCounter(id); } @Override public void writeWrongGuessCounter(LskfIdentifier id, int count) { mSpManager.writeWrongGuessCounter(id, count); } @Override public Duration getTimeSinceBoot() { return Duration.ofMillis(SystemClock.elapsedRealtime()); } @Override public void removeCallbacksAndMessages(Object token) { Handler.getMain().removeCallbacksAndMessages(token); } @Override public void postDelayed(Runnable runnable, Object token, long delayMillis) { Handler.getMain().postDelayed(runnable, token, delayMillis); } } public LockSettingsService(Context context) { this(new Injector(context)); } Loading @@ -674,6 +704,14 @@ public class LockSettingsService extends ILockSettings.Stub { mStrongAuth = injector.getStrongAuth(); mActivityManager = injector.getActivityManager(); boolean enforcing = mContext.getResources() .getBoolean( com.android.internal.R.bool .config_softwareLskfRateLimiterEnforcing); mSoftwareRateLimiter = new SoftwareRateLimiter(new SoftwareRateLimiterInjector(), enforcing); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_STARTING); filter.addAction(Intent.ACTION_LOCALE_CHANGED); Loading Loading @@ -2403,6 +2441,24 @@ public class LockSettingsService extends ILockSettings.Stub { VerifyCredentialResponse response; synchronized (mSpManager) { final long protectorId = isSpecialUserId(userId) ? SyntheticPasswordManager.NULL_PROTECTOR_ID : getCurrentLskfBasedProtectorId(userId); final LskfIdentifier lskfId = new LskfIdentifier(userId, protectorId); if (android.security.Flags.softwareRatelimiter()) { SoftwareRateLimiterResult res = mSoftwareRateLimiter.apply(lskfId, credential); switch (res.code) { case SoftwareRateLimiterResult.CONTINUE_TO_HARDWARE: break; case SoftwareRateLimiterResult.RATE_LIMITED: return VerifyCredentialResponse.fromTimeout(res.remainingDelay); case SoftwareRateLimiterResult.CREDENTIAL_TOO_SHORT: case SoftwareRateLimiterResult.DUPLICATE_WRONG_GUESS: default: return VerifyCredentialResponse.fromError(); } } if (isSpecialUserId(userId)) { response = mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(), credential, progressCallback); Loading @@ -2410,13 +2466,11 @@ public class LockSettingsService extends ILockSettings.Stub { && userId == USER_FRP) { mStorage.deactivateFactoryResetProtectionWithoutSecret(); } return response; return reportResultToSoftwareRateLimiter(response, lskfId, credential); } long protectorId = getCurrentLskfBasedProtectorId(userId); authResult = mSpManager.unlockLskfBasedProtector( getGateKeeperService(), protectorId, credential, userId, progressCallback); response = authResult.gkResponse; response = reportResultToSoftwareRateLimiter(authResult.gkResponse, lskfId, credential); if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { if ((flags & VERIFY_FLAG_WRITE_REPAIR_MODE_PW) != 0) { Loading Loading @@ -2452,6 +2506,35 @@ public class LockSettingsService extends ILockSettings.Stub { return response; } /** * Reports the result of the real credential check to the software rate-limiter, if enabled. * Returns either the same {@link VerifyCredentialResponse}, or a modified {@link * VerifyCredentialResponse} with a larger timeout. */ private VerifyCredentialResponse reportResultToSoftwareRateLimiter( VerifyCredentialResponse response, LskfIdentifier lskfId, LockscreenCredential credential) { if (android.security.Flags.softwareRatelimiter()) { if (response.isMatched()) { mSoftwareRateLimiter.reportSuccess(lskfId); } else { // TODO(b/395976735): don't count transient failures Duration swTimeout = mSoftwareRateLimiter.reportWrongGuess(lskfId, credential); // The software rate-limiter may use longer delays than the hardware one. While the // long-term solution is to update the hardware rate-limiter to match, for now this // case needs to be handled by reporting the maximum of the two delays so that the // lock screen doesn't allow another attempt until both rate-limiters allow it. Duration hwTimeout = response.getTimeoutAsDuration(); if (swTimeout.compareTo(hwTimeout) > 0) { response = VerifyCredentialResponse.fromTimeout(swTimeout); } } } return response; } private void notifyLockSettingsStateListeners(boolean success, int userId) { for (LockSettingsStateListener listener : mLockSettingsStateListeners) { if (success) { Loading Loading @@ -2608,6 +2691,10 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager.removeUser(getGateKeeperService(), userId); mStrongAuth.removeUser(userId); if (android.security.Flags.softwareRatelimiter()) { mSoftwareRateLimiter.clearUserState(userId); } AndroidKeyStoreMaintenance.onUserRemoved(userId); mUnifiedProfilePasswordCache.removePassword(userId); Loading Loading @@ -3130,6 +3217,9 @@ public class LockSettingsService extends ILockSettings.Stub { entry.getValue().zeroize(); } } if (android.security.Flags.softwareRatelimiter()) { mSoftwareRateLimiter.clearLskfState(new LskfIdentifier(userId, oldProtectorId)); } mSpManager.destroyLskfBasedProtector(oldProtectorId, userId); Slogf.i(TAG, "Successfully changed lockscreen credential of user %d", userId); return newProtectorId; Loading Loading @@ -3508,6 +3598,14 @@ public class LockSettingsService extends ILockSettings.Stub { pw.println(); pw.decreaseIndent(); if (android.security.Flags.softwareRatelimiter()) { pw.println("SoftwareRateLimiter:"); pw.increaseIndent(); mSoftwareRateLimiter.dump(pw); pw.println(); pw.decreaseIndent(); } pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size()); synchronized (mUserCreationAndRemovalLock) { pw.println("ThirdPartyAppsStarted: " + mThirdPartyAppsStarted); Loading Loading
core/java/android/security/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,13 @@ flag { bug: "325129836" } flag { name: "software_ratelimiter" namespace: "security" description: "Enable support for SoftwareRateLimiter in LockSettingsService" bug: "395976735" } flag { name: "frp_enforcement" is_exported: true Loading
core/java/com/android/internal/widget/VerifyCredentialResponse.java +10 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.util.Slog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.time.Duration; /** * Response object for a ILockSettings credential verification request. Loading Loading @@ -113,6 +114,11 @@ public final class VerifyCredentialResponse implements Parcelable { 0L /* gatekeeperPasswordHandle */); } /** Like {@link #fromTimeout(int)}, but takes a Duration instead of a raw milliseconds value. */ public static VerifyCredentialResponse fromTimeout(Duration timeout) { return fromTimeout((int) Math.min(timeout.toMillis(), (long) Integer.MAX_VALUE)); } /** * Since error (incorrect password) should never result in any of the other fields from * being populated, provide a default method to return a VerifyCredentialResponse. Loading Loading @@ -167,6 +173,10 @@ public final class VerifyCredentialResponse implements Parcelable { return mTimeout; } public Duration getTimeoutAsDuration() { return Duration.ofMillis(mTimeout); } public @ResponseCode int getResponseCode() { return mResponseCode; } Loading
core/res/res/values/config.xml +8 −0 Original line number Diff line number Diff line Loading @@ -1546,6 +1546,14 @@ necessary, though it is still better than not using Weaver at all. --> <bool name="config_disableWeaverOnUnsecuredUsers">false</bool> <!-- If true, then the Android system server will rate-limit guesses of lock screen knowledge factors such as PINs. This does not replace the hardware (Gatekeeper or Weaver) rate-limiter, but rather applies concurrently to it. The software rate-limiter uses exponential backoff and allows up to 20 guesses in theory, but practically about 10-15 before the delays get very long. The software rate-limiter also does not count duplicate wrong guesses, improving usability. --> <bool name="config_softwareLskfRateLimiterEnforcing">false</bool> <!-- Control the behavior when the user long presses the home button. 0 - Nothing 1 - Launch all apps intent Loading
core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -4149,6 +4149,7 @@ <java-symbol type="bool" name="config_enableCredentialFactoryResetProtection" /> <java-symbol type="bool" name="config_disableWeaverOnUnsecuredUsers" /> <java-symbol type="bool" name="config_softwareLskfRateLimiterEnforcing" /> <!-- ETWS primary messages --> <java-symbol type="string" name="etws_primary_default_message_earthquake" /> Loading
services/core/java/com/android/server/locksettings/LockSettingsService.java +102 −4 Original line number Diff line number Diff line Loading @@ -171,6 +171,7 @@ import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.text.SimpleDateFormat; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; Loading Loading @@ -277,6 +278,7 @@ public class LockSettingsService extends ILockSettings.Stub { private final SynchronizedStrongAuthTracker mStrongAuthTracker; private final BiometricDeferredQueue mBiometricDeferredQueue; private final LongSparseArray<byte[]> mGatekeeperPasswords; private final SoftwareRateLimiter mSoftwareRateLimiter; private final NotificationManager mNotificationManager; protected final UserManager mUserManager; Loading Loading @@ -659,6 +661,34 @@ public class LockSettingsService extends ILockSettings.Stub { } } private class SoftwareRateLimiterInjector implements SoftwareRateLimiter.Injector { @Override public int readWrongGuessCounter(LskfIdentifier id) { return mSpManager.readWrongGuessCounter(id); } @Override public void writeWrongGuessCounter(LskfIdentifier id, int count) { mSpManager.writeWrongGuessCounter(id, count); } @Override public Duration getTimeSinceBoot() { return Duration.ofMillis(SystemClock.elapsedRealtime()); } @Override public void removeCallbacksAndMessages(Object token) { Handler.getMain().removeCallbacksAndMessages(token); } @Override public void postDelayed(Runnable runnable, Object token, long delayMillis) { Handler.getMain().postDelayed(runnable, token, delayMillis); } } public LockSettingsService(Context context) { this(new Injector(context)); } Loading @@ -674,6 +704,14 @@ public class LockSettingsService extends ILockSettings.Stub { mStrongAuth = injector.getStrongAuth(); mActivityManager = injector.getActivityManager(); boolean enforcing = mContext.getResources() .getBoolean( com.android.internal.R.bool .config_softwareLskfRateLimiterEnforcing); mSoftwareRateLimiter = new SoftwareRateLimiter(new SoftwareRateLimiterInjector(), enforcing); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_STARTING); filter.addAction(Intent.ACTION_LOCALE_CHANGED); Loading Loading @@ -2403,6 +2441,24 @@ public class LockSettingsService extends ILockSettings.Stub { VerifyCredentialResponse response; synchronized (mSpManager) { final long protectorId = isSpecialUserId(userId) ? SyntheticPasswordManager.NULL_PROTECTOR_ID : getCurrentLskfBasedProtectorId(userId); final LskfIdentifier lskfId = new LskfIdentifier(userId, protectorId); if (android.security.Flags.softwareRatelimiter()) { SoftwareRateLimiterResult res = mSoftwareRateLimiter.apply(lskfId, credential); switch (res.code) { case SoftwareRateLimiterResult.CONTINUE_TO_HARDWARE: break; case SoftwareRateLimiterResult.RATE_LIMITED: return VerifyCredentialResponse.fromTimeout(res.remainingDelay); case SoftwareRateLimiterResult.CREDENTIAL_TOO_SHORT: case SoftwareRateLimiterResult.DUPLICATE_WRONG_GUESS: default: return VerifyCredentialResponse.fromError(); } } if (isSpecialUserId(userId)) { response = mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(), credential, progressCallback); Loading @@ -2410,13 +2466,11 @@ public class LockSettingsService extends ILockSettings.Stub { && userId == USER_FRP) { mStorage.deactivateFactoryResetProtectionWithoutSecret(); } return response; return reportResultToSoftwareRateLimiter(response, lskfId, credential); } long protectorId = getCurrentLskfBasedProtectorId(userId); authResult = mSpManager.unlockLskfBasedProtector( getGateKeeperService(), protectorId, credential, userId, progressCallback); response = authResult.gkResponse; response = reportResultToSoftwareRateLimiter(authResult.gkResponse, lskfId, credential); if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { if ((flags & VERIFY_FLAG_WRITE_REPAIR_MODE_PW) != 0) { Loading Loading @@ -2452,6 +2506,35 @@ public class LockSettingsService extends ILockSettings.Stub { return response; } /** * Reports the result of the real credential check to the software rate-limiter, if enabled. * Returns either the same {@link VerifyCredentialResponse}, or a modified {@link * VerifyCredentialResponse} with a larger timeout. */ private VerifyCredentialResponse reportResultToSoftwareRateLimiter( VerifyCredentialResponse response, LskfIdentifier lskfId, LockscreenCredential credential) { if (android.security.Flags.softwareRatelimiter()) { if (response.isMatched()) { mSoftwareRateLimiter.reportSuccess(lskfId); } else { // TODO(b/395976735): don't count transient failures Duration swTimeout = mSoftwareRateLimiter.reportWrongGuess(lskfId, credential); // The software rate-limiter may use longer delays than the hardware one. While the // long-term solution is to update the hardware rate-limiter to match, for now this // case needs to be handled by reporting the maximum of the two delays so that the // lock screen doesn't allow another attempt until both rate-limiters allow it. Duration hwTimeout = response.getTimeoutAsDuration(); if (swTimeout.compareTo(hwTimeout) > 0) { response = VerifyCredentialResponse.fromTimeout(swTimeout); } } } return response; } private void notifyLockSettingsStateListeners(boolean success, int userId) { for (LockSettingsStateListener listener : mLockSettingsStateListeners) { if (success) { Loading Loading @@ -2608,6 +2691,10 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager.removeUser(getGateKeeperService(), userId); mStrongAuth.removeUser(userId); if (android.security.Flags.softwareRatelimiter()) { mSoftwareRateLimiter.clearUserState(userId); } AndroidKeyStoreMaintenance.onUserRemoved(userId); mUnifiedProfilePasswordCache.removePassword(userId); Loading Loading @@ -3130,6 +3217,9 @@ public class LockSettingsService extends ILockSettings.Stub { entry.getValue().zeroize(); } } if (android.security.Flags.softwareRatelimiter()) { mSoftwareRateLimiter.clearLskfState(new LskfIdentifier(userId, oldProtectorId)); } mSpManager.destroyLskfBasedProtector(oldProtectorId, userId); Slogf.i(TAG, "Successfully changed lockscreen credential of user %d", userId); return newProtectorId; Loading Loading @@ -3508,6 +3598,14 @@ public class LockSettingsService extends ILockSettings.Stub { pw.println(); pw.decreaseIndent(); if (android.security.Flags.softwareRatelimiter()) { pw.println("SoftwareRateLimiter:"); pw.increaseIndent(); mSoftwareRateLimiter.dump(pw); pw.println(); pw.decreaseIndent(); } pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size()); synchronized (mUserCreationAndRemovalLock) { pw.println("ThirdPartyAppsStarted: " + mThirdPartyAppsStarted); Loading