Re: Maximum password length - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Maximum password length
Date
Msg-id 12825.1598921693@sss.pgh.pa.us
Whole thread Raw
In response to Re: Maximum password length  (Alexander Kukushkin <cyberdemn@gmail.com>)
Responses Re: Maximum password length
Re: Maximum password length
List pgsql-hackers
Alexander Kukushkin <cyberdemn@gmail.com> writes:
> Self-containing tokens, for example JWT, could be easily longer than 100 bytes.
> We at Zalando are using such tokens and the usual size of JWT token is
> 600-700 bytes.

> It is not possible to "paste" such token into psql password prompt,
> because the input is truncated by 100 bytes.
> It is not possible to put it into ".pgpass" either, because it assumes
> that line could not be longer than 320 bytes (64*5)

> At the moment there are only two ways to use such tokens as a password:
> 1. export PGPASSWORD=very_long.token
> 2. specify the token(password) in the connection url

This thread seems to have fallen off the radar, but I got interested again
now that we have a report of somebody else trying to use an 800-or-so-byte
password [1], so I looked over Nathan's patches in some detail.

I concur with Stephen's position that there ought to be just one upper
limit not several.  At the same time, it's not clear to me that the
password packet's length is closely related to the plaintext password
limit when we're using SCRAM --- is there any case where the verifier
string could exceed a few hundred bytes?

Also, I'm not exactly convinced that we need to document the limit in the
SGML docs, and I'm definitely down on repeating that info in 16 different
places.  If we make the limit high enough to not be a problem, nobody is
going to care exactly what it is.

Therefore, I propose setting this up with a #define symbol in
pg_config_manual.h and leaving it at that.  Giving documentation in
pg_config_manual.h seems sufficient to me.  Attached is a revised
version of Nathan's patches that does it like that.

I set the proposed limit at 1024 bytes, but given that we now know
of use-cases needing up to 800 bytes, maybe there should be a little
more headroom?  I don't want to make it enormous, though, seeing that
we're allocating static buffers of that size.

Note this patch is intended to be applied over my patch at [2],
since it modifies the test case added there.

            regards, tom lane

[1] https://www.postgresql.org/message-id/flat/CAOhmDze1nqG2vfegpSsTFCgaiFRsqgjO6yLsbmhroz2zGmJHog%40mail.gmail.com
[2] https://www.postgresql.org/message-id/4187382.1598909041%40sss.pgh.pa.us

diff --git a/contrib/oid2name/oid2name.c b/contrib/oid2name/oid2name.c
index 91b7958c48..c83355c6e6 100644
--- a/contrib/oid2name/oid2name.c
+++ b/contrib/oid2name/oid2name.c
@@ -294,7 +294,7 @@ sql_conn(struct options *my_opts)
 {
     PGconn       *conn;
     bool        have_password = false;
-    char        password[100];
+    char        password[MAX_PG_PASSWORD_LEN + 1];
     bool        new_pass;
     PGresult   *res;

diff --git a/contrib/vacuumlo/vacuumlo.c b/contrib/vacuumlo/vacuumlo.c
index e4019fafaa..74d72ca40a 100644
--- a/contrib/vacuumlo/vacuumlo.c
+++ b/contrib/vacuumlo/vacuumlo.c
@@ -70,7 +70,7 @@ vacuumlo(const char *database, const struct _param *param)
     bool        new_pass;
     bool        success = true;
     static bool have_password = false;
-    static char password[100];
+    static char password[MAX_PG_PASSWORD_LEN + 1];

     /* Note: password can be carried over from a previous call */
     if (param->pg_prompt == TRI_YES && !have_password)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..af3ceb945a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -697,8 +697,12 @@ recv_password_packet(Port *port)
             return NULL;        /* EOF */
     }

+    /*
+     * Receive the password.  Allow the password proper (not counting the null
+     * terminator or message length word) to be up to MAX_PG_PASSWORD_LEN.
+     */
     initStringInfo(&buf);
-    if (pq_getmessage(&buf, 1000))    /* receive password */
+    if (pq_getmessage(&buf, MAX_PG_PASSWORD_LEN + 1 + 4))
     {
         /* EOF - pq_getmessage already logged a suitable message */
         pfree(buf.data);
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 786672b1b6..a7ac748dd0 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1481,8 +1481,8 @@ setup_auth(FILE *cmdfd)
 static void
 get_su_pwd(void)
 {
-    char        pwd1[100];
-    char        pwd2[100];
+    char        pwd1[MAX_PG_PASSWORD_LEN + 1];
+    char        pwd2[MAX_PG_PASSWORD_LEN + 1];

     if (pwprompt)
     {
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index c08003e7f2..27423f460c 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -50,7 +50,7 @@ char       *dbport = NULL;
 char       *dbname = NULL;
 int            dbgetpassword = 0;    /* 0=auto, -1=never, 1=always */
 static bool have_password = false;
-static char password[100];
+static char password[MAX_PG_PASSWORD_LEN + 1];
 PGconn       *conn = NULL;

 /*
diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c
index 94af11b80a..9b2d8bbe50 100644
--- a/src/bin/pg_dump/pg_backup_db.c
+++ b/src/bin/pg_dump/pg_backup_db.c
@@ -122,7 +122,7 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
     const char *newdb;
     const char *newuser;
     char       *password;
-    char        passbuf[100];
+    char        passbuf[MAX_PG_PASSWORD_LEN + 1];
     bool        new_pass;

     if (!reqdb)
@@ -242,7 +242,7 @@ ConnectDatabase(Archive *AHX,
 {
     ArchiveHandle *AH = (ArchiveHandle *) AHX;
     char       *password;
-    char        passbuf[100];
+    char        passbuf[MAX_PG_PASSWORD_LEN + 1];
     bool        new_pass;

     if (AH->connection)
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 2c82b39af0..463996d8df 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1644,7 +1644,7 @@ connectDatabase(const char *dbname, const char *connection_string,
     const char **values = NULL;
     PQconninfoOption *conn_opts = NULL;
     static bool have_password = false;
-    static char password[100];
+    static char password[MAX_PG_PASSWORD_LEN + 1];

     if (prompt_password == TRI_YES && !have_password)
     {
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 08a5947a9e..dbe11331ab 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1175,7 +1175,7 @@ doConnect(void)
     PGconn       *conn;
     bool        new_pass;
     static bool have_password = false;
-    static char password[100];
+    static char password[MAX_PG_PASSWORD_LEN + 1];

     /*
      * Start the connection.  Loop until we have a password if requested by
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 9902a4a2ba..2c6880d02e 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -1964,8 +1964,8 @@ exec_command_password(PsqlScanState scan_state, bool active_branch)
     {
         char       *opt0 = psql_scan_slash_option(scan_state,
                                                   OT_SQLID, NULL, true);
-        char        pw1[100];
-        char        pw2[100];
+        char        pw1[MAX_PG_PASSWORD_LEN + 1];
+        char        pw2[MAX_PG_PASSWORD_LEN + 1];

         simple_prompt("Enter new password: ", pw1, sizeof(pw1), false);
         simple_prompt("Enter it again: ", pw2, sizeof(pw2), false);
@@ -2982,7 +2982,7 @@ copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf)
 static char *
 prompt_for_password(const char *username)
 {
-    char        buf[100];
+    char        buf[MAX_PG_PASSWORD_LEN + 1];

     if (username == NULL || username[0] == '\0')
         simple_prompt("Password: ", buf, sizeof(buf), false);
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 3302bd4dd3..127919ee10 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -120,7 +120,7 @@ main(int argc, char *argv[])
     struct adhoc_opts options;
     int            successResult;
     bool        have_password = false;
-    char        password[100];
+    char        password[MAX_PG_PASSWORD_LEN + 1];
     bool        new_pass;

     pg_logging_init(argv[0]);
diff --git a/src/bin/scripts/common.c b/src/bin/scripts/common.c
index 420d0d11a5..447a61d201 100644
--- a/src/bin/scripts/common.c
+++ b/src/bin/scripts/common.c
@@ -69,7 +69,7 @@ connectDatabase(const char *dbname, const char *pghost,
     PGconn       *conn;
     bool        new_pass;
     static bool have_password = false;
-    static char password[100];
+    static char password[MAX_PG_PASSWORD_LEN + 1];

     if (!allow_password_reuse)
         have_password = false;
diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c
index 9ced079ac7..70c346271e 100644
--- a/src/bin/scripts/createuser.c
+++ b/src/bin/scripts/createuser.c
@@ -64,7 +64,7 @@ main(int argc, char *argv[])
     bool        pwprompt = false;
     char       *newpassword = NULL;
     char        newuser_buf[128];
-    char        newpassword_buf[100];
+    char        newpassword_buf[MAX_PG_PASSWORD_LEN + 1];

     /* Tri-valued variables.  */
     enum trivalue createdb = TRI_DEFAULT,
@@ -206,7 +206,7 @@ main(int argc, char *argv[])

     if (pwprompt)
     {
-        char        pw2[100];
+        char        pw2[MAX_PG_PASSWORD_LEN + 1];

         simple_prompt("Enter password for new role: ",
                       newpassword_buf, sizeof(newpassword_buf), false);
diff --git a/src/common/saslprep.c b/src/common/saslprep.c
index 2dedf6b0fb..6b4967efc2 100644
--- a/src/common/saslprep.c
+++ b/src/common/saslprep.c
@@ -29,12 +29,6 @@
 #include "common/unicode_norm.h"
 #include "mb/pg_wchar.h"

-/*
- * Limit on how large password's we will try to process.  A password
- * larger than this will be treated the same as out-of-memory.
- */
-#define MAX_PASSWORD_LENGTH        1024
-
 /*
  * In backend, we will use palloc/pfree.  In frontend, use malloc, and
  * return SASLPREP_OOM on out-of-memory.
@@ -1079,7 +1073,7 @@ pg_saslprep(const char *input, char **output)
     *output = NULL;

     /* Check that the password isn't stupendously long */
-    if (strlen(input) > MAX_PASSWORD_LENGTH)
+    if (strlen(input) > MAX_PG_PASSWORD_LEN)
     {
 #ifndef FRONTEND
         ereport(ERROR,
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..d2b9255a60 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -97,6 +97,18 @@
  */
 #define MAXPGPATH        1024

+/*
+ * MAX_PG_PASSWORD_LEN: maximum length of a password string.
+ *
+ * This is applied indiscriminately to both plaintext and hashed passwords,
+ * so it must be large enough to accommodate all hashed-password formats.
+ * There are known use-cases in which the password is a machine-generated
+ * security token ranging up to around 800 bytes.
+ * Note that some auth methods have a tighter limit, for example RADIUS
+ * auth is limited to 128-byte passwords according to the RADIUS standard.
+ */
+#define MAX_PG_PASSWORD_LEN        1024
+
 /*
  * PG_SOMAXCONN: maximum accept-queue length limit passed to
  * listen(2).  You'd think we should use SOMAXCONN from
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 1b4323fe2a..6a95814784 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -60,13 +60,14 @@ $node->start;

 # Create 3 roles with different password methods for each one. The same
 # password is used for all of them.
+my $password = 'a' . 'b' x 1000 . 'c';
 $node->safe_psql('postgres',
-    "SET password_encryption='scram-sha-256'; CREATE ROLE scram_role LOGIN PASSWORD 'pass';"
+    "SET password_encryption='scram-sha-256'; CREATE ROLE scram_role LOGIN PASSWORD '$password';"
 );
 $node->safe_psql('postgres',
-    "SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
+    "SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD '$password';"
 );
-$ENV{"PGPASSWORD"} = 'pass';
+$ENV{"PGPASSWORD"} = $password;

 # For "trust" method, all users should be able to connect.
 reset_pg_hba($node, 'trust');
@@ -108,7 +109,7 @@ $ENV{"PGPASSFILE"} = $pgpassfile;

 append_to_file($pgpassfile, qq!
 # This very long comment is just here to exercise handling of long lines in the file. This very long comment is just
hereto exercise handling of long lines in the file. This very long comment is just here to exercise handling of long
linesin the file. This very long comment is just here to exercise handling of long lines in the file. This very long
commentis just here to exercise handling of long lines in the file. 
-*:*:postgres:scram_role:pass:this is not part of the password.
+*:*:postgres:scram_role:$password:this is not part of the password.
 !);
 chmod 0600, $pgpassfile or die;

@@ -116,8 +117,11 @@ reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password from pgpass', 0);
 test_role($node, 'md5_role',   'password from pgpass', 2);

+my $modpassword = $password;
+$modpassword =~ s/b/\\b/g;
+
 append_to_file($pgpassfile, qq!
-*:*:*:md5_role:p\\ass
+*:*:*:md5_role:$modpassword
 !);

 test_role($node, 'md5_role',   'password from pgpass', 0);

pgsql-hackers by date:

Previous
From: Kyotaro Horiguchi
Date:
Subject: Re: Append with naive multiplexing of FDWs
Next
From: Fujii Masao
Date:
Subject: Re: Remove line length restriction in passwordFromFile()