From 5d84c76021cb5a7591be848cd0004b8fd9453d1c Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Wed, 23 Sep 2026 18:20:49 -0400 Subject: [PATCH] Add VM corruption check to verify_heapam() Detect heap pages whose PD_ALL_VISIBLE flag is clear while either visibility map bit is set. This inconsistent state can cause incorrect index-only scan results and is not currently reported by verify_heapam(). This also helps users who have access to amcheck but not pg_visibility to diagnose corruption. Author: Melanie Plageman Reviewed-by: Andres Freund Discussion: https://postgr.es/m/66mqpfyti3qhfttcsv6r2lbvqqd32rrmpn6i47ovrsnvguts46@gou54xc5jld3 --- contrib/amcheck/verify_heapam.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c index 7be2422f8ce..c6e272ff0ec 100644 --- a/contrib/amcheck/verify_heapam.c +++ b/contrib/amcheck/verify_heapam.c @@ -482,6 +482,7 @@ verify_heapam(PG_FUNCTION_ARGS) while ((ctx.buffer = read_stream_next_buffer(stream, NULL)) != InvalidBuffer) { + uint8 vmbits; OffsetNumber maxoff; OffsetNumber predecessor[MaxOffsetNumber]; OffsetNumber successor[MaxOffsetNumber]; @@ -500,6 +501,23 @@ verify_heapam(PG_FUNCTION_ARGS) ctx.blkno = BufferGetBlockNumber(ctx.buffer); ctx.page = BufferGetPage(ctx.buffer); + /* + * It is corruption if PD_ALL_VISIBLE is clear while either VM bit is + * set. Missing VM pages are treated as having no bits set. VM pages + * that fail page verification are read with RBM_ZERO_ON_ERROR, so + * those failures are not reported as corruption rows here. + */ + vmbits = visibilitymap_get_status(ctx.rel, ctx.blkno, &vmbuffer); + + if (!PageIsAllVisible(ctx.page) && + (vmbits & VISIBILITYMAP_VALID_BITS) != 0) + { + ctx.offnum = InvalidOffsetNumber; + ctx.attnum = -1; + report_corruption(&ctx, + psprintf("page is not marked all-visible in page header but visibility map bit is set")); + } + /* Perform tuple checks */ maxoff = PageGetMaxOffsetNumber(ctx.page); for (ctx.offnum = FirstOffsetNumber; ctx.offnum <= maxoff;