]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Further harden nbtree posting split code.
authorPeter Geoghegan <pg@bowt.ie>
Wed, 27 Oct 2021 19:10:43 +0000 (12:10 -0700)
committerPeter Geoghegan <pg@bowt.ie>
Wed, 27 Oct 2021 19:10:43 +0000 (12:10 -0700)
Add more defensive checks around posting list split code.  These should
detect corruption involving duplicate table TIDs earlier and more
reliably than any existing check.

Follow up to commit 8f72bbac.

Discussion: https://postgr.es/m/CAH2-WzkrSY_kjyd1_M5xJK1uM0govJXMxPn8JUSvwcUOiHuWVw@mail.gmail.com
Backpatch: 13-, where nbtree deduplication was introduced.

src/backend/access/nbtree/nbtinsert.c
src/backend/access/nbtree/nbtsearch.c

index b86c122763ebb784a845f0ef8d2d3c18356bac6f..319ff5b286c4c98ccdbf60b5b5340be8f74c28c1 100644 (file)
@@ -1157,10 +1157,30 @@ _bt_insertonpg(Relation rel,
                 * its post-split version is treated as an extra step in either the
                 * insert or page split critical section.
                 */
-               Assert(P_ISLEAF(lpageop) && !ItemIdIsDead(itemid));
-               Assert(itup_key->heapkeyspace && itup_key->allequalimage);
+               Assert(P_ISLEAF(lpageop) &&
+                          itup_key->heapkeyspace && itup_key->allequalimage);
                oposting = (IndexTuple) PageGetItem(page, itemid);
 
+               /*
+                * postingoff value comes from earlier call to _bt_binsrch_posting().
+                * Its binary search might think that a plain tuple must be a posting
+                * list tuple that needs to be split.  This can happen with corruption
+                * involving an existing plain tuple that is a duplicate of the new
+                * item, up to and including its table TID.  Check for that here in
+                * passing.
+                *
+                * Also verify that our caller has made sure that the existing posting
+                * list tuple does not have its LP_DEAD bit set.
+                */
+               if (!BTreeTupleIsPosting(oposting) || ItemIdIsDead(itemid))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INDEX_CORRUPTED),
+                                        errmsg_internal("table tid from new index tuple (%u,%u) overlaps with invalid duplicate tuple at offset %u of block %u in index \"%s\"",
+                                                                        ItemPointerGetBlockNumber(&itup->t_tid),
+                                                                        ItemPointerGetOffsetNumber(&itup->t_tid),
+                                                                        BufferGetBlockNumber(buf), newitemoff,
+                                                                        RelationGetRelationName(rel))));
+
                /* use a mutable copy of itup as our itup from here on */
                origitup = itup;
                itup = CopyIndexTuple(origitup);
index 4de452357c1ceded7ab97957a3bd7b4042d43d38..8b2aad4d2b2b4ef19f775c9f12115e987f260ba0 100644 (file)
@@ -530,7 +530,24 @@ _bt_binsrch_insert(Relation rel, BTInsertState insertstate)
                 * infrequently.
                 */
                if (unlikely(result == 0 && key->scantid != NULL))
+               {
+                       /*
+                        * postingoff should never be set more than once per leaf page
+                        * binary search.  That would mean that there are duplicate table
+                        * TIDs in the index, which is never okay.  Check for that here.
+                        */
+                       if (insertstate->postingoff != 0)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INDEX_CORRUPTED),
+                                                errmsg_internal("table tid from new index tuple (%u,%u) cannot find insert offset between offsets %u and %u of block %u in index \"%s\"",
+                                                                                ItemPointerGetBlockNumber(key->scantid),
+                                                                                ItemPointerGetOffsetNumber(key->scantid),
+                                                                                low, stricthigh,
+                                                                                BufferGetBlockNumber(insertstate->buf),
+                                                                                RelationGetRelationName(rel))));
+
                        insertstate->postingoff = _bt_binsrch_posting(key, page, mid);
+               }
        }
 
        /*