postgres.git / summary / log / commit / refs

commit    d8d7c5dc8f74506d35c7e8242be997fd5cf388eb
Author:   Jacob Champion <jchampion@postgresql.org>
Date:     Fri Feb 06 18:31:45 2026 +0000

    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 <postgres@jeltef.nl>
    Co-authored-by: Jacob Champion <jacob.champion@enterprisedb.com>
    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 @@ </thead> <tbody> + <row> + <entry>3.9999</entry> + <entry>-</entry> + <entry>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 <emphasis>must not</emphasis> 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. + </entry> + </row> <row> <entry>3.1</entry> <entry>-</entry> @@ -353,6 +365,17 @@ otherwise continue the connection. </entry> </row> + + <row> + <entry><literal>_pq_.test_protocol_negotiation</literal></entry> + <entry>Reserved for protocol greasing. libpq may send this extension to + test that servers and middleware properly implement protocol extension + negotiation. Servers <emphasis>must not</emphasis> add special-case + logic for this parameter; they should simply send the list of all + unsupported options (including this one) via a NegotiateProtocolVersion + message. + </entry> + </row> </tbody> </tgroup> </table> 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"); [parent: e3d37853ecd5]