postgres.git / summary / log / commit / refs

commit    c4e7435b30984dacd0396ce0128bd54c8026fef5
Author:   Michael Paquier <michael@paquier.xyz>
Commit:   Noah Misch <noah@leadboat.com>
Date:     Mon May 11 12:13:48 2026 +0000
First released in: 14.23, 15.18, 16.14, 17.10, 18.4

    Apply timingsafe_bcmp() in authentication paths
    
    This commit applies timingsafe_bcmp() to authentication paths that
    handle attributes or data previously compared with memcpy() or strcmp(),
    which are sensitive to timing attacks.
    
    The following data is concerned by this change, some being in the
    backend and some in the frontend:
    - For a SCRAM or MD5 password, the computed key or the MD5 hash compared
    with a password during a plain authentication.
    - For a SCRAM exchange, the stored key, the client's final nonce and the
    server nonce.
    - RADIUS (up to v18), the encrypted password.
    - For MD5 authentication, the MD5(MD5()) hash.
    
    Reported-by: Joe Conway <mail@joeconway.com>
    Security: CVE-2026-6478
    Author: Michael Paquier <michael@paquier.xyz>
    Reviewed-by: John Naylor <johncnaylorls@gmail.com>
    Backpatch-through: 14


src/backend/libpq/auth-scram.c | 8 ++++---- src/backend/libpq/auth.c | 2 +- src/backend/libpq/crypt.c | 6 ++++-- src/interfaces/libpq/fe-auth-scram.c | 6 +++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index 41619599148..b634c0573b8 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -570,7 +570,7 @@ scram_verify_plain_password(const char *username, const char *password, * Compare the secret's Server Key with the one computed from the * user-supplied password. */ - return memcmp(computed_key, server_key, key_length) == 0; + return timingsafe_bcmp(computed_key, server_key, key_length) == 0; } @@ -1118,9 +1118,9 @@ verify_final_nonce(scram_state *state) if (final_nonce_len != client_nonce_len + server_nonce_len) return false; - if (memcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0) + if (timingsafe_bcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0) return false; - if (memcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0) + if (timingsafe_bcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0) return false; return true; @@ -1175,7 +1175,7 @@ verify_client_proof(scram_state *state) client_StoredKey, &errstr) < 0) elog(ERROR, "could not hash stored key: %s", errstr); - if (memcmp(client_StoredKey, state->StoredKey, state->key_length) != 0) + if (timingsafe_bcmp(client_StoredKey, state->StoredKey, state->key_length) != 0) return false; return true; diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index e0576f801d8..4a96d3ba8ef 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -3232,7 +3232,7 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por } pfree(cryptvector); - if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0) + if (timingsafe_bcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0) { ereport(LOG, (errmsg("RADIUS response from %s has incorrect MD5 signature", diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index 629e51e00be..fd8fdde106d 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -196,7 +196,8 @@ md5_crypt_verify(const char *role, const char *shadow_pass, return STATUS_ERROR; } - if (strcmp(client_pass, crypt_pwd) == 0) + if (strlen(client_pass) == strlen(crypt_pwd) && + timingsafe_bcmp(client_pass, crypt_pwd, strlen(crypt_pwd)) == 0) retval = STATUS_OK; else { @@ -258,7 +259,8 @@ plain_crypt_verify(const char *role, const char *shadow_pass, *logdetail = errstr; return STATUS_ERROR; } - if (strcmp(crypt_client_pass, shadow_pass) == 0) + if (strlen(crypt_client_pass) == strlen(shadow_pass) && + timingsafe_bcmp(crypt_client_pass, shadow_pass, strlen(shadow_pass)) == 0) return STATUS_OK; else { diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c index 0bb820e0d97..e602cba0aa7 100644 --- a/src/interfaces/libpq/fe-auth-scram.c +++ b/src/interfaces/libpq/fe-auth-scram.c @@ -626,7 +626,7 @@ read_server_first_message(fe_scram_state *state, char *input) /* Verify immediately that the server used our part of the nonce */ if (strlen(nonce) < strlen(state->client_nonce) || - memcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0) + timingsafe_bcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0) { libpq_append_conn_error(conn, "invalid SCRAM response (nonce mismatch)"); return false; @@ -873,8 +873,8 @@ verify_server_signature(fe_scram_state *state, bool *match, pg_hmac_free(ctx); /* signature processed, so now check after it */ - if (memcmp(expected_ServerSignature, state->ServerSignature, - state->key_length) != 0) + if (timingsafe_bcmp(expected_ServerSignature, state->ServerSignature, + state->key_length) != 0) *match = false; else *match = true; [parent: 8e34acfda115]