Skip to content

Commit 90adc16

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 93d4484 commit 90adc16

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
@@ -472,6 +472,12 @@ scram_verify_plain_password(const char *username, const char *password,
472472
/*
473473
* Parse and validate format of given SCRAM verifier.
474474
*
475+
* On success, the iteration count, salt, stored key, and server key are
476+
* extracted from the verifier, and returned to the caller. For 'stored_key'
477+
* and 'server_key', the caller must pass pre-allocated buffers of size
478+
* SCRAM_KEY_LEN. Salt is returned as a base64-encoded, null-terminated
479+
* string. The buffer for the salt is palloc'd by this function.
480+
*
475481
* Returns true if the SCRAM verifier has been parsed, and false otherwise.
476482
*/
477483
bool
@@ -487,6 +493,8 @@ parse_scram_verifier(const char *verifier, int *iterations, char **salt,
487493
char *serverkey_str;
488494
int decoded_len;
489495
char *decoded_salt_buf;
496+
char *decoded_stored_buf;
497+
char *decoded_server_buf;
490498

491499
/*
492500
* The verifier is of form:
@@ -519,36 +527,47 @@ parse_scram_verifier(const char *verifier, int *iterations, char **salt,
519527
* although we return the encoded version to the caller.
520528
*/
521529
decoded_salt_buf = palloc(pg_b64_dec_len(strlen(salt_str)));
522-
decoded_len = pg_b64_decode(salt_str, strlen(salt_str), decoded_salt_buf);
530+
decoded_len = pg_b64_decode(salt_str, strlen(salt_str),
531+
decoded_salt_buf);
523532
if (decoded_len < 0)
524533
goto invalid_verifier;
525534
*salt = pstrdup(salt_str);
526535

527536
/*
528537
* Decode StoredKey and ServerKey.
529538
*/
530-
if (pg_b64_dec_len(strlen(storedkey_str) != SCRAM_KEY_LEN))
531-
goto invalid_verifier;
539+
decoded_stored_buf = palloc(pg_b64_dec_len(strlen(storedkey_str)));
532540
decoded_len = pg_b64_decode(storedkey_str, strlen(storedkey_str),
533-
(char *) stored_key);
541+
decoded_stored_buf);
534542
if (decoded_len != SCRAM_KEY_LEN)
535543
goto invalid_verifier;
544+
memcpy(stored_key, decoded_stored_buf, SCRAM_KEY_LEN);
536545

537-
if (pg_b64_dec_len(strlen(serverkey_str) != SCRAM_KEY_LEN))
538-
goto invalid_verifier;
546+
decoded_server_buf = palloc(pg_b64_dec_len(strlen(serverkey_str)));
539547
decoded_len = pg_b64_decode(serverkey_str, strlen(serverkey_str),
540-
(char *) server_key);
548+
decoded_server_buf);
541549
if (decoded_len != SCRAM_KEY_LEN)
542550
goto invalid_verifier;
551+
memcpy(server_key, decoded_server_buf, SCRAM_KEY_LEN);
543552

544553
return true;
545554

546555
invalid_verifier:
547-
pfree(v);
548556
*salt = NULL;
549557
return false;
550558
}
551559

560+
/*
561+
* Generate plausible SCRAM verifier parameters for mock authentication.
562+
*
563+
* In a normal authentication, these are extracted from the verifier
564+
* stored in the server. This function generates values that look
565+
* realistic, for when there is no stored verifier.
566+
*
567+
* Like in parse_scram_verifier(), for 'stored_key' and 'server_key', the
568+
* caller must pass pre-allocated buffers of size SCRAM_KEY_LEN, and
569+
* the buffer for the salt is palloc'd by this function.
570+
*/
552571
static void
553572
mock_scram_verifier(const char *username, int *iterations, char **salt,
554573
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)