Loading services/core/java/com/android/server/locksettings/LockSettingsService.java +5 −1 Original line number Diff line number Diff line Loading @@ -645,6 +645,10 @@ public class LockSettingsService extends ILockSettings.Stub { public boolean isHeadlessSystemUserMode() { return UserManager.isHeadlessSystemUserMode(); } public Duration getTimeSinceBoot() { return Duration.ofMillis(SystemClock.elapsedRealtime()); } } private class SoftwareRateLimiterInjector implements SoftwareRateLimiter.Injector { Loading @@ -661,7 +665,7 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public Duration getTimeSinceBoot() { return Duration.ofMillis(SystemClock.elapsedRealtime()); return mInjector.getTimeSinceBoot(); } @Override Loading services/core/java/com/android/server/locksettings/SoftwareRateLimiter.java +20 −13 Original line number Diff line number Diff line Loading @@ -85,7 +85,7 @@ class SoftwareRateLimiter { /** * A table that maps the number of (real) failures to the delay that is enforced after that * number of (real) failures. Out-of-bounds indices default to the final delay. * number of (real) failures. Out-of-bounds indices default to not allowed. */ private static final Duration[] DELAY_TABLE = new Duration[] { Loading Loading @@ -172,16 +172,6 @@ class SoftwareRateLimiter { mEnforcing = enforcing; } private Duration getCurrentDelay(RateLimiterState state) { if (!mEnforcing) { return Duration.ZERO; } else if (state.numFailures >= 0 && state.numFailures < DELAY_TABLE.length) { return DELAY_TABLE[state.numFailures]; } else { return DELAY_TABLE[DELAY_TABLE.length - 1]; } } /** * Applies the software rate-limiter to the given LSKF guess. * Loading Loading @@ -229,7 +219,16 @@ class SoftwareRateLimiter { // was made, causing the lock screen to block inputs for that amount of time. But checking // for it is still needed to cover any cases where a guess gets made anyway, for example // following a reboot which causes the lock screen to "forget" the delay. final Duration delay = getCurrentDelay(state); final Duration delay; if (mEnforcing) { if (state.numFailures >= DELAY_TABLE.length || state.numFailures < 0) { Slogf.e(TAG, "No more guesses allowed; numFailures=%d", state.numFailures); return SoftwareRateLimiterResult.noMoreGuesses(); } delay = DELAY_TABLE[state.numFailures]; } else { delay = Duration.ZERO; } final Duration now = mInjector.getTimeSinceBoot(); final Duration remainingDelay = state.timeSinceBootOfLastFailure.plus(delay).minus(now); if (remainingDelay.isPositive()) { Loading Loading @@ -362,7 +361,15 @@ class SoftwareRateLimiter { SAVED_WRONG_GUESS_TIMEOUT.toMillis()); } return getCurrentDelay(state); if (!mEnforcing) { return Duration.ZERO; } if (state.numFailures >= DELAY_TABLE.length || state.numFailures < 0) { // In this case actually no more guesses are allowed, but currently there is no way to // convey that information. For now just report the final delay again. return DELAY_TABLE[DELAY_TABLE.length - 1]; } return DELAY_TABLE[state.numFailures]; } private static int getStatsCredentialType(LockscreenCredential firstGuess) { Loading services/core/java/com/android/server/locksettings/SoftwareRateLimiterResult.java +9 −3 Original line number Diff line number Diff line Loading @@ -27,14 +27,16 @@ import java.time.Duration; /** The result from the {@link SoftwareRateLimiter} */ class SoftwareRateLimiterResult { public static final int CREDENTIAL_TOO_SHORT = 0; public static final int RATE_LIMITED = 1; public static final int DUPLICATE_WRONG_GUESS = 2; public static final int CONTINUE_TO_HARDWARE = 3; public static final int NO_MORE_GUESSES = 1; public static final int RATE_LIMITED = 2; public static final int DUPLICATE_WRONG_GUESS = 3; public static final int CONTINUE_TO_HARDWARE = 4; @Retention(RetentionPolicy.SOURCE) @IntDef( value = { CREDENTIAL_TOO_SHORT, NO_MORE_GUESSES, RATE_LIMITED, DUPLICATE_WRONG_GUESS, CONTINUE_TO_HARDWARE, Loading Loading @@ -67,6 +69,10 @@ class SoftwareRateLimiterResult { return new SoftwareRateLimiterResult(CREDENTIAL_TOO_SHORT, null); } static SoftwareRateLimiterResult noMoreGuesses() { return new SoftwareRateLimiterResult(NO_MORE_GUESSES, null); } static SoftwareRateLimiterResult rateLimited(@NonNull Duration remainingDelay) { return new SoftwareRateLimiterResult(RATE_LIMITED, remainingDelay); } Loading services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +14 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import com.android.server.pm.UserManagerInternal; import java.io.FileNotFoundException; import java.security.KeyStore; import java.time.Duration; public class LockSettingsServiceTestable extends LockSettingsService { private Intent mSavedFrpNotificationIntent = null; Loading @@ -58,6 +59,7 @@ public class LockSettingsServiceTestable extends LockSettingsService { private RecoverableKeyStoreManager mRecoverableKeyStoreManager; private UserManagerInternal mUserManagerInternal; private DeviceStateCache mDeviceStateCache; private Duration mTimeSinceBoot; public boolean mIsHeadlessSystemUserMode = false; Loading Loading @@ -148,6 +150,18 @@ public class LockSettingsServiceTestable extends LockSettingsService { public boolean isHeadlessSystemUserMode() { return mIsHeadlessSystemUserMode; } void setTimeSinceBoot(Duration time) { mTimeSinceBoot = time; } @Override public Duration getTimeSinceBoot() { if (mTimeSinceBoot != null) { return mTimeSinceBoot; } return super.getTimeSinceBoot(); } } protected LockSettingsServiceTestable( Loading services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +59 −0 Original line number Diff line number Diff line Loading @@ -806,6 +806,65 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { assertTrue(response.isMatched()); } @Test @EnableFlags(android.security.Flags.FLAG_SOFTWARE_RATELIMITER) public void test20UniqueGuessesAllowed() throws Exception { final int userId = PRIMARY_USER_ID; final LockscreenCredential credential = newPassword("password"); final Duration tenYears = Duration.ofDays(10 * 365); Duration now = Duration.ZERO; VerifyCredentialResponse response; mInjector.setTimeSinceBoot(now); setCredential(userId, credential); for (int i = 0; i < 19; i++) { response = mService.verifyCredential(newPassword("wrong" + i), userId, /* flags= */ 0); assertFalse(response.isMatched()); now = now.plus(tenYears); // Advance 10 years to get past rate-limiting mInjector.setTimeSinceBoot(now); } response = mService.verifyCredential(credential, userId, /* flags= */ 0); assertTrue(response.isMatched()); } @Test @EnableFlags(android.security.Flags.FLAG_SOFTWARE_RATELIMITER) public void testMoreThan20UniqueGuessesNotAllowed() throws Exception { final int userId = PRIMARY_USER_ID; final LockscreenCredential credential = newPassword("password"); final Duration tenYears = Duration.ofDays(10 * 365); Duration now = Duration.ZERO; VerifyCredentialResponse response; mInjector.setTimeSinceBoot(now); setCredential(userId, credential); for (int i = 0; i < 20; i++) { response = mService.verifyCredential(newPassword("wrong" + i), userId, /* flags= */ 0); assertFalse(response.isMatched()); now = now.plus(tenYears); // Advance 10 years to get past rate-limiting mInjector.setTimeSinceBoot(now); } response = mService.verifyCredential(credential, userId, /* flags= */ 0); assertFalse(response.isMatched()); } @Test @DisableFlags(android.security.Flags.FLAG_SOFTWARE_RATELIMITER) public void testMoreThan20UniqueGuessesAllowed_softwareRateLimiterFlagDisabled() throws Exception { final int userId = PRIMARY_USER_ID; final LockscreenCredential credential = newPassword("password"); VerifyCredentialResponse response; setCredential(userId, credential); for (int i = 0; i < 20; i++) { response = mService.verifyCredential(newPassword("wrong" + i), userId, /* flags= */ 0); assertFalse(response.isMatched()); } response = mService.verifyCredential(credential, userId, /* flags= */ 0); assertTrue(response.isMatched()); } @Test public void testVerifyCredentialResponseTimeoutClamping() { testTimeoutClamping(Duration.ofMillis(Long.MIN_VALUE), Integer.MAX_VALUE); Loading Loading
services/core/java/com/android/server/locksettings/LockSettingsService.java +5 −1 Original line number Diff line number Diff line Loading @@ -645,6 +645,10 @@ public class LockSettingsService extends ILockSettings.Stub { public boolean isHeadlessSystemUserMode() { return UserManager.isHeadlessSystemUserMode(); } public Duration getTimeSinceBoot() { return Duration.ofMillis(SystemClock.elapsedRealtime()); } } private class SoftwareRateLimiterInjector implements SoftwareRateLimiter.Injector { Loading @@ -661,7 +665,7 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public Duration getTimeSinceBoot() { return Duration.ofMillis(SystemClock.elapsedRealtime()); return mInjector.getTimeSinceBoot(); } @Override Loading
services/core/java/com/android/server/locksettings/SoftwareRateLimiter.java +20 −13 Original line number Diff line number Diff line Loading @@ -85,7 +85,7 @@ class SoftwareRateLimiter { /** * A table that maps the number of (real) failures to the delay that is enforced after that * number of (real) failures. Out-of-bounds indices default to the final delay. * number of (real) failures. Out-of-bounds indices default to not allowed. */ private static final Duration[] DELAY_TABLE = new Duration[] { Loading Loading @@ -172,16 +172,6 @@ class SoftwareRateLimiter { mEnforcing = enforcing; } private Duration getCurrentDelay(RateLimiterState state) { if (!mEnforcing) { return Duration.ZERO; } else if (state.numFailures >= 0 && state.numFailures < DELAY_TABLE.length) { return DELAY_TABLE[state.numFailures]; } else { return DELAY_TABLE[DELAY_TABLE.length - 1]; } } /** * Applies the software rate-limiter to the given LSKF guess. * Loading Loading @@ -229,7 +219,16 @@ class SoftwareRateLimiter { // was made, causing the lock screen to block inputs for that amount of time. But checking // for it is still needed to cover any cases where a guess gets made anyway, for example // following a reboot which causes the lock screen to "forget" the delay. final Duration delay = getCurrentDelay(state); final Duration delay; if (mEnforcing) { if (state.numFailures >= DELAY_TABLE.length || state.numFailures < 0) { Slogf.e(TAG, "No more guesses allowed; numFailures=%d", state.numFailures); return SoftwareRateLimiterResult.noMoreGuesses(); } delay = DELAY_TABLE[state.numFailures]; } else { delay = Duration.ZERO; } final Duration now = mInjector.getTimeSinceBoot(); final Duration remainingDelay = state.timeSinceBootOfLastFailure.plus(delay).minus(now); if (remainingDelay.isPositive()) { Loading Loading @@ -362,7 +361,15 @@ class SoftwareRateLimiter { SAVED_WRONG_GUESS_TIMEOUT.toMillis()); } return getCurrentDelay(state); if (!mEnforcing) { return Duration.ZERO; } if (state.numFailures >= DELAY_TABLE.length || state.numFailures < 0) { // In this case actually no more guesses are allowed, but currently there is no way to // convey that information. For now just report the final delay again. return DELAY_TABLE[DELAY_TABLE.length - 1]; } return DELAY_TABLE[state.numFailures]; } private static int getStatsCredentialType(LockscreenCredential firstGuess) { Loading
services/core/java/com/android/server/locksettings/SoftwareRateLimiterResult.java +9 −3 Original line number Diff line number Diff line Loading @@ -27,14 +27,16 @@ import java.time.Duration; /** The result from the {@link SoftwareRateLimiter} */ class SoftwareRateLimiterResult { public static final int CREDENTIAL_TOO_SHORT = 0; public static final int RATE_LIMITED = 1; public static final int DUPLICATE_WRONG_GUESS = 2; public static final int CONTINUE_TO_HARDWARE = 3; public static final int NO_MORE_GUESSES = 1; public static final int RATE_LIMITED = 2; public static final int DUPLICATE_WRONG_GUESS = 3; public static final int CONTINUE_TO_HARDWARE = 4; @Retention(RetentionPolicy.SOURCE) @IntDef( value = { CREDENTIAL_TOO_SHORT, NO_MORE_GUESSES, RATE_LIMITED, DUPLICATE_WRONG_GUESS, CONTINUE_TO_HARDWARE, Loading Loading @@ -67,6 +69,10 @@ class SoftwareRateLimiterResult { return new SoftwareRateLimiterResult(CREDENTIAL_TOO_SHORT, null); } static SoftwareRateLimiterResult noMoreGuesses() { return new SoftwareRateLimiterResult(NO_MORE_GUESSES, null); } static SoftwareRateLimiterResult rateLimited(@NonNull Duration remainingDelay) { return new SoftwareRateLimiterResult(RATE_LIMITED, remainingDelay); } Loading
services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +14 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import com.android.server.pm.UserManagerInternal; import java.io.FileNotFoundException; import java.security.KeyStore; import java.time.Duration; public class LockSettingsServiceTestable extends LockSettingsService { private Intent mSavedFrpNotificationIntent = null; Loading @@ -58,6 +59,7 @@ public class LockSettingsServiceTestable extends LockSettingsService { private RecoverableKeyStoreManager mRecoverableKeyStoreManager; private UserManagerInternal mUserManagerInternal; private DeviceStateCache mDeviceStateCache; private Duration mTimeSinceBoot; public boolean mIsHeadlessSystemUserMode = false; Loading Loading @@ -148,6 +150,18 @@ public class LockSettingsServiceTestable extends LockSettingsService { public boolean isHeadlessSystemUserMode() { return mIsHeadlessSystemUserMode; } void setTimeSinceBoot(Duration time) { mTimeSinceBoot = time; } @Override public Duration getTimeSinceBoot() { if (mTimeSinceBoot != null) { return mTimeSinceBoot; } return super.getTimeSinceBoot(); } } protected LockSettingsServiceTestable( Loading
services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +59 −0 Original line number Diff line number Diff line Loading @@ -806,6 +806,65 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { assertTrue(response.isMatched()); } @Test @EnableFlags(android.security.Flags.FLAG_SOFTWARE_RATELIMITER) public void test20UniqueGuessesAllowed() throws Exception { final int userId = PRIMARY_USER_ID; final LockscreenCredential credential = newPassword("password"); final Duration tenYears = Duration.ofDays(10 * 365); Duration now = Duration.ZERO; VerifyCredentialResponse response; mInjector.setTimeSinceBoot(now); setCredential(userId, credential); for (int i = 0; i < 19; i++) { response = mService.verifyCredential(newPassword("wrong" + i), userId, /* flags= */ 0); assertFalse(response.isMatched()); now = now.plus(tenYears); // Advance 10 years to get past rate-limiting mInjector.setTimeSinceBoot(now); } response = mService.verifyCredential(credential, userId, /* flags= */ 0); assertTrue(response.isMatched()); } @Test @EnableFlags(android.security.Flags.FLAG_SOFTWARE_RATELIMITER) public void testMoreThan20UniqueGuessesNotAllowed() throws Exception { final int userId = PRIMARY_USER_ID; final LockscreenCredential credential = newPassword("password"); final Duration tenYears = Duration.ofDays(10 * 365); Duration now = Duration.ZERO; VerifyCredentialResponse response; mInjector.setTimeSinceBoot(now); setCredential(userId, credential); for (int i = 0; i < 20; i++) { response = mService.verifyCredential(newPassword("wrong" + i), userId, /* flags= */ 0); assertFalse(response.isMatched()); now = now.plus(tenYears); // Advance 10 years to get past rate-limiting mInjector.setTimeSinceBoot(now); } response = mService.verifyCredential(credential, userId, /* flags= */ 0); assertFalse(response.isMatched()); } @Test @DisableFlags(android.security.Flags.FLAG_SOFTWARE_RATELIMITER) public void testMoreThan20UniqueGuessesAllowed_softwareRateLimiterFlagDisabled() throws Exception { final int userId = PRIMARY_USER_ID; final LockscreenCredential credential = newPassword("password"); VerifyCredentialResponse response; setCredential(userId, credential); for (int i = 0; i < 20; i++) { response = mService.verifyCredential(newPassword("wrong" + i), userId, /* flags= */ 0); assertFalse(response.isMatched()); } response = mService.verifyCredential(credential, userId, /* flags= */ 0); assertTrue(response.isMatched()); } @Test public void testVerifyCredentialResponseTimeoutClamping() { testTimeoutClamping(Duration.ofMillis(Long.MIN_VALUE), Integer.MAX_VALUE); Loading