]> 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:51 +0000 (05:13 -0700)
committerNoah Misch <noah@leadboat.com>
Mon, 11 May 2026 12:13:51 +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 f9e1026a12c06e2aa1e107cc96d3b8cd0fd79031..761507efc325f5570b94457da2f02eb90279c9d4 100644 (file)
@@ -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;
index a7b91053dfd9c7c127b4c1a32f394b066f37d280..f07ba8bb2febb2ef8c5fcc62f9a2f8be9c0d72d5 100644 (file)
@@ -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",
index 3fcad991a7e635e493312034ba0eaacbe2cf9216..69a7c67e716bfc314ee01e8e8108fbdc3a020aeb 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
        {
@@ -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
                        {
index cf3195e58150c9c8aa54c211dadd0cf866da14ec..f44fc2594f9174baf6988115ce6e03bddce98437 100644 (file)
@@ -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;