]> 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:46 +0000 (05:13 -0700)
committerNoah Misch <noah@leadboat.com>
Mon, 11 May 2026 12:13:46 +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/crypt.c
src/interfaces/libpq/fe-auth-scram.c

index 0267edb29cd9e7fd41f30bf3e41babad3a16993c..4bac15fc5c1be96c85e204b1893c8f2eabfaee1a 100644 (file)
@@ -579,7 +579,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;
 }
 
 
@@ -1130,9 +1130,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;
@@ -1186,7 +1186,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 37ccec355c79c62314ae21888c7086a015d71b43..739a2e6fa8bd8cbd9d576d2f6f75d9289d7814cf 100644 (file)
@@ -293,7 +293,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;
 
@@ -372,7 +373,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 99103b7e2b66779ddefcf706d753b3af653bbbcd..08c7c66694668d19cce4b1c5fca2cf7b1791d4d0 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;