Skip to content

Commit 4c779ce

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 28dc2c2 commit 4c779ce

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
@@ -543,6 +543,12 @@ scram_verify_plain_password(const char *username, const char *password,
543543
/*
544544
* Parse and validate format of given SCRAM verifier.
545545
*
546+
* On success, the iteration count, salt, stored key, and server key are
547+
* extracted from the verifier, and returned to the caller. For 'stored_key'
548+
* and 'server_key', the caller must pass pre-allocated buffers of size
549+
* SCRAM_KEY_LEN. Salt is returned as a base64-encoded, null-terminated
550+
* string. The buffer for the salt is palloc'd by this function.
551+
*
546552
* Returns true if the SCRAM verifier has been parsed, and false otherwise.
547553
*/
548554
bool
@@ -558,6 +564,8 @@ parse_scram_verifier(const char *verifier, int *iterations, char **salt,
558564
char *serverkey_str;
559565
int decoded_len;
560566
char *decoded_salt_buf;
567+
char *decoded_stored_buf;
568+
char *decoded_server_buf;
561569

562570
/*
563571
* The verifier is of form:
@@ -590,36 +598,47 @@ parse_scram_verifier(const char *verifier, int *iterations, char **salt,
590598
* although we return the encoded version to the caller.
591599
*/
592600
decoded_salt_buf = palloc(pg_b64_dec_len(strlen(salt_str)));
593-
decoded_len = pg_b64_decode(salt_str, strlen(salt_str), decoded_salt_buf);
601+
decoded_len = pg_b64_decode(salt_str, strlen(salt_str),
602+
decoded_salt_buf);
594603
if (decoded_len < 0)
595604
goto invalid_verifier;
596605
*salt = pstrdup(salt_str);
597606

598607
/*
599608
* Decode StoredKey and ServerKey.
600609
*/
601-
if (pg_b64_dec_len(strlen(storedkey_str) != SCRAM_KEY_LEN))
602-
goto invalid_verifier;
610+
decoded_stored_buf = palloc(pg_b64_dec_len(strlen(storedkey_str)));
603611
decoded_len = pg_b64_decode(storedkey_str, strlen(storedkey_str),
604-
(char *) stored_key);
612+
decoded_stored_buf);
605613
if (decoded_len != SCRAM_KEY_LEN)
606614
goto invalid_verifier;
615+
memcpy(stored_key, decoded_stored_buf, SCRAM_KEY_LEN);
607616

608-
if (pg_b64_dec_len(strlen(serverkey_str) != SCRAM_KEY_LEN))
609-
goto invalid_verifier;
617+
decoded_server_buf = palloc(pg_b64_dec_len(strlen(serverkey_str)));
610618
decoded_len = pg_b64_decode(serverkey_str, strlen(serverkey_str),
611-
(char *) server_key);
619+
decoded_server_buf);
612620
if (decoded_len != SCRAM_KEY_LEN)
613621
goto invalid_verifier;
622+
memcpy(server_key, decoded_server_buf, SCRAM_KEY_LEN);
614623

615624
return true;
616625

617626
invalid_verifier:
618-
pfree(v);
619627
*salt = NULL;
620628
return false;
621629
}
622630

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