Make cancel request keys longer
authorHeikki Linnakangas <[email protected]>
Wed, 2 Apr 2025 13:41:48 +0000 (16:41 +0300)
committerHeikki Linnakangas <[email protected]>
Wed, 2 Apr 2025 13:41:48 +0000 (16:41 +0300)
Currently, the cancel request key is a 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 <[email protected]>
Reviewed-by: Robert Haas <[email protected]> (earlier versions)
Discussion: https://www.postgresql.org/message-id/508d0505-8b7a-4864-a681-e7e5edfe32aa@iki.fi

14 files changed:
doc/src/sgml/protocol.sgml
src/backend/storage/ipc/procsignal.c
src/backend/tcop/backend_startup.c
src/backend/tcop/postgres.c
src/backend/utils/init/globals.c
src/backend/utils/init/postinit.c
src/include/libpq/pqcomm.h
src/include/miscadmin.h
src/include/storage/procsignal.h
src/interfaces/libpq/fe-cancel.c
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-protocol3.c
src/interfaces/libpq/libpq-int.h
src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl

index 628da4cd7ccb78af139463a787bbd092ed0d5598..4dab0cb4923ab5679d3dcdf1d62a09c565128719 100644 (file)
@@ -4062,7 +4062,7 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
       </varlistentry>
 
       <varlistentry>
-       <term>Int32(12)</term>
+       <term>Int32</term>
        <listitem>
         <para>
          Length of message contents in bytes, including self.
@@ -4080,14 +4080,29 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
       </varlistentry>
 
       <varlistentry>
-       <term>Int32</term>
+       <term>Byte<replaceable>n</replaceable></term>
        <listitem>
         <para>
-         The secret key of this backend.
+         The secret key of this backend. This field extends to the end of the
+         message, indicated by the length field.
+        </para>
+        <para>
+          The maximum key length is 256 bytes. The
+          <productname>PostgreSQL</productname> server only sends keys up to
+          32 bytes, but the larger maximum size allows for future server
+          versions, as well as connection poolers and other middleware, to use
+          longer keys. One possible use case is augmenting the server's key
+          with extra information. Middleware is therefore also encouraged to
+          not use up all of the bytes, in case multiple middleware
+          applications are layered on top of each other, each of which may
+          wrap the key with extra data.
         </para>
        </listitem>
       </varlistentry>
      </variablelist>
+     <para>
+      Before protocol version 3.2, the secret key was always 4 bytes long.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -4293,14 +4308,18 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
       </varlistentry>
 
       <varlistentry>
-       <term>Int32</term>
+       <term>Byte<replaceable>n</replaceable></term>
        <listitem>
         <para>
-         The secret key for the target backend.
+         The secret key for the target backend. This field extends to the end of the
+         message, indicated by the length field. The maximum key length is 256 bytes.
         </para>
        </listitem>
       </varlistentry>
      </variablelist>
+     <para>
+      Before protocol version 3.2, the secret key was always 4 bytes long.
+     </para>
     </listitem>
    </varlistentry>
 
index 7d201965503cbb25d325fce9b599998f1a989dcc..b7c39a4c5f0c3f0960d6c2ee78c049e8874c4a77 100644 (file)
@@ -63,8 +63,8 @@
 typedef struct
 {
    pg_atomic_uint32 pss_pid;
-   bool        pss_cancel_key_valid;
-   int32       pss_cancel_key;
+   int         pss_cancel_key_len; /* 0 means no cancellation is possible */
+   char        pss_cancel_key[MAX_CANCEL_KEY_LENGTH];
    volatile sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
    slock_t     pss_mutex;      /* protects the above fields */
 
@@ -148,8 +148,7 @@ ProcSignalShmemInit(void)
 
            SpinLockInit(&slot->pss_mutex);
            pg_atomic_init_u32(&slot->pss_pid, 0);
-           slot->pss_cancel_key_valid = false;
-           slot->pss_cancel_key = 0;
+           slot->pss_cancel_key_len = 0;
            MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
            pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
            pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
@@ -163,12 +162,13 @@ ProcSignalShmemInit(void)
  *     Register the current process in the ProcSignal array
  */
 void
-ProcSignalInit(bool cancel_key_valid, int32 cancel_key)
+ProcSignalInit(char *cancel_key, int cancel_key_len)
 {
    ProcSignalSlot *slot;
    uint64      barrier_generation;
    uint32      old_pss_pid;
 
+   Assert(cancel_key_len >= 0 && cancel_key_len <= MAX_CANCEL_KEY_LENGTH);
    if (MyProcNumber < 0)
        elog(ERROR, "MyProcNumber not set");
    if (MyProcNumber >= NumProcSignalSlots)
@@ -199,8 +199,9 @@ ProcSignalInit(bool cancel_key_valid, int32 cancel_key)
        pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
    pg_atomic_write_u64(&slot->pss_barrierGeneration, barrier_generation);
 
-   slot->pss_cancel_key_valid = cancel_key_valid;
-   slot->pss_cancel_key = cancel_key;
+   if (cancel_key_len > 0)
+       memcpy(slot->pss_cancel_key, cancel_key, cancel_key_len);
+   slot->pss_cancel_key_len = cancel_key_len;
    pg_atomic_write_u32(&slot->pss_pid, MyProcPid);
 
    SpinLockRelease(&slot->pss_mutex);
@@ -254,8 +255,7 @@ CleanupProcSignalState(int status, Datum arg)
 
    /* Mark the slot as unused */
    pg_atomic_write_u32(&slot->pss_pid, 0);
-   slot->pss_cancel_key_valid = false;
-   slot->pss_cancel_key = 0;
+   slot->pss_cancel_key_len = 0;
 
    /*
     * Make this slot look like it's absorbed all possible barriers, so that
@@ -725,7 +725,7 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
  * fields in the ProcSignal slots.
  */
 void
-SendCancelRequest(int backendPID, int32 cancelAuthCode)
+SendCancelRequest(int backendPID, char *cancel_key, int cancel_key_len)
 {
    Assert(backendPID != 0);
 
@@ -754,7 +754,8 @@ SendCancelRequest(int backendPID, int32 cancelAuthCode)
        }
        else
        {
-           match = slot->pss_cancel_key_valid && slot->pss_cancel_key == cancelAuthCode;
+           match = slot->pss_cancel_key_len == cancel_key_len &&
+               timingsafe_bcmp(slot->pss_cancel_key, cancel_key, cancel_key_len) == 0;
 
            SpinLockRelease(&slot->pss_mutex);
 
index 84e1c6f2831a356e4a5f9ad5f818f749d6a1b7f0..dde8d5b35175e6373c78c74f39e8eee28a91ac82 100644 (file)
@@ -60,6 +60,7 @@ ConnectionTiming conn_timing = {.ready_for_use = TIMESTAMP_MINUS_INFINITY};
 static void BackendInitialize(ClientSocket *client_sock, CAC_state cac);
 static int ProcessSSLStartup(Port *port);
 static int ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done);
+static void ProcessCancelRequestPacket(Port *port, void *pkt, int pktlen);
 static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
 static void process_startup_packet_die(SIGNAL_ARGS);
 static void StartupPacketTimeoutHandler(void);
@@ -565,28 +566,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 
    if (proto == CANCEL_REQUEST_CODE)
    {
-       /*
-        * The client has sent a cancel request packet, not a normal
-        * start-a-new-connection packet.  Perform the necessary processing.
-        * Nothing is sent back to the client.
-        */
-       CancelRequestPacket *canc;
-       int         backendPID;
-       int32       cancelAuthCode;
-
-       if (len != sizeof(CancelRequestPacket))
-       {
-           ereport(COMMERROR,
-                   (errcode(ERRCODE_PROTOCOL_VIOLATION),
-                    errmsg("invalid length of startup packet")));
-           return STATUS_ERROR;
-       }
-       canc = (CancelRequestPacket *) buf;
-       backendPID = (int) pg_ntoh32(canc->backendPID);
-       cancelAuthCode = (int32) pg_ntoh32(canc->cancelAuthCode);
-
-       if (backendPID != 0)
-           SendCancelRequest(backendPID, cancelAuthCode);
+       ProcessCancelRequestPacket(port, buf, len);
        /* Not really an error, but we don't want to proceed further */
        return STATUS_ERROR;
    }
@@ -886,6 +866,37 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
    return STATUS_OK;
 }
 
+/*
+ * The client has sent a cancel request packet, not a normal
+ * start-a-new-connection packet.  Perform the necessary processing.  Nothing
+ * is sent back to the client.
+ */
+static void
+ProcessCancelRequestPacket(Port *port, void *pkt, int pktlen)
+{
+   CancelRequestPacket *canc;
+   int         len;
+
+   if (pktlen < offsetof(CancelRequestPacket, cancelAuthCode))
+   {
+       ereport(COMMERROR,
+               (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                errmsg("invalid length of query cancel packet")));
+       return;
+   }
+   len = pktlen - offsetof(CancelRequestPacket, cancelAuthCode);
+   if (len == 0 || len > 256)
+   {
+       ereport(COMMERROR,
+               (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                errmsg("invalid length of query cancel key")));
+       return;
+   }
+
+   canc = (CancelRequestPacket *) pkt;
+   SendCancelRequest(pg_ntoh32(canc->backendPID), canc->cancelAuthCode, len);
+}
+
 /*
  * Send a NegotiateProtocolVersion to the client.  This lets the client know
  * that they have either requested a newer minor protocol version than we are
index aec65007bb6a3f6c21d584247954ec1e7e456757..89189848862cbc999bc5d6c42d91251fdfd50161 100644 (file)
@@ -4259,16 +4259,20 @@ PostgresMain(const char *dbname, const char *username)
     * Generate a random cancel key, if this is a backend serving a
     * connection. InitPostgres() will advertise it in shared memory.
     */
-   Assert(!MyCancelKeyValid);
+   Assert(MyCancelKeyLength == 0);
    if (whereToSendOutput == DestRemote)
    {
-       if (!pg_strong_random(&MyCancelKey, sizeof(int32)))
+       int         len;
+
+       len = (MyProcPort == NULL || MyProcPort->proto >= PG_PROTOCOL(3, 2))
+           ? MAX_CANCEL_KEY_LENGTH : 4;
+       if (!pg_strong_random(&MyCancelKey, len))
        {
            ereport(ERROR,
                    (errcode(ERRCODE_INTERNAL_ERROR),
                     errmsg("could not generate random cancel key")));
        }
-       MyCancelKeyValid = true;
+       MyCancelKeyLength = len;
    }
 
    /*
@@ -4323,10 +4327,11 @@ PostgresMain(const char *dbname, const char *username)
    {
        StringInfoData buf;
 
-       Assert(MyCancelKeyValid);
+       Assert(MyCancelKeyLength > 0);
        pq_beginmessage(&buf, PqMsg_BackendKeyData);
        pq_sendint32(&buf, (int32) MyProcPid);
-       pq_sendint32(&buf, (int32) MyCancelKey);
+
+       pq_sendbytes(&buf, MyCancelKey, MyCancelKeyLength);
        pq_endmessage(&buf);
        /* Need not flush since ReadyForQuery will do it. */
    }
index b844f9fdaef54a98df4096d37077fa08bf7e11d8..2152aad97d9909d00422df29730e2c528a3c861f 100644 (file)
@@ -24,6 +24,7 @@
 #include "miscadmin.h"
 #include "postmaster/postmaster.h"
 #include "storage/procnumber.h"
+#include "storage/procsignal.h"
 
 
 ProtocolVersion FrontendProtocol;
@@ -48,8 +49,8 @@ pg_time_t MyStartTime;
 TimestampTz MyStartTimestamp;
 struct ClientSocket *MyClientSocket;
 struct Port *MyProcPort;
-bool       MyCancelKeyValid = false;
-int32      MyCancelKey = 0;
+char       MyCancelKey[MAX_CANCEL_KEY_LENGTH];
+uint8      MyCancelKeyLength = 0;
 int            MyPMChildSlot;
 
 /*
index 7958ea11b7354aa704da6ad3062be4afd96a8bfc..c09c4d404ba79ea010b8d67c3cff9857eefa22f1 100644 (file)
@@ -753,7 +753,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
     */
    SharedInvalBackendInit(false);
 
-   ProcSignalInit(MyCancelKeyValid, MyCancelKey);
+   ProcSignalInit(MyCancelKey, MyCancelKeyLength);
 
    /*
     * Also set up timeout handlers needed for backend operation.  We need
index 0aceb7147c7212e04a9b2ed217d8d6bbf1370acb..d11069cf8dc822e6ef656d752201072689207de5 100644 (file)
@@ -128,7 +128,12 @@ typedef uint32 AuthRequest;
  *
  * The cancel request code must not match any protocol version number
  * we're ever likely to use.  This random choice should do.
+ *
+ * Before PostgreSQL v18 and the protocol version bump from 3.0 to 3.2, the
+ * cancel key was always 4 bytes.  With protocol version 3.2, it's variable
+ * length.
  */
+
 #define CANCEL_REQUEST_CODE PG_PROTOCOL(1234,5678)
 
 typedef struct CancelRequestPacket
@@ -136,7 +141,8 @@ typedef struct CancelRequestPacket
    /* Note that each field is stored in network byte order! */
    MsgType     cancelRequestCode;  /* code to identify a cancel request */
    uint32      backendPID;     /* PID of client's backend */
-   uint32      cancelAuthCode; /* secret key to authorize cancel */
+   char        cancelAuthCode[FLEXIBLE_ARRAY_MEMBER];  /* secret key to
+                                                        * authorize cancel */
 } CancelRequestPacket;
 
 /* Application-Layer Protocol Negotiation is required for direct connections
index 603d0424354d398f76d9b328c44f8b6648d47845..0d8528b2875141c1fabda9ea6b9eba6001b96025 100644 (file)
@@ -191,8 +191,8 @@ extern PGDLLIMPORT pg_time_t MyStartTime;
 extern PGDLLIMPORT TimestampTz MyStartTimestamp;
 extern PGDLLIMPORT struct Port *MyProcPort;
 extern PGDLLIMPORT struct Latch *MyLatch;
-extern PGDLLIMPORT bool MyCancelKeyValid;
-extern PGDLLIMPORT int32 MyCancelKey;
+extern PGDLLIMPORT char MyCancelKey[];
+extern PGDLLIMPORT uint8 MyCancelKeyLength;
 extern PGDLLIMPORT int MyPMChildSlot;
 
 extern PGDLLIMPORT char OutputFileName[];
index 022fd8ed933802a65f0de4ac66718681fcd45154..016dfd9b3f6cca8b0dbe2c8c25371f7c13fbf400 100644 (file)
@@ -56,16 +56,26 @@ typedef enum
    PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
 } ProcSignalBarrierType;
 
+/*
+ * Length of query cancel keys generated.
+ *
+ * Note that the protocol allows for longer keys, or shorter, but this is the
+ * length we actually generate.  Client code, and the server code that handles
+ * incoming cancellation packets from clients, mustn't use this hardcoded
+ * length.
+ */
+#define MAX_CANCEL_KEY_LENGTH  32
+
 /*
  * s for functions in procsignal.c
  */
 extern Size ProcSignalShmemSize(void);
 extern void ProcSignalShmemInit(void);
 
-extern void ProcSignalInit(bool cancel_key_valid, int32 cancel_key);
+extern void ProcSignalInit(char *cancel_key, int cancel_key_len);
 extern int SendProcSignal(pid_t pid, ProcSignalReason reason,
                           ProcNumber procNumber);
-extern void SendCancelRequest(int backendPID, int32 cancelAuthCode);
+extern void SendCancelRequest(int backendPID, char *cancel_key, int cancel_key_len);
 
 extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
 extern void WaitForProcSignalBarrier(uint64 generation);
index 7ebaa335bbae2be99dd94c3d734d436c2e329037..e84e64bf2a70a6573e2113bbd46b1e2d52082e59 100644 (file)
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * fe-cancel.c
- *   functions related to setting up a connection to the backend
+ *   functions related to query cancellation
  *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -41,7 +41,6 @@ struct pg_cancel
 {
    SockAddr    raddr;          /* Remote address */
    int         be_pid;         /* PID of to-be-canceled backend */
-   int         be_key;         /* cancel key of to-be-canceled backend */
    int         pgtcp_user_timeout; /* tcp user timeout */
    int         keepalives;     /* use TCP keepalives? */
    int         keepalives_idle;    /* time between TCP keepalives */
@@ -49,6 +48,10 @@ struct pg_cancel
                                         * retransmits */
    int         keepalives_count;   /* maximum number of TCP keepalive
                                     * retransmits */
+
+   /* Pre-constructed cancel request packet starts here */
+   int32       cancel_pkt_len; /* in network-byte-order */
+   char        cancel_req[FLEXIBLE_ARRAY_MEMBER];  /* CancelRequestPacket */
 };
 
 
@@ -83,6 +86,13 @@ PQcancelCreate(PGconn *conn)
        return (PGcancelConn *) cancelConn;
    }
 
+   /* Check that we have received a cancellation key */
+   if (conn->be_cancel_key_len == 0)
+   {
+       libpq_append_conn_error(cancelConn, "no cancellation key received");
+       return (PGcancelConn *) cancelConn;
+   }
+
    /*
     * Indicate that this connection is used to send a cancellation
     */
@@ -101,7 +111,15 @@ PQcancelCreate(PGconn *conn)
     * Copy cancellation token data from the original connection
     */
    cancelConn->be_pid = conn->be_pid;
-   cancelConn->be_key = conn->be_key;
+   if (conn->be_cancel_key != NULL)
+   {
+       cancelConn->be_cancel_key = malloc(conn->be_cancel_key_len);
+       if (!conn->be_cancel_key)
+           goto oom_error;
+       memcpy(cancelConn->be_cancel_key, conn->be_cancel_key, conn->be_cancel_key_len);
+   }
+   cancelConn->be_cancel_key_len = conn->be_cancel_key_len;
+   cancelConn->pversion = conn->pversion;
 
    /*
     * Cancel requests should not iterate over all possible hosts. The request
@@ -349,6 +367,8 @@ PGcancel *
 PQgetCancel(PGconn *conn)
 {
    PGcancel   *cancel;
+   int         cancel_req_len;
+   CancelRequestPacket *req;
 
    if (!conn)
        return NULL;
@@ -356,13 +376,17 @@ PQgetCancel(PGconn *conn)
    if (conn->sock == PGINVALID_SOCKET)
        return NULL;
 
-   cancel = malloc(sizeof(PGcancel));
+   /* Check that we have received a cancellation key */
+   if (conn->be_cancel_key_len == 0)
+       return NULL;
+
+   cancel_req_len = offsetof(CancelRequestPacket, cancelAuthCode) + conn->be_cancel_key_len;
+   cancel = malloc(offsetof(PGcancel, cancel_req) + cancel_req_len);
    if (cancel == NULL)
        return NULL;
 
    memcpy(&cancel->raddr, &conn->raddr, sizeof(SockAddr));
-   cancel->be_pid = conn->be_pid;
-   cancel->be_key = conn->be_key;
+
    /* We use -1 to indicate an unset connection option */
    cancel->pgtcp_user_timeout = -1;
    cancel->keepalives = -1;
@@ -405,6 +429,13 @@ PQgetCancel(PGconn *conn)
            goto fail;
    }
 
+   req = (CancelRequestPacket *) &cancel->cancel_req;
+   req->cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
+   req->backendPID = pg_hton32(conn->be_pid);
+   memcpy(req->cancelAuthCode, conn->be_cancel_key, conn->be_cancel_key_len);
+   /* include the length field itself in the length */
+   cancel->cancel_pkt_len = pg_hton32(cancel_req_len + 4);
+
    return cancel;
 
 fail:
@@ -412,6 +443,42 @@ fail:
    return NULL;
 }
 
+/*
+ * PQsendCancelRequest
+ *  Submit a CancelRequest message, but don't wait for it to finish
+ *
+ * Returns: 1 if successfully submitted
+ *         0 if error (conn->errorMessage is set)
+ */
+int
+PQsendCancelRequest(PGconn *cancelConn)
+{
+   CancelRequestPacket req;
+
+   /* Start the message. */
+   if (pqPutMsgStart(0, cancelConn))
+       return STATUS_ERROR;
+
+   /* Send the message body. */
+   memset(&req, 0, offsetof(CancelRequestPacket, cancelAuthCode));
+   req.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
+   req.backendPID = pg_hton32(cancelConn->be_pid);
+   if (pqPutnchar((char *) &req, offsetof(CancelRequestPacket, cancelAuthCode), cancelConn))
+       return STATUS_ERROR;
+   if (pqPutnchar(cancelConn->be_cancel_key, cancelConn->be_cancel_key_len, cancelConn))
+       return STATUS_ERROR;
+
+   /* Finish the message. */
+   if (pqPutMsgEnd(cancelConn))
+       return STATUS_ERROR;
+
+   /* Flush to ensure backend gets it. */
+   if (pqFlush(cancelConn))
+       return STATUS_ERROR;
+
+   return STATUS_OK;
+}
+
 /* PQfreeCancel: free a cancel structure */
 void
 PQfreeCancel(PGcancel *cancel)
@@ -465,11 +532,8 @@ PQcancel(PGcancel *cancel, char *errbuf, int errbufsize)
    int         save_errno = SOCK_ERRNO;
    pgsocket    tmpsock = PGINVALID_SOCKET;
    int         maxlen;
-   struct
-   {
-       uint32      packetlen;
-       CancelRequestPacket cp;
-   }           crp;
+   char        recvbuf;
+   int         cancel_pkt_len;
 
    if (!cancel)
    {
@@ -571,15 +635,15 @@ retry3:
        goto cancel_errReturn;
    }
 
-   /* Create and send the cancel request packet. */
-
-   crp.packetlen = pg_hton32((uint32) sizeof(crp));
-   crp.cp.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
-   crp.cp.backendPID = pg_hton32(cancel->be_pid);
-   crp.cp.cancelAuthCode = pg_hton32(cancel->be_key);
+   cancel_pkt_len = pg_ntoh32(cancel->cancel_pkt_len);
 
 retry4:
-   if (send(tmpsock, (char *) &crp, sizeof(crp), 0) != (int) sizeof(crp))
+
+   /*
+    * Send the cancel request packet. It starts with the message length at
+    * cancel_pkt_len, followed by the actual packet.
+    */
+   if (send(tmpsock, (char *) &cancel->cancel_pkt_len, cancel_pkt_len, 0) != cancel_pkt_len)
    {
        if (SOCK_ERRNO == EINTR)
            /* Interrupted system call - we'll just try again */
@@ -596,7 +660,7 @@ retry4:
     * read to obtain any data, we are just waiting for EOF to be signaled.
     */
 retry5:
-   if (recv(tmpsock, (char *) &crp, 1, 0) < 0)
+   if (recv(tmpsock, &recvbuf, 1, 0) < 0)
    {
        if (SOCK_ERRNO == EINTR)
            /* Interrupted system call - we'll just try again */
index 5e3275ffd76f1dd67a4db185d9c9eff9ac1044da..715b5d5aff45d7a2e5bf9dae30a1aa4020ace186 100644 (file)
@@ -696,7 +696,12 @@ pqDropServerData(PGconn *conn)
    if (!conn->cancelRequest)
    {
        conn->be_pid = 0;
-       conn->be_key = 0;
+       if (conn->be_cancel_key != NULL)
+       {
+           free(conn->be_cancel_key);
+           conn->be_cancel_key = NULL;
+       }
+       conn->be_cancel_key_len = 0;
    }
 }
 
@@ -3692,13 +3697,7 @@ keep_going:                      /* We will come back to here until there is
                 */
                if (conn->cancelRequest)
                {
-                   CancelRequestPacket cancelpacket;
-
-                   packetlen = sizeof(cancelpacket);
-                   cancelpacket.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
-                   cancelpacket.backendPID = pg_hton32(conn->be_pid);
-                   cancelpacket.cancelAuthCode = pg_hton32(conn->be_key);
-                   if (pqPacketSend(conn, 0, &cancelpacket, packetlen) != STATUS_OK)
+                   if (PQsendCancelRequest(conn) != STATUS_OK)
                    {
                        libpq_append_conn_error(conn, "could not send cancel packet: %s",
                                                SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
index 7ba49ea4592022a5c7ff4cc1272a8821f3b73ceb..d85910f41fc7e1b10cfa7f588723df03700f07f1 100644 (file)
@@ -48,6 +48,7 @@ static int    getRowDescriptions(PGconn *conn, int msgLength);
 static int getParamDescriptions(PGconn *conn, int msgLength);
 static int getAnotherTuple(PGconn *conn, int msgLength);
 static int getParameterStatus(PGconn *conn);
+static int getBackendKeyData(PGconn *conn, int msgLength);
 static int getNotify(PGconn *conn);
 static int getCopyStart(PGconn *conn, ExecStatusType copytype);
 static int getReadyForQuery(PGconn *conn);
@@ -308,9 +309,7 @@ pqParseInput3(PGconn *conn)
                     * just as easy to handle it as part of the main loop.
                     * Save the data and continue processing.
                     */
-                   if (pqGetInt(&(conn->be_pid), 4, conn))
-                       return;
-                   if (pqGetInt(&(conn->be_key), 4, conn))
+                   if (getBackendKeyData(conn, msgLength))
                        return;
                    break;
                case PqMsg_RowDescription:
@@ -1524,6 +1523,46 @@ getParameterStatus(PGconn *conn)
    return 0;
 }
 
+/*
+ * parseInput subroutine to read a BackendKeyData message.
+ * Entry: 'v' message type and length have already been consumed.
+ * Exit: returns 0 if successfully consumed message.
+ *      returns EOF if not enough data.
+ */
+static int
+getBackendKeyData(PGconn *conn, int msgLength)
+{
+   uint8       cancel_key_len;
+
+   if (conn->be_cancel_key)
+   {
+       free(conn->be_cancel_key);
+       conn->be_cancel_key = NULL;
+       conn->be_cancel_key_len = 0;
+   }
+
+   if (pqGetInt(&(conn->be_pid), 4, conn))
+       return EOF;
+
+   cancel_key_len = 5 + msgLength - (conn->inCursor - conn->inStart);
+
+   conn->be_cancel_key = malloc(cancel_key_len);
+   if (conn->be_cancel_key == NULL)
+   {
+       libpq_append_conn_error(conn, "out of memory");
+       /* discard the message */
+       return EOF;
+   }
+   if (pqGetnchar(conn->be_cancel_key, cancel_key_len, conn))
+   {
+       free(conn->be_cancel_key);
+       conn->be_cancel_key = NULL;
+       return EOF;
+   }
+   conn->be_cancel_key_len = cancel_key_len;
+   return 0;
+}
+
 
 /*
  * Attempt to read a Notify response message.
index 232e0b00f7501e615126ac02ab8e1506c7b1d9a9..25de3e950551c3292d90a0d6534ccb31d7ae7a5b 100644 (file)
@@ -546,7 +546,8 @@ struct pg_conn
 
    /* Miscellaneous stuff */
    int         be_pid;         /* PID of backend --- needed for cancels */
-   int         be_key;         /* key of backend --- needed for cancels */
+   char       *be_cancel_key;  /* query cancellation key and its length */
+   uint16      be_cancel_key_len;
    pgParameterStatus *pstatus; /* ParameterStatus data */
    int         client_encoding;    /* encoding id */
    bool        std_strings;    /* standard_conforming_strings */
@@ -766,6 +767,10 @@ extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid,
                                 int result_is_int,
                                 const PQArgBlock *args, int nargs);
 
+/* === in fe-cancel.c === */
+
+extern int PQsendCancelRequest(PGconn *cancelConn);
+
 /* === in fe-misc.c === */
 
  /*
index 9691b8504189f7ad03d617dafc43586ba66a54fd..61524bdbd8f280ab9b96aeb239479c13f9ab5684 100644 (file)
@@ -49,11 +49,11 @@ for my $testname (@tests)
        push @extraargs, "-t" => $traceout;
    }
 
-   # Execute the test
+   # Execute the test using the latest protocol version.
    $node->command_ok(
        [
            'libpq_pipeline', @extraargs,
-           $testname, $node->connstr('postgres')
+           $testname, $node->connstr('postgres') . " max_protocol_version=latest"
        ],
        "libpq_pipeline $testname");
 
@@ -72,6 +72,14 @@ for my $testname (@tests)
    }
 }
 
+# There were changes to query cancellation in protocol version 3.2, so
+# test separately that it still works the old protocol version too.
+$node->command_ok(
+   [
+    'libpq_pipeline', 'cancel', $node->connstr('postgres') . " max_protocol_version=3.0"
+   ],
+   "libpq_pipeline cancel with protocol 3.0");
+
 $node->stop('fast');
 
 done_testing();