Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bd008faa authored by Eric Biggers's avatar Eric Biggers Committed by Android (Google) Code Review
Browse files

Merge "Don't allow more than 20 failed primary authentication attempts" into main

parents 1b817a32 5799bc7d
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -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 {
@@ -661,7 +665,7 @@ public class LockSettingsService extends ILockSettings.Stub {

        @Override
        public Duration getTimeSinceBoot() {
            return Duration.ofMillis(SystemClock.elapsedRealtime());
            return mInjector.getTimeSinceBoot();
        }

        @Override
+20 −13
Original line number Diff line number Diff line
@@ -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[] {
@@ -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.
     *
@@ -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()) {
@@ -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) {
+9 −3
Original line number Diff line number Diff line
@@ -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,
@@ -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);
    }
+14 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;

@@ -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(
+59 −0
Original line number Diff line number Diff line
@@ -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