Check for unbounded authentication exchanges in libpq.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Wed, 22 Feb 2023 19:27:38 +0000 (21:27 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Wed, 22 Feb 2023 19:27:38 +0000 (21:27 +0200)
A couple of code paths in CONNECTION_AWAITING_RESPONSE will eagerly read
bytes off a connection that should be closed. Don't let a misbehaving
server chew up client resources here; a v2 error can't be infinitely
long, and a v3 error should be bounded by its original message length.

For the existing error_return cases, I added some additional error
messages for symmetry with the new ones, and cleaned up some message
rot.

Author: Jacob Champion
Discussion: https://www.postgresql.org/message-id/8e729daf-7d71-6965-9687-8bc0630599b3%40timescale.com

src/interfaces/libpq/fe-connect.c

index 50b5df3490bfa77ef21e3a6aec56ca562b8f3995..8f80c35c89448f936a108b72fe00b31127f07365 100644 (file)
@@ -3214,28 +3214,46 @@ keep_going:                     /* We will come back to here until there is
 
                /*
                 * Try to validate message length before using it.
+                *
                 * Authentication requests can't be very large, although GSS
                 * auth requests may not be that small.  Same for
-                * NegotiateProtocolVersion.  Errors can be a
-                * little larger, but not huge.  If we see a large apparent
-                * length in an error, it means we're really talking to a
-                * pre-3.0-protocol server; cope.  (Before version 14, the
-                * server also used the old protocol for errors that happened
-                * before processing the startup packet.)
+                * NegotiateProtocolVersion.
+                *
+                * Errors can be a little larger, but not huge.  If we see a
+                * large apparent length in an error, it means we're really
+                * talking to a pre-3.0-protocol server; cope.  (Before
+                * version 14, the server also used the old protocol for
+                * errors that happened before processing the startup packet.)
                 */
-               if ((beresp == 'R' || beresp == 'v') && (msgLength < 8 || msgLength > 2000))
+               if (beresp == 'R' && (msgLength < 8 || msgLength > 2000))
                {
-                   libpq_append_conn_error(conn, "expected authentication request from server, but received %c",
-                                      beresp);
+                   libpq_append_conn_error(conn, "received invalid authentication request");
+                   goto error_return;
+               }
+               if (beresp == 'v' && (msgLength < 8 || msgLength > 2000))
+               {
+                   libpq_append_conn_error(conn, "received invalid protocol negotiation message");
                    goto error_return;
                }
 
-               if (beresp == 'E' && (msgLength < 8 || msgLength > 30000))
+#define MAX_ERRLEN 30000
+               if (beresp == 'E' && (msgLength < 8 || msgLength > MAX_ERRLEN))
                {
                    /* Handle error from a pre-3.0 server */
                    conn->inCursor = conn->inStart + 1; /* reread data */
                    if (pqGets_append(&conn->errorMessage, conn))
                    {
+                       /*
+                        * We may not have authenticated the server yet, so
+                        * don't let the buffer grow forever.
+                        */
+                       avail = conn->inEnd - conn->inCursor;
+                       if (avail > MAX_ERRLEN)
+                       {
+                           libpq_append_conn_error(conn, "received invalid error message");
+                           goto error_return;
+                       }
+
                        /* We'll come back when there is more data */
                        return PGRES_POLLING_READING;
                    }
@@ -3255,9 +3273,15 @@ keep_going:                      /* We will come back to here until there is
 
                    goto error_return;
                }
+#undef MAX_ERRLEN
 
                /*
                 * Can't process if message body isn't all here yet.
+                *
+                * After this check passes, any further EOF during parsing
+                * implies that the server sent a bad/truncated message.
+                * Reading more bytes won't help in that case, so don't return
+                * PGRES_POLLING_READING after this point.
                 */
                msgLength -= 4;
                avail = conn->inEnd - conn->inCursor;
@@ -3280,8 +3304,8 @@ keep_going:                       /* We will come back to here until there is
                {
                    if (pqGetErrorNotice3(conn, true))
                    {
-                       /* We'll come back when there is more data */
-                       return PGRES_POLLING_READING;
+                       libpq_append_conn_error(conn, "received invalid error message");
+                       goto error_return;
                    }
                    /* OK, we read the message; mark data consumed */
                    conn->inStart = conn->inCursor;
@@ -3357,6 +3381,7 @@ keep_going:                       /* We will come back to here until there is
                {
                    if (pqGetNegotiateProtocolVersion3(conn))
                    {
+                       libpq_append_conn_error(conn, "received invalid protocol negotiation message");
                        goto error_return;
                    }
                    /* OK, we read the message; mark data consumed */
@@ -3370,6 +3395,8 @@ keep_going:                       /* We will come back to here until there is
                /* Get the type of request. */
                if (pqGetInt((int *) &areq, 4, conn))
                {
+                   /* can't happen because we checked the length already */
+                   libpq_append_conn_error(conn, "received invalid authentication request");
                    goto error_return;
                }
                msgLength -= 4;