Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit dc4957e

Browse files
hlinnakaCommitfest Bot
authored and
Commitfest Bot
committed
Make cancel request keys longer
Currently, cancel request key is 32-bit token, which isn't very much entropy. If you want to cancel another session's query, you can brute-force it. In most environments, an unauthorized cancellation of a query isn't very serious, but it nevertheless would be nice to have more protection from it. Hence make the key longer, to make it harder to guess. The longer cancellation keys are generated when using the new protocol version 3.2. For connections using version 3.0, short 4-bytes keys are still used. The new longer key length is not hardcoded in the protocol anymore, the client is expected to deal with variable length keys, up to 256 bytes. This flexibility allows e.g. a connection pooler to add more information to the cancel key, which might be useful for finding the connection. Reviewed-by: Jelte Fennema-Nio <postgres@jeltef.nl> Reviewed-by: Robert Haas <robertmhaas@gmail.com> (earlier versions) Discussion: https://www.postgresql.org/message-id/508d0505-8b7a-4864-a681-e7e5edfe32aa@iki.fi
1 parent 3d9a0b4 commit dc4957e

File tree

15 files changed

+252
-85
lines changed

15 files changed

+252
-85
lines changed

doc/src/sgml/protocol.sgml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@
4949
supported by the server.
5050
</para>
5151

52+
<para>
53+
The difference between protocol version 3.0 and 3.2 is that the secret key
54+
used in query cancellation was enlarged from 4 bytes to a variable length
55+
field. The BackendKeyData message was changed to accomodate that, and the
56+
CancelRequest message was redefined to have a variable length payload.
57+
</para>
58+
5259
<para>
5360
In order to serve multiple clients efficiently, the server launches
5461
a new <quote>backend</quote> process for each client.
@@ -4062,7 +4069,7 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
40624069
</varlistentry>
40634070

40644071
<varlistentry>
4065-
<term>Int32(12)</term>
4072+
<term>Int32</term>
40664073
<listitem>
40674074
<para>
40684075
Length of message contents in bytes, including self.
@@ -4080,14 +4087,18 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
40804087
</varlistentry>
40814088

40824089
<varlistentry>
4083-
<term>Int32</term>
4090+
<term>Byte<replaceable>n</replaceable></term>
40844091
<listitem>
40854092
<para>
4086-
The secret key of this backend.
4093+
The secret key of this backend. This field extends to the end of the
4094+
message, indicated by the length field. The maximum key length is 256 bytes.
40874095
</para>
40884096
</listitem>
40894097
</varlistentry>
40904098
</variablelist>
4099+
<para>
4100+
Before protocol version 3.2, the secret key was always 4 bytes long.
4101+
</para>
40914102
</listitem>
40924103
</varlistentry>
40934104

@@ -4293,14 +4304,18 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
42934304
</varlistentry>
42944305

42954306
<varlistentry>
4296-
<term>Int32</term>
4307+
<term>Byte<replaceable>n</replaceable></term>
42974308
<listitem>
42984309
<para>
4299-
The secret key for the target backend.
4310+
The secret key for the target backend. This field extends to the end of the
4311+
message, indicated by the length field. The maximum key length is 256 bytes.
43004312
</para>
43014313
</listitem>
43024314
</varlistentry>
43034315
</variablelist>
4316+
<para>
4317+
Before protocol version 3.2, the secret key was always 4 bytes long.
4318+
</para>
43044319
</listitem>
43054320
</varlistentry>
43064321

src/backend/storage/ipc/procsignal.c

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@
6363
typedef struct
6464
{
6565
pg_atomic_uint32 pss_pid;
66-
bool pss_cancel_key_valid;
67-
int32 pss_cancel_key;
66+
int pss_cancel_key_len; /* 0 means no cancellation is possible */
67+
char pss_cancel_key[MAX_CANCEL_KEY_LENGTH];
6868
volatile sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
6969
slock_t pss_mutex; /* protects the above fields */
7070

@@ -148,8 +148,7 @@ ProcSignalShmemInit(void)
148148

149149
SpinLockInit(&slot->pss_mutex);
150150
pg_atomic_init_u32(&slot->pss_pid, 0);
151-
slot->pss_cancel_key_valid = false;
152-
slot->pss_cancel_key = 0;
151+
slot->pss_cancel_key_len = 0;
153152
MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
154153
pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
155154
pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
@@ -163,12 +162,13 @@ ProcSignalShmemInit(void)
163162
* Register the current process in the ProcSignal array
164163
*/
165164
void
166-
ProcSignalInit(bool cancel_key_valid, int32 cancel_key)
165+
ProcSignalInit(char *cancel_key, int cancel_key_len)
167166
{
168167
ProcSignalSlot *slot;
169168
uint64 barrier_generation;
170169
uint32 old_pss_pid;
171170

171+
Assert(cancel_key_len >= 0 && cancel_key_len <= MAX_CANCEL_KEY_LENGTH);
172172
if (MyProcNumber < 0)
173173
elog(ERROR, "MyProcNumber not set");
174174
if (MyProcNumber >= NumProcSignalSlots)
@@ -199,8 +199,9 @@ ProcSignalInit(bool cancel_key_valid, int32 cancel_key)
199199
pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
200200
pg_atomic_write_u64(&slot->pss_barrierGeneration, barrier_generation);
201201

202-
slot->pss_cancel_key_valid = cancel_key_valid;
203-
slot->pss_cancel_key = cancel_key;
202+
if (cancel_key_len > 0)
203+
memcpy(slot->pss_cancel_key, cancel_key, cancel_key_len);
204+
slot->pss_cancel_key_len = cancel_key_len;
204205
pg_atomic_write_u32(&slot->pss_pid, MyProcPid);
205206

206207
SpinLockRelease(&slot->pss_mutex);
@@ -254,8 +255,7 @@ CleanupProcSignalState(int status, Datum arg)
254255

255256
/* Mark the slot as unused */
256257
pg_atomic_write_u32(&slot->pss_pid, 0);
257-
slot->pss_cancel_key_valid = false;
258-
slot->pss_cancel_key = 0;
258+
slot->pss_cancel_key_len = 0;
259259

260260
/*
261261
* Make this slot look like it's absorbed all possible barriers, so that
@@ -725,7 +725,7 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
725725
* fields in the ProcSignal slots.
726726
*/
727727
void
728-
SendCancelRequest(int backendPID, int32 cancelAuthCode)
728+
SendCancelRequest(int backendPID, char *cancel_key, int cancel_key_len)
729729
{
730730
Assert(backendPID != 0);
731731

@@ -754,7 +754,8 @@ SendCancelRequest(int backendPID, int32 cancelAuthCode)
754754
}
755755
else
756756
{
757-
match = slot->pss_cancel_key_valid && slot->pss_cancel_key == cancelAuthCode;
757+
match = slot->pss_cancel_key_len == cancel_key_len &&
758+
timingsafe_bcmp(slot->pss_cancel_key, cancel_key, cancel_key_len) == 0;
758759

759760
SpinLockRelease(&slot->pss_mutex);
760761

src/backend/tcop/backend_startup.c

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ ConnectionTiming conn_timing = {.ready_for_use = TIMESTAMP_MINUS_INFINITY};
5959
static void BackendInitialize(ClientSocket *client_sock, CAC_state cac);
6060
static int ProcessSSLStartup(Port *port);
6161
static int ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done);
62+
static void ProcessCancelRequestPacket(Port *port, void *pkt, int pktlen);
6263
static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
6364
static void process_startup_packet_die(SIGNAL_ARGS);
6465
static void StartupPacketTimeoutHandler(void);
@@ -557,28 +558,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
557558

558559
if (proto == CANCEL_REQUEST_CODE)
559560
{
560-
/*
561-
* The client has sent a cancel request packet, not a normal
562-
* start-a-new-connection packet. Perform the necessary processing.
563-
* Nothing is sent back to the client.
564-
*/
565-
CancelRequestPacket *canc;
566-
int backendPID;
567-
int32 cancelAuthCode;
568-
569-
if (len != sizeof(CancelRequestPacket))
570-
{
571-
ereport(COMMERROR,
572-
(errcode(ERRCODE_PROTOCOL_VIOLATION),
573-
errmsg("invalid length of startup packet")));
574-
return STATUS_ERROR;
575-
}
576-
canc = (CancelRequestPacket *) buf;
577-
backendPID = (int) pg_ntoh32(canc->backendPID);
578-
cancelAuthCode = (int32) pg_ntoh32(canc->cancelAuthCode);
579-
580-
if (backendPID != 0)
581-
SendCancelRequest(backendPID, cancelAuthCode);
561+
ProcessCancelRequestPacket(port, buf, len);
582562
/* Not really an error, but we don't want to proceed further */
583563
return STATUS_ERROR;
584564
}
@@ -878,6 +858,37 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
878858
return STATUS_OK;
879859
}
880860

861+
/*
862+
* The client has sent a cancel request packet, not a normal
863+
* start-a-new-connection packet. Perform the necessary processing. Nothing
864+
* is sent back to the client.
865+
*/
866+
static void
867+
ProcessCancelRequestPacket(Port *port, void *pkt, int pktlen)
868+
{
869+
CancelRequestPacket *canc;
870+
int len;
871+
872+
if (pktlen < offsetof(CancelRequestPacket, cancelAuthCode))
873+
{
874+
ereport(COMMERROR,
875+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
876+
errmsg("invalid length of query cancel packet")));
877+
return;
878+
}
879+
len = pktlen - offsetof(CancelRequestPacket, cancelAuthCode);
880+
if (len == 0 || len > 256)
881+
{
882+
ereport(COMMERROR,
883+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
884+
errmsg("invalid length of query cancel key")));
885+
return;
886+
}
887+
888+
canc = (CancelRequestPacket *) pkt;
889+
SendCancelRequest(pg_ntoh32(canc->backendPID), canc->cancelAuthCode, len);
890+
}
891+
881892
/*
882893
* Send a NegotiateProtocolVersion to the client. This lets the client know
883894
* that they have either requested a newer minor protocol version than we are

src/backend/tcop/postgres.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4259,16 +4259,20 @@ PostgresMain(const char *dbname, const char *username)
42594259
* Generate a random cancel key, if this is a backend serving a
42604260
* connection. InitPostgres() will advertise it in shared memory.
42614261
*/
4262-
Assert(!MyCancelKeyValid);
4262+
Assert(MyCancelKeyLength == 0);
42634263
if (whereToSendOutput == DestRemote)
42644264
{
4265-
if (!pg_strong_random(&MyCancelKey, sizeof(int32)))
4265+
int len;
4266+
4267+
len = (MyProcPort == NULL || MyProcPort->proto >= PG_PROTOCOL(3, 1))
4268+
? MAX_CANCEL_KEY_LENGTH : 4;
4269+
if (!pg_strong_random(&MyCancelKey, len))
42664270
{
42674271
ereport(ERROR,
42684272
(errcode(ERRCODE_INTERNAL_ERROR),
42694273
errmsg("could not generate random cancel key")));
42704274
}
4271-
MyCancelKeyValid = true;
4275+
MyCancelKeyLength = len;
42724276
}
42734277

42744278
/*
@@ -4323,10 +4327,11 @@ PostgresMain(const char *dbname, const char *username)
43234327
{
43244328
StringInfoData buf;
43254329

4326-
Assert(MyCancelKeyValid);
4330+
Assert(MyCancelKeyLength > 0);
43274331
pq_beginmessage(&buf, PqMsg_BackendKeyData);
43284332
pq_sendint32(&buf, (int32) MyProcPid);
4329-
pq_sendint32(&buf, (int32) MyCancelKey);
4333+
4334+
pq_sendbytes(&buf, MyCancelKey, MyCancelKeyLength);
43304335
pq_endmessage(&buf);
43314336
/* Need not flush since ReadyForQuery will do it. */
43324337
}

src/backend/utils/init/globals.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "miscadmin.h"
2525
#include "postmaster/postmaster.h"
2626
#include "storage/procnumber.h"
27+
#include "storage/procsignal.h"
2728

2829

2930
ProtocolVersion FrontendProtocol;
@@ -48,8 +49,8 @@ pg_time_t MyStartTime;
4849
TimestampTz MyStartTimestamp;
4950
struct ClientSocket *MyClientSocket;
5051
struct Port *MyProcPort;
51-
bool MyCancelKeyValid = false;
52-
int32 MyCancelKey = 0;
52+
char MyCancelKey[MAX_CANCEL_KEY_LENGTH];
53+
uint8 MyCancelKeyLength = 0;
5354
int MyPMChildSlot;
5455

5556
/*

src/backend/utils/init/postinit.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
753753
*/
754754
SharedInvalBackendInit(false);
755755

756-
ProcSignalInit(MyCancelKeyValid, MyCancelKey);
756+
ProcSignalInit(MyCancelKey, MyCancelKeyLength);
757757

758758
/*
759759
* Also set up timeout handlers needed for backend operation. We need

src/include/libpq/pqcomm.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,20 @@ typedef uint32 AuthRequest;
128128
*
129129
* The cancel request code must not match any protocol version number
130130
* we're ever likely to use. This random choice should do.
131+
*
132+
* Before PostgreSQL v17 and the protocol version bump from 3.0 to 3.1, the
133+
* cancel key was always 4 bytes. Starting with v17, it's variable length.
131134
*/
135+
132136
#define CANCEL_REQUEST_CODE PG_PROTOCOL(1234,5678)
133137

134138
typedef struct CancelRequestPacket
135139
{
136140
/* Note that each field is stored in network byte order! */
137141
MsgType cancelRequestCode; /* code to identify a cancel request */
138142
uint32 backendPID; /* PID of client's backend */
139-
uint32 cancelAuthCode; /* secret key to authorize cancel */
143+
char cancelAuthCode[FLEXIBLE_ARRAY_MEMBER]; /* secret key to
144+
* authorize cancel */
140145
} CancelRequestPacket;
141146

142147
/* Application-Layer Protocol Negotiation is required for direct connections

src/include/miscadmin.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,8 @@ extern PGDLLIMPORT pg_time_t MyStartTime;
191191
extern PGDLLIMPORT TimestampTz MyStartTimestamp;
192192
extern PGDLLIMPORT struct Port *MyProcPort;
193193
extern PGDLLIMPORT struct Latch *MyLatch;
194-
extern PGDLLIMPORT bool MyCancelKeyValid;
195-
extern PGDLLIMPORT int32 MyCancelKey;
194+
extern PGDLLIMPORT char MyCancelKey[];
195+
extern PGDLLIMPORT uint8 MyCancelKeyLength;
196196
extern PGDLLIMPORT int MyPMChildSlot;
197197

198198
extern PGDLLIMPORT char OutputFileName[];

src/include/storage/procsignal.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,26 @@ typedef enum
5656
PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
5757
} ProcSignalBarrierType;
5858

59+
/*
60+
* Length of query cancel keys generated.
61+
*
62+
* Note that the protocol allows for longer keys, or shorter, but this is the
63+
* length we actually generate. Client code, and the server code that handles
64+
* incoming cancellation packets from clients, cannot use this hardcoded
65+
* length.
66+
*/
67+
#define MAX_CANCEL_KEY_LENGTH 32
68+
5969
/*
6070
* prototypes for functions in procsignal.c
6171
*/
6272
extern Size ProcSignalShmemSize(void);
6373
extern void ProcSignalShmemInit(void);
6474

65-
extern void ProcSignalInit(bool cancel_key_valid, int32 cancel_key);
75+
extern void ProcSignalInit(char *cancel_key, int cancel_key_len);
6676
extern int SendProcSignal(pid_t pid, ProcSignalReason reason,
6777
ProcNumber procNumber);
68-
extern void SendCancelRequest(int backendPID, int32 cancelAuthCode);
78+
extern void SendCancelRequest(int backendPID, char *cancel_key, int cancel_key_len);
6979

7080
extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
7181
extern void WaitForProcSignalBarrier(uint64 generation);

0 commit comments

Comments
 (0)