Skip to content

Commit e7428a9

Browse files
Add hardening to catch invalid TIDs in indexes.
Add hardening to the heapam index tuple deletion path to catch TIDs in index pages that point to a heap item that index tuples should never point to. The corruption we're trying to catch here is particularly tricky to detect, since it typically involves "extra" (corrupt) index tuples, as opposed to the absence of required index tuples in the index. For example, a heap TID from an index page that turns out to point to an LP_UNUSED item in the heap page has a good chance of being caught by one of the new checks. There is a decent chance that the recently fixed parallel VACUUM bug (see commit 9bacec1) would have been caught had that particular check been in place for Postgres 14. No backpatch of this extra hardening for now, though. Author: Peter Geoghegan <pg@bowt.ie> Reviewed-By: Andres Freund <andres@anarazel.de> Discussion: https://postgr.es/m/CAH2-Wzk-4_raTzawWGaiqNvkpwDXxv3y1AQhQyUeHfkU=tFCeA@mail.gmail.com
1 parent babbbb5 commit e7428a9

File tree

5 files changed

+76
-2
lines changed

5 files changed

+76
-2
lines changed

src/backend/access/heap/heapam.c

+65
Original file line numberDiff line numberDiff line change
@@ -7252,6 +7252,63 @@ index_delete_prefetch_buffer(Relation rel,
72527252
}
72537253
#endif
72547254

7255+
/*
7256+
* Helper function for heap_index_delete_tuples. Checks for index corruption
7257+
* involving an invalid TID in index AM caller's index page.
7258+
*
7259+
* This is an ideal place for these checks. The index AM must hold a buffer
7260+
* lock on the index page containing the TIDs we examine here, so we don't
7261+
* have to worry about concurrent VACUUMs at all. We can be sure that the
7262+
* index is corrupt when htid points directly to an LP_UNUSED item or
7263+
* heap-only tuple, which is not the case during standard index scans.
7264+
*/
7265+
static inline void
7266+
index_delete_check_htid(TM_IndexDeleteOp *delstate,
7267+
Page page, OffsetNumber maxoff,
7268+
ItemPointer htid, TM_IndexStatus *istatus)
7269+
{
7270+
OffsetNumber indexpagehoffnum = ItemPointerGetOffsetNumber(htid);
7271+
ItemId iid;
7272+
7273+
Assert(OffsetNumberIsValid(istatus->idxoffnum));
7274+
7275+
if (unlikely(indexpagehoffnum > maxoff))
7276+
ereport(ERROR,
7277+
(errcode(ERRCODE_INDEX_CORRUPTED),
7278+
errmsg_internal("heap tid from index tuple (%u,%u) points past end of heap page line pointer array at offset %u of block %u in index \"%s\"",
7279+
ItemPointerGetBlockNumber(htid),
7280+
indexpagehoffnum,
7281+
istatus->idxoffnum, delstate->iblknum,
7282+
RelationGetRelationName(delstate->irel))));
7283+
7284+
iid = PageGetItemId(page, indexpagehoffnum);
7285+
if (unlikely(!ItemIdIsUsed(iid)))
7286+
ereport(ERROR,
7287+
(errcode(ERRCODE_INDEX_CORRUPTED),
7288+
errmsg_internal("heap tid from index tuple (%u,%u) points to unused heap page item at offset %u of block %u in index \"%s\"",
7289+
ItemPointerGetBlockNumber(htid),
7290+
indexpagehoffnum,
7291+
istatus->idxoffnum, delstate->iblknum,
7292+
RelationGetRelationName(delstate->irel))));
7293+
7294+
if (ItemIdHasStorage(iid))
7295+
{
7296+
HeapTupleHeader htup;
7297+
7298+
Assert(ItemIdIsNormal(iid));
7299+
htup = (HeapTupleHeader) PageGetItem(page, iid);
7300+
7301+
if (unlikely(HeapTupleHeaderIsHeapOnly(htup)))
7302+
ereport(ERROR,
7303+
(errcode(ERRCODE_INDEX_CORRUPTED),
7304+
errmsg_internal("heap tid from index tuple (%u,%u) points to heap-only tuple at offset %u of block %u in index \"%s\"",
7305+
ItemPointerGetBlockNumber(htid),
7306+
indexpagehoffnum,
7307+
istatus->idxoffnum, delstate->iblknum,
7308+
RelationGetRelationName(delstate->irel))));
7309+
}
7310+
}
7311+
72557312
/*
72567313
* heapam implementation of tableam's index_delete_tuples interface.
72577314
*
@@ -7446,6 +7503,14 @@ heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
74467503
maxoff = PageGetMaxOffsetNumber(page);
74477504
}
74487505

7506+
/*
7507+
* In passing, detect index corruption involving an index page with a
7508+
* TID that points to a location in the heap that couldn't possibly be
7509+
* correct. We only do this with actual TIDs from caller's index page
7510+
* (not items reached by traversing through a HOT chain).
7511+
*/
7512+
index_delete_check_htid(delstate, page, maxoff, htid, istatus);
7513+
74497514
if (istatus->knowndeletable)
74507515
Assert(!delstate->bottomup && !istatus->promising);
74517516
else

src/backend/access/index/genam.c

+5-2
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ index_compute_xid_horizon_for_tuples(Relation irel,
303303

304304
Assert(nitems > 0);
305305

306+
delstate.irel = irel;
307+
delstate.iblknum = BufferGetBlockNumber(ibuf);
306308
delstate.bottomup = false;
307309
delstate.bottomupfreespace = 0;
308310
delstate.ndeltids = 0;
@@ -312,16 +314,17 @@ index_compute_xid_horizon_for_tuples(Relation irel,
312314
/* identify what the index tuples about to be deleted point to */
313315
for (int i = 0; i < nitems; i++)
314316
{
317+
OffsetNumber offnum = itemnos[i];
315318
ItemId iitemid;
316319

317-
iitemid = PageGetItemId(ipage, itemnos[i]);
320+
iitemid = PageGetItemId(ipage, offnum);
318321
itup = (IndexTuple) PageGetItem(ipage, iitemid);
319322

320323
Assert(ItemIdIsDead(iitemid));
321324

322325
ItemPointerCopy(&itup->t_tid, &delstate.deltids[i].tid);
323326
delstate.deltids[i].id = delstate.ndeltids;
324-
delstate.status[i].idxoffnum = InvalidOffsetNumber; /* unused */
327+
delstate.status[i].idxoffnum = offnum;
325328
delstate.status[i].knowndeletable = true; /* LP_DEAD-marked */
326329
delstate.status[i].promising = false; /* unused */
327330
delstate.status[i].freespace = 0; /* unused */

src/backend/access/nbtree/nbtdedup.c

+2
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,8 @@ _bt_bottomupdel_pass(Relation rel, Buffer buf, Relation heapRel,
348348
* concerning ourselves with avoiding work during the tableam call. Our
349349
* role in costing the bottom-up deletion process is strictly advisory.
350350
*/
351+
delstate.irel = rel;
352+
delstate.iblknum = BufferGetBlockNumber(buf);
351353
delstate.bottomup = true;
352354
delstate.bottomupfreespace = Max(BLCKSZ / 16, newitemsz);
353355
delstate.ndeltids = 0;

src/backend/access/nbtree/nbtinsert.c

+2
Original file line numberDiff line numberDiff line change
@@ -2810,6 +2810,8 @@ _bt_simpledel_pass(Relation rel, Buffer buffer, Relation heapRel,
28102810
&ndeadblocks);
28112811

28122812
/* Initialize tableam state that describes index deletion operation */
2813+
delstate.irel = rel;
2814+
delstate.iblknum = BufferGetBlockNumber(buffer);
28132815
delstate.bottomup = false;
28142816
delstate.bottomupfreespace = 0;
28152817
delstate.ndeltids = 0;

src/include/access/tableam.h

+2
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ typedef struct TM_IndexStatus
220220
*/
221221
typedef struct TM_IndexDeleteOp
222222
{
223+
Relation irel; /* Target index relation */
224+
BlockNumber iblknum; /* Index block number (for error reports) */
223225
bool bottomup; /* Bottom-up (not simple) deletion? */
224226
int bottomupfreespace; /* Bottom-up space target */
225227

0 commit comments

Comments
 (0)