From 66e86a8bedaff7c9d47f05b7e025c12f8a9747ac Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 23 Sep 2026 11:59:53 -0400
Subject: [PATCH v2 2/3] Read visibility map pages with RBM_ZERO_ON_ERROR in VM
 clear redo

ed62d26caca started registering VM blocks when clearing the VM which is
required for protection against torn pages as well as for correct
incremental backups. However, it read the VM pages in recovery with
RBM_NORMAL which errors out when it encounters a corrupt page. This is
usually desirable, however, we still retain code paths that modify the
VM in recovery without the block having been registered. A crash while
modifying the VM page could lead to a corrupt page and no FPI to recover
it. As long as we can trivially produce corrupt pages during recovery
through our own redo mechanism, we shouldn't error out when reading a
corrupt VM page.

Make clearing the VM read the page with RBM_ZERO_ON_ERROR. This is
consistent with the VM's other redo paths which set the VM bit
(heap_xlog_prune_freeze() and heap_xlog_multi_insert()).

Backpatch-through: 17
---
 src/backend/access/heap/heapam_xlog.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/heap/heapam_xlog.c b/src/backend/access/heap/heapam_xlog.c
index c65804a6256..4d99d99080b 100644
--- a/src/backend/access/heap/heapam_xlog.c
+++ b/src/backend/access/heap/heapam_xlog.c
@@ -85,8 +85,9 @@ heap_xlog_vm_clear(XLogReaderState *record,
 	 * read it. These will either apply an FPI or indicate that we should
 	 * clear the requested bits ourselves.
 	 */
-	if (XLogReadBufferForRedo(record, wal_vm_block_id,
-							  &vmbuffer) == BLK_NEEDS_REDO)
+	if (XLogReadBufferForRedoExtended(record, wal_vm_block_id,
+									  RBM_ZERO_ON_ERROR, false,
+									  &vmbuffer) == BLK_NEEDS_REDO)
 	{
 		if (visibilitymap_clear(reln, heap_blkno, vmbuffer, flags))
 			PageSetLSN(BufferGetPage(vmbuffer), lsn);
@@ -850,8 +851,9 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 
 		Assert(xlrec->flags & XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED);
 
-		if (XLogReadBufferForRedo(record, HEAP_UPDATE_BLKREF_VM_NEW,
-								  &vmbuffer_new) == BLK_NEEDS_REDO)
+		if (XLogReadBufferForRedoExtended(record, HEAP_UPDATE_BLKREF_VM_NEW,
+										  RBM_ZERO_ON_ERROR, false,
+										  &vmbuffer_new) == BLK_NEEDS_REDO)
 		{
 			/*
 			 * If both the old and new heap pages were all-visible and their
@@ -888,8 +890,9 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 
 		Assert(xlrec->flags & XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED);
 
-		if (XLogReadBufferForRedo(record, HEAP_UPDATE_BLKREF_VM_OLD,
-								  &vmbuffer_old) == BLK_NEEDS_REDO)
+		if (XLogReadBufferForRedoExtended(record, HEAP_UPDATE_BLKREF_VM_OLD,
+										  RBM_ZERO_ON_ERROR, false,
+										  &vmbuffer_old) == BLK_NEEDS_REDO)
 		{
 			if (visibilitymap_clear(reln, oldblk, vmbuffer_old,
 									VISIBILITYMAP_VALID_BITS))
-- 
2.43.0

