Skip to content

Commit ae09433

Browse files
hlinnakaCommitfest Bot
authored and
Commitfest Bot
committed
libpq: Add min/max_protocol_version connection options
All supported version of the PostgreSQL server send the NegotiateProtocolVersion message when an unsupported minor protocol version is requested by a client. But many other applications that implement the PostgreSQL protocol (connection poolers, or other databases) do not, and the same is true for PostgreSQL server versions older than 9.2. Connecting to such other applications thus fails if a client requests a protocol version different than 3.0. This patch adds a max_protocol_version connection option to libpq that specifies the protocol version that libpq should request from the server. Currently all allowed values result in the use of 3.0, but that will be changed in a future commit that bumps the protocol version. Even after that version bump the default will likely stay 3.0 for the time being. Once more of the ecosystem supports the NegotiateProtocolVersion message we might want to change the default to the latest minor version. We also add the similar min_protocol_version connection option, to allow a client to specify that connecting should fail if a lower protocol version is attempted by the server. This can be used to ensure certain protocol features are in used, which can be particularly useful if those features impact security. Author: Jelte Fennema-Nio <[email protected]> Reviewed-by: Robert Haas <[email protected]> (earlier versions) Discussion: https://www.postgresql.org/message-id/CAGECzQTfc_O%[email protected] Discussion: https://www.postgresql.org/message-id/CAGECzQRbAGqJnnJJxTdKewTsNOovUt4bsx3NFfofz3m2j-t7tA@mail.gmail.com
1 parent f30ca65 commit ae09433

File tree

4 files changed

+186
-2
lines changed

4 files changed

+186
-2
lines changed

doc/src/sgml/libpq.sgml

+68-1
Original file line numberDiff line numberDiff line change
@@ -2144,6 +2144,54 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
21442144
</listitem>
21452145
</varlistentry>
21462146

2147+
<varlistentry id="libpq-connect-min-protocol-version" xreflabel="min_protocol_version">
2148+
<term><literal>min_protocol_version</literal></term>
2149+
<listitem>
2150+
<para>
2151+
Specifies the minimum protocol version to allow for the connection.
2152+
The default is to allow any version of the
2153+
<productname>PostgreSQL</productname> protocol supported by libpq,
2154+
which currently means <literal>3.0</literal>. If the server
2155+
does not support at least this protocol version the connection will be
2156+
closed.
2157+
</para>
2158+
2159+
<para>
2160+
The current supported values are
2161+
<literal>3.0</literal>
2162+
and <literal>latest</literal>. The <literal>latest</literal> value is
2163+
equivalent to the latest protocol version that is supported by the used
2164+
libpq version, which currently is <literal>3.0</literal>.
2165+
</para>
2166+
</listitem>
2167+
</varlistentry>
2168+
2169+
<varlistentry id="libpq-connect-max-protocol-version" xreflabel="max_protocol_version">
2170+
<term><literal>max_protocol_version</literal></term>
2171+
<listitem>
2172+
<para>
2173+
Specifies the protocol version to request from the server.
2174+
The default is to use version <literal>3.0</literal> of the
2175+
<productname>PostgreSQL</productname> protocol, unless the connection
2176+
string specifies a feature that relies on a higher protocol version,
2177+
in which case the latest version supported by libpq is used. If the
2178+
server does not support the protocol version requested by the client,
2179+
the connection is automatically downgraded to a lower minor protocol
2180+
version that the server supports. After the connection attempt has
2181+
completed you can use <xref linkend="libpq-PQprotocolVersion"/> to
2182+
find out which exact protocol version was negotiated.
2183+
</para>
2184+
2185+
<para>
2186+
The current supported values are
2187+
<literal>3.0</literal>
2188+
and <literal>latest</literal>. The <literal>latest</literal> value is
2189+
equivalent to the latest protocol version that is supported by the
2190+
libpq version used, which is currently <literal>3.0</literal>.
2191+
</para>
2192+
</listitem>
2193+
</varlistentry>
2194+
21472195
<varlistentry id="libpq-connect-ssl-max-protocol-version" xreflabel="ssl_max_protocol_version">
21482196
<term><literal>ssl_max_protocol_version</literal></term>
21492197
<listitem>
@@ -2482,7 +2530,6 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
24822530
</para>
24832531
</listitem>
24842532
</varlistentry>
2485-
24862533
</variablelist>
24872534
</para>
24882535
</sect2>
@@ -9329,6 +9376,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
93299376
linkend="libpq-connect-load-balance-hosts"/> connection parameter.
93309377
</para>
93319378
</listitem>
9379+
9380+
<listitem>
9381+
<para>
9382+
<indexterm>
9383+
<primary><envar>PGMINPROTOCOLVERSION</envar></primary>
9384+
</indexterm>
9385+
<envar>PGMINPROTOCOLVERSION</envar> behaves the same as the <xref
9386+
linkend="libpq-connect-min-protocol-version"/> connection parameter.
9387+
</para>
9388+
</listitem>
9389+
9390+
<listitem>
9391+
<para>
9392+
<indexterm>
9393+
<primary><envar>PGMAXPROTOCOLVERSION</envar></primary>
9394+
</indexterm>
9395+
<envar>PGMAXPROTOCOLVERSION</envar> behaves the same as the <xref
9396+
linkend="libpq-connect-max-protocol-version"/> connection parameter.
9397+
</para>
9398+
</listitem>
93329399
</itemizedlist>
93339400
</para>
93349401

src/interfaces/libpq/fe-connect.c

+103-1
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,16 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
325325
"Require-Auth", "", 14, /* sizeof("scram-sha-256") == 14 */
326326
offsetof(struct pg_conn, require_auth)},
327327

328+
{"min_protocol_version", "PGMINPROTOCOLVERSION",
329+
NULL, NULL,
330+
"Min-Protocol-Version", "", 6, /* sizeof("latest") = 6 */
331+
offsetof(struct pg_conn, min_protocol_version)},
332+
333+
{"max_protocol_version", "PGMAXPROTOCOLVERSION",
334+
NULL, NULL,
335+
"Max-Protocol-Version", "", 6, /* sizeof("latest") = 6 */
336+
offsetof(struct pg_conn, max_protocol_version)},
337+
328338
{"ssl_min_protocol_version", "PGSSLMINPROTOCOLVERSION", "TLSv1.2", NULL,
329339
"SSL-Minimum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
330340
offsetof(struct pg_conn, ssl_min_protocol_version)},
@@ -483,6 +493,7 @@ static void pgpassfileWarning(PGconn *conn);
483493
static void default_threadlock(int acquire);
484494
static bool sslVerifyProtocolVersion(const char *version);
485495
static bool sslVerifyProtocolRange(const char *min, const char *max);
496+
static bool pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn, const char *context);
486497

487498

488499
/* global variable because fe-auth.c needs to access it */
@@ -2081,6 +2092,42 @@ pqConnectOptions2(PGconn *conn)
20812092
}
20822093
}
20832094

2095+
if (conn->min_protocol_version)
2096+
{
2097+
if (!pqParseProtocolVersion(conn->min_protocol_version, &conn->min_pversion, conn, "min_protocol_version"))
2098+
return false;
2099+
}
2100+
else
2101+
{
2102+
conn->min_pversion = PG_PROTOCOL_EARLIEST;
2103+
}
2104+
2105+
if (conn->max_protocol_version)
2106+
{
2107+
if (!pqParseProtocolVersion(conn->max_protocol_version, &conn->max_pversion, conn, "max_protocol_version"))
2108+
return false;
2109+
}
2110+
else
2111+
{
2112+
/*
2113+
* To not break connecting to older servers/poolers that do not yet
2114+
* support NegotiateProtocolVersion, default to the 3.0 protocol at
2115+
* least for a while longer. Except when min_protocol_version is set
2116+
* to something larger, then we might as well default to the latest.
2117+
*/
2118+
if (conn->min_pversion > PG_PROTOCOL(3, 0))
2119+
conn->max_pversion = PG_PROTOCOL_LATEST;
2120+
else
2121+
conn->max_pversion = PG_PROTOCOL(3, 0);
2122+
}
2123+
2124+
if (conn->min_pversion > conn->max_pversion)
2125+
{
2126+
conn->status = CONNECTION_BAD;
2127+
libpq_append_conn_error(conn, "min_protocol_version is greater than max_protocol_version");
2128+
return false;
2129+
}
2130+
20842131
/*
20852132
* Resolve special "auto" client_encoding from the locale
20862133
*/
@@ -3084,7 +3131,7 @@ PQconnectPoll(PGconn *conn)
30843131
* must persist across individual connection attempts, but we must
30853132
* reset them when we start to consider a new server.
30863133
*/
3087-
conn->pversion = PG_PROTOCOL(3, 0);
3134+
conn->pversion = conn->max_pversion;
30883135
conn->send_appname = true;
30893136
conn->failed_enc_methods = 0;
30903137
conn->current_enc_method = 0;
@@ -4103,6 +4150,7 @@ PQconnectPoll(PGconn *conn)
41034150

41044151
/* OK, we read the message; mark data consumed */
41054152
pqParseDone(conn, conn->inCursor);
4153+
41064154
goto keep_going;
41074155
}
41084156

@@ -8158,6 +8206,60 @@ pqParseIntParam(const char *value, int *result, PGconn *conn,
81588206
return false;
81598207
}
81608208

8209+
/*
8210+
* Parse and try to interpret "value" as a ProtocolVersion value, and if successful,
8211+
* store it in *result.
8212+
*/
8213+
static bool
8214+
pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn,
8215+
const char *context)
8216+
{
8217+
char *end;
8218+
int major;
8219+
int minor;
8220+
ProtocolVersion version;
8221+
8222+
if (strcmp(value, "latest") == 0)
8223+
{
8224+
*result = PG_PROTOCOL_LATEST;
8225+
return true;
8226+
}
8227+
8228+
major = strtol(value, &end, 10);
8229+
if (*end != '.')
8230+
{
8231+
conn->status = CONNECTION_BAD;
8232+
libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
8233+
context,
8234+
value);
8235+
return false;
8236+
}
8237+
8238+
minor = strtol(&end[1], &end, 10);
8239+
if (*end != '\0')
8240+
{
8241+
conn->status = CONNECTION_BAD;
8242+
libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
8243+
context,
8244+
value);
8245+
return false;
8246+
}
8247+
8248+
version = PG_PROTOCOL(major, minor);
8249+
if (version > PG_PROTOCOL_LATEST ||
8250+
version < PG_PROTOCOL_EARLIEST)
8251+
{
8252+
conn->status = CONNECTION_BAD;
8253+
libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
8254+
context,
8255+
value);
8256+
return false;
8257+
}
8258+
8259+
*result = version;
8260+
return true;
8261+
}
8262+
81618263
/*
81628264
* To keep the API consistent, the locking stubs are always provided, even
81638265
* if they are not required.

src/interfaces/libpq/fe-protocol3.c

+11
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,17 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
14441444
goto failure;
14451445
}
14461446

1447+
if (their_version < conn->min_pversion)
1448+
{
1449+
libpq_append_conn_error(conn, "server only supports protocol version %d.%d, but min_protocol_version was set to %d.%d",
1450+
PG_PROTOCOL_MAJOR(their_version),
1451+
PG_PROTOCOL_MINOR(their_version),
1452+
PG_PROTOCOL_MAJOR(conn->min_pversion),
1453+
PG_PROTOCOL_MINOR(conn->min_pversion));
1454+
1455+
goto failure;
1456+
}
1457+
14471458
/* the version is acceptable */
14481459
conn->pversion = their_version;
14491460

src/interfaces/libpq/libpq-int.h

+4
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@ struct pg_conn
417417
char *gsslib; /* What GSS library to use ("gssapi" or
418418
* "sspi") */
419419
char *gssdelegation; /* Try to delegate GSS credentials? (0 or 1) */
420+
char *min_protocol_version; /* minimum used protocol version */
421+
char *max_protocol_version; /* maximum used protocol version */
420422
char *ssl_min_protocol_version; /* minimum TLS protocol version */
421423
char *ssl_max_protocol_version; /* maximum TLS protocol version */
422424
char *target_session_attrs; /* desired session properties */
@@ -538,6 +540,8 @@ struct pg_conn
538540
void *scram_client_key_binary; /* binary SCRAM client key */
539541
size_t scram_server_key_len;
540542
void *scram_server_key_binary; /* binary SCRAM server key */
543+
ProtocolVersion min_pversion; /* protocol version to request */
544+
ProtocolVersion max_pversion; /* protocol version to request */
541545

542546
/* Miscellaneous stuff */
543547
int be_pid; /* PID of backend --- needed for cancels */

0 commit comments

Comments
 (0)