]> 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:49 +0000 (05:13 -0700)
committerNoah Misch <noah@leadboat.com>
Mon, 11 May 2026 12:13:49 +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 9b286aa4d7fad615f7075561bb20474347b45169..1b9e1cc0cc163a35cdba838595f49508caabd33f 100644 (file)
@@ -577,7 +577,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;
 }
 
 
@@ -1125,9 +1125,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;
@@ -1182,7 +1182,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 183d9e731d6baea8c697b64079973bd449d95597..ea149e478b08040be8cb199f38dcb111ebdb84be 100644 (file)
@@ -3248,7 +3248,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 ef496a0bea9955721fb497ca7e8144bec96ae1d9..3e67e75fe84e386f489a9acfdb300fc8da2af40b 100644 (file)
@@ -197,7 +197,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
        {
@@ -259,7 +260,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 6b779ec7ffd1e77bae0d9bf9d16c658d302132e5..4219e21b3fb055215c65d05e135333a30d45f09b 100644 (file)
@@ -632,7 +632,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;
@@ -879,8 +879,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;