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
This commit is contained in:
Michael Paquier 2026-05-11 05:13:47 -07:00 committed by Noah Misch
parent c5790ec4fd
commit d93ef41317
4 changed files with 12 additions and 10 deletions

View file

@ -581,7 +581,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;
}
@ -1132,9 +1132,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;
@ -1188,7 +1188,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;

View file

@ -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",

View file

@ -230,7 +230,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
{
@ -292,7 +293,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
{

View file

@ -631,7 +631,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;
@ -896,8 +896,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;