From 0ea74fd41e404bc35a3d03cc46dc5c1f0d7e0d69 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Wed, 2 Sep 2026 21:59:36 +0000
Subject: [PATCH v3 1/3] Fix LockHasWaiters() crash for fast-path locks.

LockHasWaiters() assumes that the LOCALLOCK's lock and proclock
pointers are populated, but this is not the case for locks
acquired via the fast-path optimization. Weak relation locks
(those below ShareUpdateExclusiveLock, including AccessShareLock)
are not stored in the shared lock hash table, leaving the
LOCALLOCK entry with lock = NULL and proclock = NULL.

If LockHasWaiters() is called for such a lock, it dereferences
those NULL pointers when reading proclock->holdMask and
lock->waitMask, causing a segfault.

Having LockHasWaiters() transfer the lock into the main lock table
with FastPathGetRelationLockEntry() would avoid the crash, but
that gives a read-only check the side effect of adding entries to
the main lock table. It is also unnecessary, because if nobody
else has moved our lock into the main lock table, it has no
waiters.

Fix by looking up the main lock table when the LOCALLOCK pointers
are NULL. If no entry is found, the lock is still held via the
fast path and cannot have any waiters, so we return false without
moving it. If an entry is found, some backend already transferred
the lock, and we re-find the lock and proclock as LockRelease()
does.

Reported-by: Satyanarayana Narlapuram <satyanarlapuram@gmail.com>
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Co-authored-by: Satyanarayana Narlapuram <satyanarlapuram@gmail.com>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://www.postgresql.org/message-id/flat/CAHg%2BQDe_%3DZahnRx37bzrqYenKn_S5YDQ00fTfwe-ZUmjqO%3DqLg%40mail.gmail.com
---
 src/backend/storage/lmgr/lock.c | 40 ++++++++++++++++++++++++++++++---
 1 file changed, 37 insertions(+), 3 deletions(-)

diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index cf5c98c8176..838a5f251b5 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -743,11 +743,45 @@ LockHasWaiters(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
 	LWLockAcquire(partitionLock, LW_SHARED);
 
 	/*
-	 * We don't need to re-find the lock or proclock, since we kept their
-	 * addresses in the locallock table, and they couldn't have been removed
-	 * while we were holding a lock on them.
+	 * Normally we can rely on the lock and proclock addresses kept in the
+	 * locallock table. But if the lock was acquired via the fast path, those
+	 * pointers are NULL, because the lock was never entered in the shared
+	 * lock table. A fast-path lock is a weak relation lock, and it can only
+	 * gain a waiter if some backend requests a conflicting (strong) lock, and
+	 * that request first moves all matching fast-path locks into the shared
+	 * table (see FastPathTransferRelationLocks()). So if we still find no
+	 * shared lock entry, the lock cannot have any waiters, and we return
+	 * false without moving it. If another backend did move it, look up the
+	 * lock and proclock here, the same way LockRelease() does.
 	 */
 	lock = locallock->lock;
+	if (!lock)
+	{
+		PROCLOCKTAG proclocktag;
+
+		Assert(EligibleForRelationFastPath(locktag, lockmode));
+		lock = (LOCK *) hash_search_with_hash_value(LockMethodLockHash,
+													locktag,
+													locallock->hashcode,
+													HASH_FIND,
+													NULL);
+		if (!lock)
+		{
+			/* Still fast-path only, so nobody could be waiting on it. */
+			LWLockRelease(partitionLock);
+			return false;
+		}
+		locallock->lock = lock;
+
+		proclocktag.myLock = lock;
+		proclocktag.myProc = MyProc;
+		locallock->proclock = (PROCLOCK *) hash_search(LockMethodProcLockHash,
+													   &proclocktag,
+													   HASH_FIND,
+													   NULL);
+		if (!locallock->proclock)
+			elog(ERROR, "failed to re-find shared proclock object");
+	}
 	LOCK_PRINT("LockHasWaiters: found", lock, lockmode);
 	proclock = locallock->proclock;
 	PROCLOCK_PRINT("LockHasWaiters: found", proclock);
-- 
2.47.3

