v5-0003-Implement-more-complicated-grease.patch
text/x-patch
Filename: v5-0003-Implement-more-complicated-grease.patch
Type: text/x-patch
Part: 2
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v5-0003
Subject: Implement more complicated grease
| File | + | − |
|---|---|---|
| src/include/libpq/pqcomm.h | 13 | 6 |
| src/interfaces/libpq/fe-connect.c | 52 | 8 |
| src/interfaces/libpq/fe-protocol3.c | 99 | 22 |
| src/interfaces/libpq/libpq-int.h | 3 | 0 |
From 3f074ffeaf9152c8a01350e2d5f69da0777c3f1c Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <postgres@jeltef.nl>
Date: Thu, 15 Jan 2026 09:01:52 +0100
Subject: [PATCH v5 3/5] Implement more complicated grease
This actually implements the randomization that is part of the GREASE
acronym. When using max_protocol_version=grease, the version will now be
chosen from the range 3.9990-3.9999. The names of the reserved protocol
extensions are randomized too, as well as how many are requested
exactly.
---
src/include/libpq/pqcomm.h | 19 +++--
src/interfaces/libpq/fe-connect.c | 60 ++++++++++++--
src/interfaces/libpq/fe-protocol3.c | 121 +++++++++++++++++++++++-----
src/interfaces/libpq/libpq-int.h | 3 +
4 files changed, 167 insertions(+), 36 deletions(-)
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index ca5127adfe5..d38789bc35b 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -105,13 +105,20 @@ 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 GREASE (Generate Random Extensions And Sustain Extensibility). This
- * helps ensure that servers properly implement protocol version negotiation
- * via NegotiateProtocolVersion. Version 3.9999 was chosen to be safely within
- * the valid range but unlikely to ever be implemented.
+ * GREASE (Generate Random Extensions And Sustain Extensibility) version range.
+ * These are intentionally unsupported protocol versions used to ensure servers
+ * properly implement protocol version negotiation via NegotiateProtocolVersion.
+ * The range 3.9990-3.9999 was chosen to be safely within the valid range but
+ * unlikely to ever be implemented. A random version from this range is selected
+ * at connection time to prevent implementations from accidentally depending on
+ * specific GREASE values.
*/
-#define PG_PROTOCOL_GREASE PG_PROTOCOL(3,9999)
+#define PG_PROTOCOL_GREASE_MIN PG_PROTOCOL(3,9990)
+#define PG_PROTOCOL_GREASE_MAX PG_PROTOCOL(3,9999)
+
+/* Check if a protocol version is in the GREASE range */
+#define PG_PROTOCOL_IS_GREASE(v) \
+ ((v) >= PG_PROTOCOL_GREASE_MIN && (v) <= PG_PROTOCOL_GREASE_MAX)
/*
* A client can send a cancel-current-operation request to the postmaster.
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9fcf094a36f..fdc05cd3243 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2142,12 +2142,45 @@ pqConnectOptions2(PGconn *conn)
else
{
/*
- * Default to the GREASE protocol version to test that servers
- * properly implement NegotiateProtocolVersion. The server will
- * automatically downgrade to a supported version. This will be
- * changed to a supported version before the PG19 release.
+ * Default to a GREASE protocol version to test that servers properly
+ * implement NegotiateProtocolVersion. The server will automatically
+ * downgrade to a supported version. This will be changed to a
+ * supported version before the PG19 release.
*/
- conn->max_pversion = PG_PROTOCOL_GREASE;
+ conn->max_pversion = PG_PROTOCOL_GREASE_MAX;
+ }
+
+ /*
+ * If using GREASE, randomize the version, the number of GREASE parameters
+ * (0-5), and each parameter's ID. This prevents implementations from
+ * accidentally depending on specific GREASE values.
+ */
+ if (PG_PROTOCOL_IS_GREASE(conn->max_pversion))
+ {
+ int grease_range;
+
+ libpq_prng_init(conn);
+ grease_range = PG_PROTOCOL_MINOR(PG_PROTOCOL_GREASE_MAX) -
+ PG_PROTOCOL_MINOR(PG_PROTOCOL_GREASE_MIN);
+ conn->max_pversion = PG_PROTOCOL(3,
+ PG_PROTOCOL_MINOR(PG_PROTOCOL_GREASE_MIN) +
+ pg_prng_uint64_range(&conn->prng_state, 0, grease_range));
+ conn->ngrease_params = pg_prng_uint64_range(&conn->prng_state, 0, 5);
+
+ /* Initialize prefix indices and shuffle them (Fisher-Yates) */
+ for (int g = 0; g < 5; g++)
+ conn->grease_prefix[g] = g;
+ for (int g = 4; g > 0; g--)
+ {
+ int j = pg_prng_uint64_range(&conn->prng_state, 0, g);
+ int tmp = conn->grease_prefix[g];
+
+ conn->grease_prefix[g] = conn->grease_prefix[j];
+ conn->grease_prefix[j] = tmp;
+ }
+
+ for (int g = 0; g < conn->ngrease_params; g++)
+ conn->grease_params[g] = (uint16) pg_prng_uint64_range(&conn->prng_state, 0, 0xFFFF);
}
if (conn->min_pversion > conn->max_pversion)
@@ -4383,10 +4416,11 @@ keep_going: /* We will come back to here until there is
goto error_return;
}
- if (conn->max_pversion == PG_PROTOCOL_GREASE &&
- conn->pversion == PG_PROTOCOL_GREASE)
+ if (PG_PROTOCOL_IS_GREASE(conn->max_pversion) &&
+ PG_PROTOCOL_IS_GREASE(conn->pversion))
{
- libpq_append_conn_error(conn, "server incorrectly accepted reserved GREASE protocol version 3.9999 without negotiation");
+ libpq_append_conn_error(conn, "server incorrectly accepted reserved GREASE protocol version 3.%d without negotiation",
+ PG_PROTOCOL_MINOR(conn->pversion));
goto error_return;
}
@@ -8341,6 +8375,16 @@ pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn,
return true;
}
+ if (strcmp(value, "grease") == 0)
+ {
+ /*
+ * Use a placeholder; the actual random version is selected later when
+ * the PRNG is initialized.
+ */
+ *result = PG_PROTOCOL_GREASE_MAX;
+ return true;
+ }
+
libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
context, value);
return false;
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 4f78b88b3a8..c0e0142798a 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -31,6 +31,20 @@
#include "mb/pg_wchar.h"
#include "port/pg_bswap.h"
+/*
+ * GREASE parameter name prefixes. Each GREASE parameter uses a different
+ * prefix to prevent implementations from pattern-matching on a single prefix.
+ * The full parameter name is the prefix followed by a random 4-digit hex
+ * number, e.g. "_pq_.protocol_grease_a1b2".
+ */
+static const char *const grease_prefixes[5] = {
+ "_pq_.test_protocol_negotiation_",
+ "_pq_.negotiation_test_",
+ "_pq_.protocol_grease_",
+ "_pq_.grease_the_server_",
+ "_pq_.always_unknown_extension_"
+};
+
/*
* This macro lists the backend message types that could be "long" (more
* than a couple of kilobytes).
@@ -1444,8 +1458,9 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
{
int their_version;
int num;
- bool found_test_protocol_negotiation;
- bool expect_test_protocol_negotiation;
+ int ngrease_found;
+ bool grease_found[5] = {false};
+ bool expect_grease;
if (pqGetInt(&their_version, 4, conn) != 0)
goto eof;
@@ -1473,10 +1488,24 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
goto failure;
}
- /* The GREASE protocol version is intentionally unsupported and reserved */
- if (their_version == PG_PROTOCOL_GREASE)
+ /* GREASE protocol versions are intentionally unsupported and reserved */
+ if (PG_PROTOCOL_IS_GREASE(their_version))
+ {
+ libpq_append_conn_error(conn, "received invalid protocol negotiation message: server claimed to support reserved GREASE protocol version 3.%d",
+ PG_PROTOCOL_MINOR(their_version));
+ goto failure;
+ }
+
+ /*
+ * If the server responds with a version newer than what this libpq
+ * supports, disconnect. This can happen if we sent a GREASE version and a
+ * future server legitimately supports a newer minor version than us.
+ */
+ if (their_version > PG_PROTOCOL_LATEST)
{
- libpq_append_conn_error(conn, "received invalid protocol negotiation message: server claimed to support reserved GREASE protocol version 3.9999");
+ libpq_append_conn_error(conn, "server proposed protocol version 3.%d, but libpq only supports up to 3.%d",
+ PG_PROTOCOL_MINOR(their_version),
+ PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST));
goto failure;
}
@@ -1509,13 +1538,16 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
/*
* Check that all expected unsupported parameters are reported by the
- * server.
+ * server. In GREASE mode, we expect all our GREASE parameters to be
+ * reported as unsupported.
*/
- found_test_protocol_negotiation = false;
- expect_test_protocol_negotiation = (conn->max_pversion == PG_PROTOCOL_GREASE);
+ ngrease_found = 0;
+ expect_grease = PG_PROTOCOL_IS_GREASE(conn->max_pversion);
for (int i = 0; i < num; i++)
{
+ bool matched = false;
+
if (pqGets(&conn->workBuffer, conn))
{
goto eof;
@@ -1526,12 +1558,31 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
goto failure;
}
- /* Check if this is the expected test parameter */
- if (expect_test_protocol_negotiation && strcmp(conn->workBuffer.data, "_pq_.test_protocol_negotiation") == 0)
+ /* Check if this matches any of our expected GREASE parameters */
+ if (expect_grease)
{
- found_test_protocol_negotiation = true;
+ for (int j = 0; j < conn->ngrease_params; j++)
+ {
+ char expected_opt[32];
+
+ snprintf(expected_opt, sizeof(expected_opt), "%s%04x",
+ grease_prefixes[conn->grease_prefix[j]], conn->grease_params[j]);
+ if (strcmp(conn->workBuffer.data, expected_opt) == 0)
+ {
+ if (grease_found[j])
+ {
+ libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported duplicate unsupported parameter (\"%s\")", conn->workBuffer.data);
+ goto failure;
+ }
+ grease_found[j] = true;
+ ngrease_found++;
+ matched = true;
+ break;
+ }
+ }
}
- else
+
+ if (!matched)
{
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported an unsupported parameter that was not requested (\"%s\")", conn->workBuffer.data);
goto failure;
@@ -1539,13 +1590,28 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
}
/*
- * If we requested the GREASE protocol version, the server must report
- * _pq_.test_protocol_negotiation as unsupported. This ensures
- * comprehensive NegotiateProtocolVersion implementation.
+ * If we requested a GREASE protocol version, the server must report all
+ * our GREASE options as unsupported. This ensures comprehensive
+ * NegotiateProtocolVersion implementation.
*/
- if (expect_test_protocol_negotiation && !found_test_protocol_negotiation)
+ if (expect_grease && ngrease_found != conn->ngrease_params)
{
- libpq_append_conn_error(conn, "server did not report the unsupported `_pq_.test_protocol_negotiation` parameter in its protocol negotiation message");
+ PQExpBufferData missing;
+
+ initPQExpBuffer(&missing);
+ for (int j = 0; j < conn->ngrease_params; j++)
+ {
+ if (!grease_found[j])
+ {
+ if (missing.len > 0)
+ appendPQExpBufferStr(&missing, ", ");
+ appendPQExpBuffer(&missing, "%s%04x",
+ grease_prefixes[conn->grease_prefix[j]], conn->grease_params[j]);
+ }
+ }
+ libpq_append_conn_error(conn, "server did not report unsupported GREASE parameter(s): %s",
+ missing.data);
+ termPQExpBuffer(&missing);
goto failure;
}
@@ -2497,12 +2563,23 @@ build_startup_packet(const PGconn *conn, char *packet,
ADD_STARTUP_OPTION("client_encoding", conn->client_encoding_initial);
/*
- * Add the test protocol negotiation option if we're using the GREASE
- * protocol version. This tests that servers properly report unsupported
- * protocol options in their NegotiateProtocolVersion response.
+ * Add GREASE protocol options if we're using a GREASE protocol version.
+ * This tests that servers properly report unsupported protocol options in
+ * their NegotiateProtocolVersion response. Each option name includes a
+ * random suffix to prevent implementations from depending on specific
+ * GREASE values. The number of options (0-5) is also randomized.
*/
- if (conn->pversion == PG_PROTOCOL_GREASE)
- ADD_STARTUP_OPTION("_pq_.test_protocol_negotiation", "");
+ if (PG_PROTOCOL_IS_GREASE(conn->pversion))
+ {
+ for (int i = 0; i < conn->ngrease_params; i++)
+ {
+ char grease_opt[32];
+
+ snprintf(grease_opt, sizeof(grease_opt), "%s%04x",
+ grease_prefixes[conn->grease_prefix[i]], conn->grease_params[i]);
+ ADD_STARTUP_OPTION(grease_opt, "");
+ }
+ }
/* Add any environment-driven GUC settings needed */
for (next_eo = options; next_eo->envName; next_eo++)
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index fb6a7cbf15d..a599d57e325 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -547,6 +547,9 @@ struct pg_conn
uint8 *scram_server_key_binary; /* binary SCRAM server key */
ProtocolVersion min_pversion; /* protocol version to request */
ProtocolVersion max_pversion; /* protocol version to request */
+ int ngrease_params; /* number of GREASE parameters (0-5) */
+ int grease_prefix[5]; /* which prefix to use for each param */
+ uint16 grease_params[5]; /* random IDs for GREASE parameter names */
/* Miscellaneous stuff */
int be_pid; /* PID of backend --- needed for cancels */
--
2.52.0