Skip to content

Commit 09ec55b

Browse files
committed
Fix buffer overflow when parsing SCRAM verifiers in backend
Any authenticated user can overflow a stack-based buffer by changing the user's own password to a purpose-crafted value. This often suffices to execute arbitrary code as the PostgreSQL operating system account. This fix is contributed by multiple folks, based on an initial analysis from Tom Lane. This issue has been introduced by 68e61ee, so it was possible to make use of it at authentication time. It became more easily to trigger after ccae190 which has made the SCRAM parsing more strict when changing a password, in the case where the client passes down a verifier already hashed using SCRAM. Back-patch to v10 where SCRAM has been introduced. Reported-by: Alexander Lakhin Author: Jonathan Katz, Heikki Linnakangas, Michael Paquier Security: CVE-2019-10164 Backpatch-through: 10
1 parent 3412030 commit 09ec55b

File tree

3 files changed

+68
-8
lines changed

3 files changed

+68
-8
lines changed

src/backend/libpq/auth-scram.c

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,12 @@ scram_verify_plain_password(const char *username, const char *password,
542542
/*
543543
* Parse and validate format of given SCRAM verifier.
544544
*
545+
* On success, the iteration count, salt, stored key, and server key are
546+
* extracted from the verifier, and returned to the caller. For 'stored_key'
547+
* and 'server_key', the caller must pass pre-allocated buffers of size
548+
* SCRAM_KEY_LEN. Salt is returned as a base64-encoded, null-terminated
549+
* string. The buffer for the salt is palloc'd by this function.
550+
*
545551
* Returns true if the SCRAM verifier has been parsed, and false otherwise.
546552
*/
547553
bool
@@ -557,6 +563,8 @@ parse_scram_verifier(const char *verifier, int *iterations, char **salt,
557563
char *serverkey_str;
558564
int decoded_len;
559565
char *decoded_salt_buf;
566+
char *decoded_stored_buf;
567+
char *decoded_server_buf;
560568

561569
/*
562570
* The verifier is of form:
@@ -589,36 +597,47 @@ parse_scram_verifier(const char *verifier, int *iterations, char **salt,
589597
* although we return the encoded version to the caller.
590598
*/
591599
decoded_salt_buf = palloc(pg_b64_dec_len(strlen(salt_str)));
592-
decoded_len = pg_b64_decode(salt_str, strlen(salt_str), decoded_salt_buf);
600+
decoded_len = pg_b64_decode(salt_str, strlen(salt_str),
601+
decoded_salt_buf);
593602
if (decoded_len < 0)
594603
goto invalid_verifier;
595604
*salt = pstrdup(salt_str);
596605

597606
/*
598607
* Decode StoredKey and ServerKey.
599608
*/
600-
if (pg_b64_dec_len(strlen(storedkey_str) != SCRAM_KEY_LEN))
601-
goto invalid_verifier;
609+
decoded_stored_buf = palloc(pg_b64_dec_len(strlen(storedkey_str)));
602610
decoded_len = pg_b64_decode(storedkey_str, strlen(storedkey_str),
603-
(char *) stored_key);
611+
decoded_stored_buf);
604612
if (decoded_len != SCRAM_KEY_LEN)
605613
goto invalid_verifier;
614+
memcpy(stored_key, decoded_stored_buf, SCRAM_KEY_LEN);
606615

607-
if (pg_b64_dec_len(strlen(serverkey_str) != SCRAM_KEY_LEN))
608-
goto invalid_verifier;
616+
decoded_server_buf = palloc(pg_b64_dec_len(strlen(serverkey_str)));
609617
decoded_len = pg_b64_decode(serverkey_str, strlen(serverkey_str),
610-
(char *) server_key);
618+
decoded_server_buf);
611619
if (decoded_len != SCRAM_KEY_LEN)
612620
goto invalid_verifier;
621+
memcpy(server_key, decoded_server_buf, SCRAM_KEY_LEN);
613622

614623
return true;
615624

616625
invalid_verifier:
617-
pfree(v);
618626
*salt = NULL;
619627
return false;
620628
}
621629

630+
/*
631+
* Generate plausible SCRAM verifier parameters for mock authentication.
632+
*
633+
* In a normal authentication, these are extracted from the verifier
634+
* stored in the server. This function generates values that look
635+
* realistic, for when there is no stored verifier.
636+
*
637+
* Like in parse_scram_verifier(), for 'stored_key' and 'server_key', the
638+
* caller must pass pre-allocated buffers of size SCRAM_KEY_LEN, and
639+
* the buffer for the salt is palloc'd by this function.
640+
*/
622641
static void
623642
mock_scram_verifier(const char *username, int *iterations, char **salt,
624643
uint8 *stored_key, uint8 *server_key)

src/test/regress/expected/password.out

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,26 @@ SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
100100

101101
(1 row)
102102

103+
-- Test with invalid stored and server keys.
104+
--
105+
-- The first is valid, to act as a control. The others have too long
106+
-- stored/server keys. They will be re-hashed.
107+
CREATE ROLE regress_passwd_sha_len0 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI=';
108+
CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96RqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI=';
109+
CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
110+
-- Check that the invalid verifiers were re-hashed. A re-hashed verifier
111+
-- should not contain the original salt.
112+
SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
113+
FROM pg_authid
114+
WHERE rolname LIKE 'regress_passwd_sha_len%'
115+
ORDER BY rolname;
116+
rolname | is_rolpassword_rehashed
117+
-------------------------+-------------------------
118+
regress_passwd_sha_len0 | f
119+
regress_passwd_sha_len1 | t
120+
regress_passwd_sha_len2 | t
121+
(3 rows)
122+
103123
DROP ROLE regress_passwd1;
104124
DROP ROLE regress_passwd2;
105125
DROP ROLE regress_passwd3;
@@ -109,6 +129,9 @@ DROP ROLE regress_passwd6;
109129
DROP ROLE regress_passwd7;
110130
DROP ROLE regress_passwd8;
111131
DROP ROLE regress_passwd_empty;
132+
DROP ROLE regress_passwd_sha_len0;
133+
DROP ROLE regress_passwd_sha_len1;
134+
DROP ROLE regress_passwd_sha_len2;
112135
-- all entries should have been removed
113136
SELECT rolname, rolpassword
114137
FROM pg_authid

src/test/regress/sql/password.sql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,21 @@ ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
7575
ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
7676
SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
7777

78+
-- Test with invalid stored and server keys.
79+
--
80+
-- The first is valid, to act as a control. The others have too long
81+
-- stored/server keys. They will be re-hashed.
82+
CREATE ROLE regress_passwd_sha_len0 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI=';
83+
CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96RqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI=';
84+
CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
85+
86+
-- Check that the invalid verifiers were re-hashed. A re-hashed verifier
87+
-- should not contain the original salt.
88+
SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
89+
FROM pg_authid
90+
WHERE rolname LIKE 'regress_passwd_sha_len%'
91+
ORDER BY rolname;
92+
7893
DROP ROLE regress_passwd1;
7994
DROP ROLE regress_passwd2;
8095
DROP ROLE regress_passwd3;
@@ -84,6 +99,9 @@ DROP ROLE regress_passwd6;
8499
DROP ROLE regress_passwd7;
85100
DROP ROLE regress_passwd8;
86101
DROP ROLE regress_passwd_empty;
102+
DROP ROLE regress_passwd_sha_len0;
103+
DROP ROLE regress_passwd_sha_len1;
104+
DROP ROLE regress_passwd_sha_len2;
87105

88106
-- all entries should have been removed
89107
SELECT rolname, rolpassword

0 commit comments

Comments
 (0)