From 4608619a1cf578f16e799510eaa0a21c0f1f08e3 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Mon, 11 May 2026 05:13:51 -0700 Subject: [PATCH] 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 Security: CVE-2026-6478 Author: Michael Paquier Reviewed-by: John Naylor 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 | 5 +++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index f9e1026a12c..761507efc32 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -542,7 +542,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, SCRAM_KEY_LEN) == 0; + return timingsafe_bcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0; } @@ -1082,9 +1082,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; @@ -1136,7 +1136,7 @@ verify_client_proof(scram_state *state) if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey) < 0) elog(ERROR, "could not hash stored key"); - if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0) + if (timingsafe_bcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0) return false; return true; diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index a7b91053dfd..f07ba8bb2fe 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -3469,7 +3469,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 3fcad991a7e..69a7c67e716 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 { @@ -261,7 +262,8 @@ plain_crypt_verify(const char *role, const char *shadow_pass, */ 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 cf3195e5815..f44fc2594f9 100644 --- a/src/interfaces/libpq/fe-auth-scram.c +++ b/src/interfaces/libpq/fe-auth-scram.c @@ -628,7 +628,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) { appendPQExpBufferStr(&conn->errorMessage, libpq_gettext("invalid SCRAM response (nonce mismatch)\n")); @@ -857,7 +857,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, SCRAM_KEY_LEN) != 0) + if (timingsafe_bcmp(expected_ServerSignature, state->ServerSignature, + SCRAM_KEY_LEN) != 0) *match = false; else *match = true;