From 63c3b469d8e3addbe092861b06fb251afa4ac32a Mon Sep 17 00:00:00 2001
From: JoongHyuk Shin <sjh910805@gmail.com>
Date: Fri, 17 Apr 2026 15:07:11 +0900
Subject: [PATCH] Fix TOCTOU race in ReplicationSlotsComputeRequiredLSN()

ReplicationSlotsComputeRequiredLSN() released ReplicationSlotControlLock
before calling XLogSetReplicationSlotMinimumLSN(), creating a window
where a concurrent backend could compute a correct (lower) minimum LSN,
only for the first backend to overwrite it with a stale (higher) value.
This could lead to premature WAL removal.

The exact same pattern was already fixed for the xmin variant in commit
2a5225b99d7, which moved the lock release in
ReplicationSlotsComputeRequiredXmin() to after the global xmin update.
The LSN function was missed in that fix.

Move LWLockRelease() to after XLogSetReplicationSlotMinimumLSN() so the
lock is held for the entire compute-and-update sequence, matching the
xmin function's behavior.

Author: JoongHyuk Shin <sjh910805@gmail.com>
---
 src/backend/replication/slot.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 83fcde74718..0d87f0fd39d 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1312,6 +1312,11 @@ ReplicationSlotsComputeRequiredLSN(void)
 
 	Assert(ReplicationSlotCtl != NULL);
 
+	/*
+	 * Hold ReplicationSlotControlLock until after updating the minimum LSN.
+	 * Without this, a concurrent backend could compute a correct (lower)
+	 * minimum and then have it overwritten by our stale (higher) value.
+	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 	for (i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
@@ -1357,9 +1362,9 @@ ReplicationSlotsComputeRequiredLSN(void)
 			 restart_lsn < min_required))
 			min_required = restart_lsn;
 	}
-	LWLockRelease(ReplicationSlotControlLock);
 
 	XLogSetReplicationSlotMinimumLSN(min_required);
+	LWLockRelease(ReplicationSlotControlLock);
 }
 
 /*
-- 
2.52.0

