]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Apply timingsafe_bcmp() in authentication paths
authorMichael Paquier <michael@paquier.xyz>
Mon, 11 May 2026 12:13:47 +0000 (05:13 -0700)
committerNoah Misch <noah@leadboat.com>
Mon, 11 May 2026 12:13:47 +0000 (05:13 -0700)
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
src/backend/libpq/auth.c
src/backend/libpq/crypt.c
src/interfaces/libpq/fe-auth-scram.c

index db778405724ad2846d4cd25b4cf1f467d2eeb62d..8fe5b989c2f7ba48e937cabd46b20c2b96634b45 100644 (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;
index 4da46666439db5e348e03df4ced2d7f931036403..40b6f93bffc814177d59261060e18997cca923a1 100644 (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",
index f6b641e726ec355c0ab34c8865cfe2c0a4dd52af..063ac95778da1ed0cf9dcb58dd80ad32f26e3016 100644 (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
                        {
index f6d6a5aa977b83cd8da7001b71240152b6f92d09..f0feeeaca51f51b63a401e011f00e8afd419d322 100644 (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;