]> 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:48 +0000 (05:13 -0700)
committerNoah Misch <noah@leadboat.com>
Mon, 11 May 2026 12:13:48 +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 416195991486de55f76db1bd3c1fe5d367a1cd29..b634c0573b8a578a43a63138943cd4d6917e415e 100644 (file)
@@ -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;
index e0576f801d85dbbac756a424f5a2bdaa7e42ea04..4a96d3ba8ef8d580eb72d365949a94f5e6dcd958 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 629e51e00be9e53524aee2432239690edab31627..fd8fdde106da5da54e0ce116f1ab95bfc31bd4bd 100644 (file)
@@ -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
                        {
index 0bb820e0d97419509d51f5bac206afdb49d75e54..e602cba0aa706bd3d183c8a1f44e18c9719b742b 100644 (file)
@@ -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;