From 2c7f2b2545782dae405939d19140b675887a89b3 Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Tue, 16 Dec 2025 15:30:49 +0900
Subject: [PATCH v3] Fix segfault from releasing locks in detached DSM segments

If a FATAL error occurs while holding a lock in a DSM segment (such
as a dshash lock) and the process is not in a transaction, a
segmentation fault can occur during process exit.

The problem sequence is:

 1. Process acquires a lock in a DSM segment (e.g., via dshash)
 2. FATAL error occurs outside transaction context
 3. proc_exit() begins, calling before_shmem_exit callbacks
 4. dsm_backend_shutdown() detaches all DSM segments
 5. Later, on_shmem_exit callbacks run
 6. ProcKill() calls LWLockReleaseAll()
 7. Segfault: the lock being released is in unmapped memory

This only manifests outside transaction contexts because
AbortCurrentTransaction() calls LWLockReleaseAll() during transaction
abort, releasing locks before DSM cleanup. Background workers and
other non-transactional code paths are vulnerable.

Fix by calling LWLockReleaseAll() unconditionally at the start of
shmem_exit(), before any callbacks run. This ensures locks are
released while their memory is still mapped.

This is safe because after an error outside a transaction, any held
LWLocks are in an unpredictable state anyway, so before_shmem_exit
callbacks cannot safely acquire new locks. Releasing early prevents
access to potentially detached memory without breaking existing
callback functionality.

Also add a comment noting that LWLockReleaseAll() must be safe to
call before LWLock initialization (which it is, since
num_held_lwlocks will be 0).

Reported-by: Rahila Syed <rahilasyed90@gmail.com>
Author: Rahila Syed <rahilasyed90@gmail.com>
Reviewed-by: Amit Langote <amitlangote09@gmail.com>
Reviewed-by: Dilip Kumar <dilipbalaut@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/CAH2L28uSvyiosL+kaic9249jRVoQiQF6JOnaCitKFq=xiFzX3g@mail.gmail.com
Backpatch-through: 14
---
 src/backend/storage/ipc/ipc.c     | 10 ++++++++--
 src/backend/storage/lmgr/lwlock.c |  4 ++++
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 2704e80b3a7..eb3185f1f59 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -29,6 +29,7 @@
 #endif
 #include "storage/dsm.h"
 #include "storage/ipc.h"
+#include "storage/lwlock.h"
 #include "tcop/tcopprot.h"
 
 
@@ -229,13 +230,18 @@ shmem_exit(int code)
 {
 	shmem_exit_inprogress = true;
 
+	/*
+	 * Release any LWLocks we might be holding, before running callbacks that
+	 * may detach the memory containing those locks.
+	 */
+	LWLockReleaseAll();
+
 	/*
 	 * Call before_shmem_exit callbacks.
 	 *
 	 * These should be things that need most of the system to still be up and
 	 * working, such as cleanup of temp relations, which requires catalog
-	 * access; or things that need to be completed because later cleanup steps
-	 * depend on them, such as releasing lwlocks.
+	 * access.
 	 */
 	elog(DEBUG3, "shmem_exit(%d): %d before_shmem_exit callbacks to make",
 		 code, before_shmem_exit_index);
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 255cfa8fa95..4cbbbce2cbc 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -1940,6 +1940,10 @@ LWLockReleaseClearVar(LWLock *lock, pg_atomic_uint64 *valptr, uint64 val)
  * unchanged by this operation.  This is necessary since InterruptHoldoffCount
  * has been set to an appropriate level earlier in error recovery. We could
  * decrement it below zero if we allow it to drop for each released lock!
+ *
+ * Note that this function must be safe to call even before the LWLock
+ * subsystem has been initialized (e.g., during early startup failures).
+ * In that case, num_held_lwlocks will be 0 and we do nothing.
  */
 void
 LWLockReleaseAll(void)
-- 
2.47.3

