From 3000ad8e1afcca05479ed2d5903214b96dc17227 Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Sat, 7 Mar 2026 00:00:38 +0100 Subject: [PATCH v5 4/5] fixup! Don't use deprecated and insecure PQcancel psql and other tools anymore Keep track of generation that should be cancelled. --- src/fe_utils/cancel.c | 46 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/src/fe_utils/cancel.c b/src/fe_utils/cancel.c index 5d645c554ab..caaf3e7c675 100644 --- a/src/fe_utils/cancel.c +++ b/src/fe_utils/cancel.c @@ -46,6 +46,7 @@ #include "postgres_fe.h" #include +#include #include #ifndef WIN32 @@ -85,9 +86,22 @@ static PGcancelConn *cancelConn = NULL; /* * Generation counter for cancelConn. Incremented each time cancelConn is - * changed. Used to detect if cancelConn was replaced while we were using it. + * changed (under cancelConnLock). Read by the signal handler (which can run + * on any thread) and by the cancel thread, so it needs to be atomic. */ -static uint64 cancelConnGeneration = 0; +static _Atomic sig_atomic_t cancelConnGeneration = 0; + +/* + * The generation that was current when SIGINT was received. The cancel + * thread compares this against cancelConnGeneration before sending a cancel + * request, so that a stale signal (from a query that already finished) does + * not cancel a subsequent query. + * + * Written by the signal handler (or Windows console handler) and read by + * the cancel thread, so it must be an atomic type for cross-thread safety. + * sig_atomic_t is guaranteed lock-free and thus safe in signal handlers. + */ +static _Atomic sig_atomic_t cancelConnGenerationDuringSignal = 0; /* * Mutex protecting cancelConn and cancelConnGeneration. @@ -151,22 +165,29 @@ static void SendCancelRequest(void) { PGcancelConn *cc; - uint64 generation; + sig_atomic_t generation; bool putConnectionBack = false; /* * Borrow the cancel connection from the global, setting it to NULL so * that SetCancelConn/ResetCancelConn won't free it while we're using it. + * + * Also check that the generation matches what the signal handler + * captured. If it doesn't, the main thread already moved on to a + * different query (or no query at all), so sending a cancel would be + * wrong. */ pthread_mutex_lock(&cancelConnLock); cc = cancelConn; - generation = cancelConnGeneration; + generation = atomic_load(&cancelConnGeneration); + if (cc == NULL || generation != atomic_load(&cancelConnGenerationDuringSignal)) + { + pthread_mutex_unlock(&cancelConnLock); + return; + } cancelConn = NULL; pthread_mutex_unlock(&cancelConnLock); - if (cc == NULL) - return; - write_stderr(cancel_sent_msg); if (!PQcancelBlocking(cc)) @@ -185,7 +206,7 @@ SendCancelRequest(void) * using it. */ pthread_mutex_lock(&cancelConnLock); - if (cancelConnGeneration == generation) + if (atomic_load(&cancelConnGeneration) == generation) { /* Generation unchanged, put it back for reuse */ cancelConn = cc; @@ -210,7 +231,7 @@ SetCancelConnInternal(PGcancelConn *newCancelConn) pthread_mutex_lock(&cancelConnLock); oldCancelConn = cancelConn; cancelConn = newCancelConn; - cancelConnGeneration++; + atomic_fetch_add(&cancelConnGeneration, 1); pthread_mutex_unlock(&cancelConnLock); if (oldCancelConn != NULL) @@ -256,6 +277,7 @@ consoleHandler(DWORD dwCtrlType) dwCtrlType == CTRL_BREAK_EVENT) { CancelRequested = true; + atomic_store(&cancelConnGenerationDuringSignal, atomic_load(&cancelConnGeneration)); if (cancel_callback != NULL) cancel_callback(); @@ -311,6 +333,12 @@ handle_sigint(SIGNAL_ARGS) CancelRequested = true; + /* + * Capture the current generation so the cancel thread can verify it's + * still sending the cancel for the right query. + */ + atomic_store(&cancelConnGenerationDuringSignal, atomic_load(&cancelConnGeneration)); + if (cancel_callback != NULL) cancel_callback(); -- 2.53.0