Skip to content

Server-provided SCRAM iteration count may keep a Tokio worker busy #9

Description

@jordans10

Description

Hello, while reviewing the SCRAM-SHA-256 authentication flow, I noticed a potential performance and security concern that I would like to confirm.

The relevant locations appear to be:

parse_server_first() parses i= from the SCRAM server-first message as a u32, but does not appear to enforce a reasonable upper bound:

i = v.parse::<u32>().ok();

client_final() passes this value directly to hi_sha256(), where it controls the PBKDF2-HMAC-SHA256 iteration count:

for _ in 1..iters {
    u = hmac_sha256(password, &u);
    for (o, ui) in out.iter_mut().zip(u.iter()) {
        *o ^= *ui;
    }
}

The computation runs after reading the server-first message and before reaching the next .await, so the complete loop executes synchronously on the current Tokio worker without yielding.

Potential impact

The normal PostgreSQL default is i=4096, for which the impact should be minimal.

However, a malicious, impersonated, compromised, or severely misconfigured PostgreSQL server could return a valid SCRAM message containing an excessively large i= value. This may cause the client to perform a large amount of server-controlled synchronous CPU work and delay other tasks running on the same Tokio runtime.

In a local single-threaded Tokio test using the current ScramClient::client_final() implementation, i=500000 caused approximately 78 ms of scheduling delay. The execution time grew roughly linearly with i, while the current parsing logic can theoretically accept values up to u32::MAX.

I may have missed a constraint applied elsewhere in the project. If no additional constraint exists, this could potentially allow client-side computational amplification or denial of service.

Possible mitigation

It may be worth enforcing a reasonable supported range for the SCRAM iteration count and rejecting unusually large values. For example:

const MAX_SCRAM_ITERATIONS: u32 = 100_000;

if !(1..=MAX_SCRAM_ITERATIONS).contains(&iters) {
    return Err(PgWireError::Auth(
        "SCRAM iteration count is outside the supported range".into(),
    ));
}

The exact limit may need to be chosen based on PostgreSQL compatibility and the requirements of this project. For accepted values that may still be expensive, spawn_blocking could also help avoid keeping a Tokio worker busy.

Please let me know if I have misunderstood the call flow or the potential impact.

Thank you for taking the time to review this.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions