From d8d7c5dc8f74506d35c7e8242be997fd5cf388eb Mon Sep 17 00:00:00 2001 From: Jacob Champion Date: Fri, 6 Feb 2026 10:31:45 -0800 Subject: [PATCH] libpq: Prepare for protocol grease during 19beta The main reason that libpq doesn't request protocol version 3.2 by default is because other proxy/server implementations don't implement the negotiation. This is a bit of a chicken-and-egg problem: We don't bump the default version that libpq requests, but other implementations may not be incentivized to implement version negotiation if their users never run into issues. One established practice to combat this is to flip Postel's Law on its head, by sending parameters that the server cannot possibly support. If the server fails the handshake instead of correctly negotiating, then the problem is surfaced naturally. If the server instead claims to support the bogus parameters, then we fail the connection to make the lie obvious. This is called "grease" (or "greasing"), after the GREASE mechanism in TLS that popularized the concept: https://www.rfc-editor.org/rfc/rfc8701.html This patch reserves 3.9999 as an explicitly unsupported protocol version number and `_pq_.test_protocol_negotiation` as an explicitly unsupported protocol extension. A later commit will send these by default in order to stress-test the ecosystem during the beta period; that commit will then be reverted before 19 RC1, so that we can decide what to do with whatever data has been gathered. The _pq_.test_protocol_negotiation change here is intentionally docs- only: after its implementation is reverted, the parameter should remain reserved. Extracted/adapted from a patch by Jelte Fennema-Nio. Author: Jelte Fennema-Nio Co-authored-by: Jacob Champion Discussion: https://postgr.es/m/DDPR5BPWH1RJ.1LWAK6QAURVAY%40jeltef.nl --- doc/src/sgml/protocol.sgml | 23 +++++++++++++++++++++++ src/include/libpq/pqcomm.h | 10 ++++++++++ src/interfaces/libpq/fe-protocol3.c | 14 +++++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 36fd327d4b9..89ac680efd5 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -270,6 +270,18 @@ + + 3.9999 + - + Reserved for protocol greasing. libpq may use this version, which + is higher than any minor version the project ever expects to use, to + test that servers and middleware properly implement protocol version + negotiation. Servers must not add special-case + logic for this version; they should simply compare it to their latest + supported version (which will always be smaller) and downgrade via a + NegotiateProtocolVersion message. + + 3.1 - @@ -353,6 +365,17 @@ otherwise continue the connection. + + + _pq_.test_protocol_negotiation + Reserved for protocol greasing. libpq may send this extension to + test that servers and middleware properly implement protocol extension + negotiation. Servers must not add special-case + logic for this parameter; they should simply send the list of all + unsupported options (including this one) via a NegotiateProtocolVersion + message. + + diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index 1bbe5b9ee45..a29c9c94d79 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -104,6 +104,16 @@ is_unixsock_path(const char *path) */ #define PG_PROTOCOL_RESERVED_31 PG_PROTOCOL(3,1) +/* + * PG_PROTOCOL_GREASE is an intentionally unsupported protocol version used + * for "greasing" (the practice of sending valid, but extraneous or otherwise + * unusual, messages to keep peer implementations honest). This helps ensure + * that servers properly implement protocol version negotiation. Version 3.9999 + * was chosen since it is safely within the valid range, it is representable + * via PQfullProtocolVersion, and it is unlikely to ever be needed in practice. + */ +#define PG_PROTOCOL_GREASE PG_PROTOCOL(3,9999) + /* * A client can send a cancel-current-operation request to the postmaster. * This is uglier than sending it directly to the client's backend, but it diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 103428033ef..90bbb2eba1f 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -1451,7 +1451,19 @@ pqGetNegotiateProtocolVersion3(PGconn *conn) if (pqGetInt(&num, 4, conn) != 0) goto eof; - /* Check the protocol version */ + /* + * Check the protocol version. + * + * PG_PROTOCOL_GREASE is intentionally unsupported and reserved. It's + * higher than any real version, so check for that first, to get the most + * specific error message. Then check the upper and lower bounds. + */ + if (their_version == PG_PROTOCOL_GREASE) + { + libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requested \"grease\" protocol version 3.9999"); + goto failure; + } + if (their_version > conn->pversion) { libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requested downgrade to a higher-numbered version"); -- 2.47.3