On Thu, Sep 19, 2024 at 07:37:55PM -0400, Jonathan S. Katz wrote:
> On 9/19/24 6:14 PM, Tom Lane wrote:
>> Nathan Bossart <nathandboss...@gmail.com> writes:
>> > Oh, actually, I see that we are already validating the hash, but you can
>> > create valid SCRAM-SHA-256 hashes that are really long.
> 
> You _can_, but it's up to a driver or a very determined user to do this, as
> it involves creating a very long salt.

I can't think of any reason to support this, unless we want Alexander to
find more bugs.

>>  So putting an
>> > arbitrary limit (patch attached) is probably the correct path forward.  I'd
>> > also remove pg_authid's TOAST table while at it.
>> 
>> Shouldn't we enforce the limit in every case in encrypt_password,
>> not just this one?  (I do agree that encrypt_password is an okay
>> place to enforce it.)

Yeah, that seems like a good idea.  I've attached a more fleshed-out patch
set that applies the limit in all cases.

> +1; if there's any breakage, my guess is it would be on very long plaintext
> passwords, but that would be from a very old upgrade?

IIUC there's zero support for plain-text passwords in newer versions, and
any that remain in older clusters will be silently converted to a hash by
pg_upgrade.

>> I think you will get pushback from a limit of 256 bytes --- I seem
>> to recall discussion of actual use-cases where people were using
>> strings of a couple of kB.  Whatever the limit is, the error message
>> had better cite it explicitly.
> 
> I think it's OK to be a bit generous with the limit. Also, currently oru
> hashes are 256-bit (I know the above says byte), but this could increase
> should we support larger hashes.

Hm.  Are you thinking of commit 67a472d?  That one removed the password
length restrictions in client-side code and password message packets, which
I think is entirely separate from the lengths of the hashes stored in
rolpassword.

>> Also, the ereport call needs an errcode.
>> ERRCODE_PROGRAM_LIMIT_EXCEEDED is probably suitable.

This is added in v2.

-- 
nathan
>From 0493783d99195080f5ef48f8b5d749b2a3979be6 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nat...@postgresql.org>
Date: Thu, 19 Sep 2024 20:59:10 -0500
Subject: [PATCH v2 1/2] place limit on password hash length

---
 src/backend/libpq/crypt.c              | 56 ++++++++++++++++++--------
 src/include/libpq/crypt.h              |  9 +++++
 src/test/regress/expected/password.out |  7 ++++
 src/test/regress/sql/password.sql      |  4 ++
 4 files changed, 59 insertions(+), 17 deletions(-)

diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 629e51e00b..2cdc977bea 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -125,32 +125,54 @@ encrypt_password(PasswordType target_type, const char 
*role,
                 * Cannot convert an already-encrypted password from one format 
to
                 * another, so return it as it is.
                 */
-               return pstrdup(password);
+               encrypted_password = pstrdup(password);
        }
-
-       switch (target_type)
+       else
        {
-               case PASSWORD_TYPE_MD5:
-                       encrypted_password = palloc(MD5_PASSWD_LEN + 1);
+               switch (target_type)
+               {
+                       case PASSWORD_TYPE_MD5:
+                               encrypted_password = palloc(MD5_PASSWD_LEN + 1);
 
-                       if (!pg_md5_encrypt(password, role, strlen(role),
-                                                               
encrypted_password, &errstr))
-                               elog(ERROR, "password encryption failed: %s", 
errstr);
-                       return encrypted_password;
+                               if (!pg_md5_encrypt(password, role, 
strlen(role),
+                                                                       
encrypted_password, &errstr))
+                                       elog(ERROR, "password encryption 
failed: %s", errstr);
+                               break;
 
-               case PASSWORD_TYPE_SCRAM_SHA_256:
-                       return pg_be_scram_build_secret(password);
+                       case PASSWORD_TYPE_SCRAM_SHA_256:
+                               encrypted_password = 
pg_be_scram_build_secret(password);
+                               break;
 
-               case PASSWORD_TYPE_PLAINTEXT:
-                       elog(ERROR, "cannot encrypt password with 'plaintext'");
+                       case PASSWORD_TYPE_PLAINTEXT:
+                               elog(ERROR, "cannot encrypt password with 
'plaintext'");
+                               encrypted_password = palloc(0); /* keep 
compiler quiet */
+                               break;
+               }
        }
 
        /*
-        * This shouldn't happen, because the above switch statements should
-        * handle every combination of source and target password types.
+        * Valid password hashes may be very long, but we don't want to store
+        * anything that might get TOASTed, since de-TOASTing won't work during
+        * authentication because we haven't selected a database yet and cannot
+        * read pg_class. 256 bytes should be more than enough for all practical
+        * use, so fail for anything longer.
         */
-       elog(ERROR, "cannot encrypt password to requested type");
-       return NULL;                            /* keep compiler quiet */
+       if (strlen(encrypted_password) > MAX_ENCRYPTED_PASSWORD_LEN)
+       {
+               /*
+                * We don't expect any of our own hashing routines to produce 
hashes
+                * that are too long.
+                */
+               Assert(guessed_type != PASSWORD_TYPE_PLAINTEXT);
+
+               ereport(ERROR,
+                               (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                                errmsg("encrypted password is too long"),
+                                errdetail("Encrypted passwords must be no 
longer than %d characters.",
+                                                  
MAX_ENCRYPTED_PASSWORD_LEN)));
+       }
+
+       return encrypted_password;
 }
 
 /*
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index f744de4d20..7a1099b917 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,15 @@
 
 #include "datatype/timestamp.h"
 
+/*
+ * Valid password hashes may be very long, but we don't want to store anything
+ * that might get TOASTed, since de-TOASTing won't work during authentication
+ * because we haven't selected a database yet and cannot read pg_class.  256
+ * bytes should be more than enough for all practical use, and our own password
+ * encryption routines should never produce hashes longer than this.
+ */
+#define MAX_ENCRYPTED_PASSWORD_LEN (256)
+
 /*
  * Types of password hashes or secrets.
  *
diff --git a/src/test/regress/expected/password.out 
b/src/test/regress/expected/password.out
index 924d6e001d..8ab995a162 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -127,6 +127,13 @@ SELECT rolname, rolpassword not like 
'%A6xHKoH/494E941doaPOYg==%' as is_rolpassw
  regress_passwd_sha_len2 | t
 (3 rows)
 
+-- Test that valid hashes that are too long are rejected
+CREATE ROLE regress_passwd10 PASSWORD 
'SCRAM-SHA-256$00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004096:NuDacwYSUxeOeFUEf3ivTQ==$Wgvq3OCYrJI6eUfvKlAzn4p/j3mzgWzXbVnWeFK1qhY=:r1qSP0j2QojCjLpFUjI0i6ckInvxJDKoyWnN3zF8WCM=';
+ERROR:  encrypted password is too long
+DETAIL:  Encrypted passwords must be no longer than 256 characters.
+ALTER ROLE regress_passwd9 PASSWORD 
'SCRAM-SHA-256$00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004096:NuDacwYSUxeOeFUEf3ivTQ==$Wgvq3OCYrJI6eUfvKlAzn4p/j3mzgWzXbVnWeFK1qhY=:r1qSP0j2QojCjLpFUjI0i6ckInvxJDKoyWnN3zF8WCM=';
+ERROR:  encrypted password is too long
+DETAIL:  Encrypted passwords must be no longer than 256 characters.
 DROP ROLE regress_passwd1;
 DROP ROLE regress_passwd2;
 DROP ROLE regress_passwd3;
diff --git a/src/test/regress/sql/password.sql 
b/src/test/regress/sql/password.sql
index bb82aa4aa2..442c903c00 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -95,6 +95,10 @@ SELECT rolname, rolpassword not like 
'%A6xHKoH/494E941doaPOYg==%' as is_rolpassw
     WHERE rolname LIKE 'regress_passwd_sha_len%'
     ORDER BY rolname;
 
+-- Test that valid hashes that are too long are rejected
+CREATE ROLE regress_passwd10 PASSWORD 
'SCRAM-SHA-256$00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004096:NuDacwYSUxeOeFUEf3ivTQ==$Wgvq3OCYrJI6eUfvKlAzn4p/j3mzgWzXbVnWeFK1qhY=:r1qSP0j2QojCjLpFUjI0i6ckInvxJDKoyWnN3zF8WCM=';
+ALTER ROLE regress_passwd9 PASSWORD 
'SCRAM-SHA-256$00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004096:NuDacwYSUxeOeFUEf3ivTQ==$Wgvq3OCYrJI6eUfvKlAzn4p/j3mzgWzXbVnWeFK1qhY=:r1qSP0j2QojCjLpFUjI0i6ckInvxJDKoyWnN3zF8WCM=';
+
 DROP ROLE regress_passwd1;
 DROP ROLE regress_passwd2;
 DROP ROLE regress_passwd3;
-- 
2.39.5 (Apple Git-154)

>From 535057307ac9ad47668bed4c48767e6d131391bf Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nat...@postgresql.org>
Date: Thu, 19 Sep 2024 21:09:30 -0500
Subject: [PATCH v2 2/2] remove pg_authid's TOAST table

XXX: NEEDS CATVERSION BUMP
---
 src/backend/catalog/catalog.c              |  4 +---
 src/include/catalog/pg_authid.h            |  2 --
 src/test/regress/expected/create_index.out | 12 ++++++------
 src/test/regress/expected/misc_sanity.out  |  5 ++++-
 src/test/regress/expected/tablespace.out   | 12 ++++++------
 src/test/regress/sql/create_index.sql      |  8 ++++----
 src/test/regress/sql/misc_sanity.sql       |  2 ++
 src/test/regress/sql/tablespace.sql        |  8 ++++----
 8 files changed, 27 insertions(+), 26 deletions(-)

diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 6c39434a30..d6b07a7865 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -300,9 +300,7 @@ IsSharedRelation(Oid relationId)
                relationId == TablespaceOidIndexId)
                return true;
        /* These are their toast tables and toast indexes */
-       if (relationId == PgAuthidToastTable ||
-               relationId == PgAuthidToastIndex ||
-               relationId == PgDatabaseToastTable ||
+       if (relationId == PgDatabaseToastTable ||
                relationId == PgDatabaseToastIndex ||
                relationId == PgDbRoleSettingToastTable ||
                relationId == PgDbRoleSettingToastIndex ||
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index e08863f78a..e846d75731 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -55,8 +55,6 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION 
BKI_ROWTYPE_OID(284
  */
 typedef FormData_pg_authid *Form_pg_authid;
 
-DECLARE_TOAST_WITH_MACRO(pg_authid, 4175, 4176, PgAuthidToastTable, 
PgAuthidToastIndex);
-
 DECLARE_UNIQUE_INDEX(pg_authid_rolname_index, 2676, AuthIdRolnameIndexId, 
pg_authid, btree(rolname name_ops));
 DECLARE_UNIQUE_INDEX_PKEY(pg_authid_oid_index, 2677, AuthIdOidIndexId, 
pg_authid, btree(oid oid_ops));
 
diff --git a/src/test/regress/expected/create_index.out 
b/src/test/regress/expected/create_index.out
index cf6eac5734..22c8787e80 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2665,9 +2665,9 @@ ERROR:  cannot reindex system catalogs concurrently
 REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
 ERROR:  cannot reindex system catalogs concurrently
 -- These are the toast table and index of pg_authid.
-REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1262; -- no catalog toast table
 ERROR:  cannot reindex system catalogs concurrently
-REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast 
index
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -- no catalog toast 
index
 ERROR:  cannot reindex system catalogs concurrently
 REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
 ERROR:  cannot reindex system catalogs concurrently
@@ -2974,10 +2974,10 @@ ERROR:  must be owner of schema schema_to_reindex
 RESET ROLE;
 GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
 SET SESSION ROLE regress_reindexuser;
-REINDEX TABLE pg_toast.pg_toast_1260;
-ERROR:  permission denied for table pg_toast_1260
-REINDEX INDEX pg_toast.pg_toast_1260_index;
-ERROR:  permission denied for index pg_toast_1260_index
+REINDEX TABLE pg_toast.pg_toast_1262;
+ERROR:  permission denied for table pg_toast_1262
+REINDEX INDEX pg_toast.pg_toast_1262_index;
+ERROR:  permission denied for index pg_toast_1262_index
 -- Clean up
 RESET ROLE;
 REVOKE USAGE ON SCHEMA pg_toast FROM regress_reindexuser;
diff --git a/src/test/regress/expected/misc_sanity.out 
b/src/test/regress/expected/misc_sanity.out
index 45e7492877..b032a3f476 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -40,6 +40,8 @@ WHERE refclassid = 0 OR refobjid = 0 OR
 -- 2. pg_largeobject and pg_largeobject_metadata.  Large object catalogs
 -- and toast tables are mutually exclusive and large object data is handled
 -- as user data by pg_upgrade, which would cause failures.
+-- 3. pg_authid, since its toast table cannot be accessed when it would be
+-- needed, i.e., during authentication before we've selected a database.
 SELECT relname, attname, atttypid::regtype
 FROM pg_class c JOIN pg_attribute a ON c.oid = attrelid
 WHERE c.oid < 16384 AND
@@ -53,12 +55,13 @@ ORDER BY 1, 2;
  pg_attribute            | attfdwoptions | text[]
  pg_attribute            | attmissingval | anyarray
  pg_attribute            | attoptions    | text[]
+ pg_authid               | rolpassword   | text
  pg_class                | relacl        | aclitem[]
  pg_class                | reloptions    | text[]
  pg_class                | relpartbound  | pg_node_tree
  pg_largeobject          | data          | bytea
  pg_largeobject_metadata | lomacl        | aclitem[]
-(9 rows)
+(10 rows)
 
 -- system catalogs without primary keys
 --
diff --git a/src/test/regress/expected/tablespace.out 
b/src/test/regress/expected/tablespace.out
index 9aabb85349..dd535d41a3 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -51,13 +51,13 @@ ERROR:  cannot move system relation 
"pg_authid_rolname_index"
 REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
 ERROR:  cannot reindex system catalogs concurrently
 -- toast relations, fail
-REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
-ERROR:  cannot move system relation "pg_toast_1260_index"
-REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY 
pg_toast.pg_toast_1260_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index;
+ERROR:  cannot move system relation "pg_toast_1262_index"
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY 
pg_toast.pg_toast_1262_index;
 ERROR:  cannot reindex system catalogs concurrently
-REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
-ERROR:  cannot move system relation "pg_toast_1260_index"
-REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY 
pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262;
+ERROR:  cannot move system relation "pg_toast_1262_index"
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY 
pg_toast.pg_toast_1262;
 ERROR:  cannot reindex system catalogs concurrently
 -- system catalog, fail
 REINDEX (TABLESPACE pg_global) TABLE pg_authid;
diff --git a/src/test/regress/sql/create_index.sql 
b/src/test/regress/sql/create_index.sql
index e296891cab..145860b6ec 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -1127,8 +1127,8 @@ COMMIT;
 REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
 REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
 -- These are the toast table and index of pg_authid.
-REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
-REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast 
index
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1262; -- no catalog toast table
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -- no catalog toast 
index
 REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
 REINDEX (CONCURRENTLY) SYSTEM postgres; -- ditto
 REINDEX (CONCURRENTLY) SYSTEM;  -- ditto
@@ -1305,8 +1305,8 @@ REINDEX SCHEMA schema_to_reindex;
 RESET ROLE;
 GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
 SET SESSION ROLE regress_reindexuser;
-REINDEX TABLE pg_toast.pg_toast_1260;
-REINDEX INDEX pg_toast.pg_toast_1260_index;
+REINDEX TABLE pg_toast.pg_toast_1262;
+REINDEX INDEX pg_toast.pg_toast_1262_index;
 
 -- Clean up
 RESET ROLE;
diff --git a/src/test/regress/sql/misc_sanity.sql 
b/src/test/regress/sql/misc_sanity.sql
index 16f3a7c0c1..135793871b 100644
--- a/src/test/regress/sql/misc_sanity.sql
+++ b/src/test/regress/sql/misc_sanity.sql
@@ -43,6 +43,8 @@ WHERE refclassid = 0 OR refobjid = 0 OR
 -- 2. pg_largeobject and pg_largeobject_metadata.  Large object catalogs
 -- and toast tables are mutually exclusive and large object data is handled
 -- as user data by pg_upgrade, which would cause failures.
+-- 3. pg_authid, since its toast table cannot be accessed when it would be
+-- needed, i.e., during authentication before we've selected a database.
 
 SELECT relname, attname, atttypid::regtype
 FROM pg_class c JOIN pg_attribute a ON c.oid = attrelid
diff --git a/src/test/regress/sql/tablespace.sql 
b/src/test/regress/sql/tablespace.sql
index d274d9615e..c8b83788f0 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -40,10 +40,10 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY 
pg_am;
 REINDEX (TABLESPACE regress_tblspace) TABLE pg_authid;
 REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
 -- toast relations, fail
-REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
-REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY 
pg_toast.pg_toast_1260_index;
-REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
-REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY 
pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY 
pg_toast.pg_toast_1262_index;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262;
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY 
pg_toast.pg_toast_1262;
 -- system catalog, fail
 REINDEX (TABLESPACE pg_global) TABLE pg_authid;
 REINDEX (TABLESPACE pg_global) TABLE CONCURRENTLY pg_authid;
-- 
2.39.5 (Apple Git-154)

Reply via email to