Skip to content

Commit f30ca65

Browse files
hlinnakaCommitfest Bot
authored and
Commitfest Bot
committed
libpq: Handle NegotiateProtocolVersion message differently
Previously libpq would always error when the server returns a NegotiateProtocolVersion message. This was fine because libpq only supported a single protocol version and did not support any protocol parameters. But we now that we're discussing protocol changes we need to change this behaviour, and introduce a fallback mechanism when connecting to an older server. This patch modifies the client side checks to allow a range of supported protocol versions, instead of only allowing the exact version that was requested. Currently this "range" only contains the 3.0 version, but in a future commit we'll change this. In addition it changes the errors for protocol to say that they error because we did not request any parameters, not because the server did not support some of them. In a future commit more infrastructure for protocol parameters will be added, so that these checks can also succeed when receiving unsupported parameters back. Note that at the moment this change does not have any behavioural effect, because libpq will only request version 3.0 and will never send protocol parameters. Which means that the client never receives a NegotiateProtocolVersion message from the server. Author: Jelte Fennema-Nio <[email protected]> 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 52f21c9 commit f30ca65

File tree

3 files changed

+70
-34
lines changed

3 files changed

+70
-34
lines changed

src/interfaces/libpq/fe-connect.c

+11-1
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,7 @@ pqDropServerData(PGconn *conn)
667667

668668
/* Reset assorted other per-connection state */
669669
conn->last_sqlstate[0] = '\0';
670+
conn->negotiate_pversion_received = false;
670671
conn->auth_req_received = false;
671672
conn->client_finished_auth = false;
672673
conn->password_needed = false;
@@ -4084,16 +4085,25 @@ PQconnectPoll(PGconn *conn)
40844085

40854086
CONNECTION_FAILED();
40864087
}
4088+
/* Handle NegotiateProtocolVersion */
40874089
else if (beresp == PqMsg_NegotiateProtocolVersion)
40884090
{
4091+
if (conn->negotiate_pversion_received)
4092+
{
4093+
libpq_append_conn_error(conn, "received duplicate protocol negotiation message");
4094+
goto error_return;
4095+
}
4096+
conn->negotiate_pversion_received = true;
4097+
40894098
if (pqGetNegotiateProtocolVersion3(conn))
40904099
{
40914100
libpq_append_conn_error(conn, "received invalid protocol negotiation message");
40924101
goto error_return;
40934102
}
4103+
40944104
/* OK, we read the message; mark data consumed */
40954105
pqParseDone(conn, conn->inCursor);
4096-
goto error_return;
4106+
goto keep_going;
40974107
}
40984108

40994109
/* It is an authentication request. */

src/interfaces/libpq/fe-protocol3.c

+58-33
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ getAnotherTuple(PGconn *conn, int msgLength)
870870
/*
871871
* Attempt to read an Error or Notice response message.
872872
* This is possible in several places, so we break it out as a subroutine.
873+
*
873874
* Entry: 'E' or 'N' message type and length have already been consumed.
874875
* Exit: returns 0 if successfully consumed message.
875876
* returns EOF if not enough data.
@@ -1399,64 +1400,87 @@ reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding)
13991400

14001401

14011402
/*
1402-
* Attempt to read a NegotiateProtocolVersion message.
1403+
* Attempt to read a NegotiateProtocolVersion message. Sets conn->pversion
1404+
* to the version that's negotiated by the server.
1405+
*
14031406
* Entry: 'v' message type and length have already been consumed.
14041407
* Exit: returns 0 if successfully consumed message.
1405-
* returns EOF if not enough data.
1408+
* returns 1 on failure. The error message is filled in.
14061409
*/
14071410
int
14081411
pqGetNegotiateProtocolVersion3(PGconn *conn)
14091412
{
1410-
int tmp;
1411-
ProtocolVersion their_version;
1413+
int their_version;
14121414
int num;
1413-
PQExpBufferData buf;
14141415

1415-
if (pqGetInt(&tmp, 4, conn) != 0)
1416-
return EOF;
1417-
their_version = tmp;
1416+
if (pqGetInt(&their_version, 4, conn) != 0)
1417+
goto eof;
14181418

14191419
if (pqGetInt(&num, 4, conn) != 0)
1420-
return EOF;
1420+
goto eof;
14211421

1422-
initPQExpBuffer(&buf);
1423-
for (int i = 0; i < num; i++)
1422+
/* Check the protocol version */
1423+
if (their_version > conn->pversion)
14241424
{
1425-
if (pqGets(&conn->workBuffer, conn))
1426-
{
1427-
termPQExpBuffer(&buf);
1428-
return EOF;
1429-
}
1430-
if (buf.len > 0)
1431-
appendPQExpBufferChar(&buf, ' ');
1432-
appendPQExpBufferStr(&buf, conn->workBuffer.data);
1425+
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requested downgrade to a higher-numbered version");
1426+
goto failure;
1427+
}
1428+
1429+
if (their_version < PG_PROTOCOL(3, 0))
1430+
{
1431+
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requested downgrade to pre-3.0 protocol version");
1432+
goto failure;
14331433
}
14341434

1435-
if (their_version < conn->pversion)
1436-
libpq_append_conn_error(conn, "protocol version not supported by server: client uses %u.%u, server supports up to %u.%u",
1437-
PG_PROTOCOL_MAJOR(conn->pversion), PG_PROTOCOL_MINOR(conn->pversion),
1438-
PG_PROTOCOL_MAJOR(their_version), PG_PROTOCOL_MINOR(their_version));
1439-
if (num > 0)
1435+
if (num < 0)
14401436
{
1441-
appendPQExpBuffer(&conn->errorMessage,
1442-
libpq_ngettext("protocol extension not supported by server: %s",
1443-
"protocol extensions not supported by server: %s", num),
1444-
buf.data);
1445-
appendPQExpBufferChar(&conn->errorMessage, '\n');
1437+
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported negative number of unsupported parameters");
1438+
goto failure;
1439+
}
1440+
1441+
if (their_version == conn->pversion && num == 0)
1442+
{
1443+
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server negotiated but asks for no changes");
1444+
goto failure;
14461445
}
14471446

1448-
/* neither -- server shouldn't have sent it */
1449-
if (!(their_version < conn->pversion) && !(num > 0))
1450-
libpq_append_conn_error(conn, "invalid %s message", "NegotiateProtocolVersion");
1447+
/* the version is acceptable */
1448+
conn->pversion = their_version;
1449+
1450+
/*
1451+
* We don't currently request any protocol extensions, so we don't expect
1452+
* the server to reply with any either.
1453+
*/
1454+
for (int i = 0; i < num; i++)
1455+
{
1456+
if (pqGets(&conn->workBuffer, conn))
1457+
{
1458+
goto eof;
1459+
}
1460+
if (strncmp(conn->workBuffer.data, "_pq_.", 5) != 0)
1461+
{
1462+
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported unsupported parameter name without a _pq_. prefix (\"%s\")", conn->workBuffer.data);
1463+
goto failure;
1464+
}
1465+
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported an unsupported parameter that was not requested (\"%s\")", conn->workBuffer.data);
1466+
goto failure;
1467+
}
14511468

1452-
termPQExpBuffer(&buf);
14531469
return 0;
1470+
1471+
eof:
1472+
libpq_append_conn_error(conn, "received invalid protocol negotation message: message too short");
1473+
failure:
1474+
conn->asyncStatus = PGASYNC_READY;
1475+
pqSaveErrorResult(conn);
1476+
return 1;
14541477
}
14551478

14561479

14571480
/*
14581481
* Attempt to read a ParameterStatus message.
14591482
* This is possible in several places, so we break it out as a subroutine.
1483+
*
14601484
* Entry: 'S' message type and length have already been consumed.
14611485
* Exit: returns 0 if successfully consumed message.
14621486
* returns EOF if not enough data.
@@ -1486,6 +1510,7 @@ getParameterStatus(PGconn *conn)
14861510
/*
14871511
* Attempt to read a Notify response message.
14881512
* This is possible in several places, so we break it out as a subroutine.
1513+
*
14891514
* Entry: 'A' message type and length have already been consumed.
14901515
* Exit: returns 0 if successfully consumed Notify message.
14911516
* returns EOF if not enough data.

src/interfaces/libpq/libpq-int.h

+1
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ struct pg_conn
496496
SockAddr raddr; /* Remote address */
497497
ProtocolVersion pversion; /* FE/BE protocol version in use */
498498
int sversion; /* server version, e.g. 70401 for 7.4.1 */
499+
bool negotiate_pversion_received; /* true if NegotiateProtocolVersion received */
499500
bool auth_req_received; /* true if any type of auth req received */
500501
bool password_needed; /* true if server demanded a password */
501502
bool gssapi_used; /* true if authenticated via gssapi */

0 commit comments

Comments
 (0)