Fix possible double-release of spinlock in procsignal.c
authorMichael Paquier <michael@paquier.xyz>
Thu, 27 Feb 2025 00:43:06 +0000 (09:43 +0900)
committerMichael Paquier <michael@paquier.xyz>
Thu, 27 Feb 2025 00:43:06 +0000 (09:43 +0900)
9d9b9d46f3c5 has added spinlocks to protect the fields in ProcSignal
flags, introducing a code path in ProcSignalInit() where a spinlock
could be released twice if the pss_pid field of a ProcSignalSlot is
found as already set.  Multiple spinlock releases have no effect with
most spinlock implementations, but this could cause the code to run into
issues when the spinlock is acquired concurrently by a different
process.

This sanity check on pss_pid generates a LOG that can be delayed until
after the spinlock is released as, like older versions up to v17, the
code expects the initialization of the ProcSignalSlot to happen even if
pss_pid is found incorrect.  The code is changed so as the old pss_pid
is read while holding the slot's spinlock, with the LOG from the sanity
check generated after releasing the spinlock, preventing the double
release.

Author: Maksim Melnikov <m.melnikov@postgrespro.ru>
Co-authored-by: Maxim Orlov <orlovmg@gmail.com>
Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
Discussion: https://postgr.es/m/dca47527-2d8b-4e3b-b5a0-e2deb73371a4@postgrespro.ru

src/backend/storage/ipc/procsignal.c

index 7401b6e625e521ff5e00efd2c01ce9d6b81fdce6..7d201965503cbb25d325fce9b599998f1a989dcc 100644 (file)
@@ -167,6 +167,7 @@ ProcSignalInit(bool cancel_key_valid, int32 cancel_key)
 {
    ProcSignalSlot *slot;
    uint64      barrier_generation;
+   uint32      old_pss_pid;
 
    if (MyProcNumber < 0)
        elog(ERROR, "MyProcNumber not set");
@@ -174,14 +175,10 @@ ProcSignalInit(bool cancel_key_valid, int32 cancel_key)
        elog(ERROR, "unexpected MyProcNumber %d in ProcSignalInit (max %d)", MyProcNumber, NumProcSignalSlots);
    slot = &ProcSignal->psh_slot[MyProcNumber];
 
-   /* sanity check */
    SpinLockAcquire(&slot->pss_mutex);
-   if (pg_atomic_read_u32(&slot->pss_pid) != 0)
-   {
-       SpinLockRelease(&slot->pss_mutex);
-       elog(LOG, "process %d taking over ProcSignal slot %d, but it's not empty",
-            MyProcPid, MyProcNumber);
-   }
+
+   /* Value used for sanity check below */
+   old_pss_pid = pg_atomic_read_u32(&slot->pss_pid);
 
    /* Clear out any leftover signal reasons */
    MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));
@@ -208,6 +205,11 @@ ProcSignalInit(bool cancel_key_valid, int32 cancel_key)
 
    SpinLockRelease(&slot->pss_mutex);
 
+   /* Spinlock is released, do the check */
+   if (old_pss_pid != 0)
+       elog(LOG, "process %d taking over ProcSignal slot %d, but it's not empty",
+            MyProcPid, MyProcNumber);
+
    /* Remember slot location for CheckProcSignal */
    MyProcSignalSlot = slot;