]> 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:50 +0000 (05:13 -0700)
committerNoah Misch <noah@leadboat.com>
Mon, 11 May 2026 12:13:50 +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 ee7f52218ab33bc23184b77d2d52e822309271d4..2e66dd9c3839396390833f87a80c1c0f90cae332 100644 (file)
@@ -555,7 +555,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;
 }
 
 
@@ -1095,9 +1095,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;
@@ -1151,7 +1151,7 @@ verify_client_proof(scram_state *state)
        if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
                elog(ERROR, "could not hash stored key: %s", errstr);
 
-       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 280cfb9fff37155b45ad93a076c2706c90675e26..281ebb79e03e7d18e68213cddac324abde814049 100644 (file)
@@ -3322,7 +3322,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 1ff8b0507d4a856ef6cdbebc2903508c9611bb00..21ab5f1bb0c3c39d3a6f462483da9e4ed9295c4e 100644 (file)
@@ -195,7 +195,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
        {
@@ -257,7 +258,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 cd66e9757ba42fb9e467d17c7eadf4b4277d5680..a06218f9d776e0eb1e073eabae912c820ad6a91a 100644 (file)
@@ -648,7 +648,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"));
@@ -901,7 +901,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;