@@ -155,11 +155,14 @@ static inline bool invariant_g_offset(BtreeCheckState *state, BTScanInsert key,
155155 OffsetNumber lowerbound );
156156static inline bool invariant_l_nontarget_offset (BtreeCheckState * state ,
157157 BTScanInsert key ,
158+ BlockNumber nontargetblock ,
158159 Page nontarget ,
159160 OffsetNumber upperbound );
160161static Page palloc_btree_page (BtreeCheckState * state , BlockNumber blocknum );
161162static inline BTScanInsert bt_mkscankey_pivotsearch (Relation rel ,
162163 IndexTuple itup );
164+ static ItemId PageGetItemIdCareful (BtreeCheckState * state , BlockNumber block ,
165+ Page page , OffsetNumber offset );
163166static inline ItemPointer BTreeTupleGetHeapTIDCareful (BtreeCheckState * state ,
164167 IndexTuple itup , bool nonpivot );
165168
@@ -710,7 +713,9 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level)
710713 ItemId itemid ;
711714
712715 /* Internal page -- downlink gets leftmost on next level */
713- itemid = PageGetItemId (state -> target , P_FIRSTDATAKEY (opaque ));
716+ itemid = PageGetItemIdCareful (state , state -> targetblock ,
717+ state -> target ,
718+ P_FIRSTDATAKEY (opaque ));
714719 itup = (IndexTuple ) PageGetItem (state -> target , itemid );
715720 nextleveldown .leftmost = BTreeInnerTupleGetDownLink (itup );
716721 nextleveldown .level = opaque -> btpo .level - 1 ;
@@ -837,26 +842,29 @@ bt_target_page_check(BtreeCheckState *state)
837842 * Check the number of attributes in high key. Note, rightmost page
838843 * doesn't contain a high key, so nothing to check
839844 */
840- if (!P_RIGHTMOST (topaque ) &&
841- !_bt_check_natts (state -> rel , state -> heapkeyspace , state -> target ,
842- P_HIKEY ))
845+ if (!P_RIGHTMOST (topaque ))
843846 {
844847 ItemId itemid ;
845848 IndexTuple itup ;
846849
847- itemid = PageGetItemId (state -> target , P_HIKEY );
848- itup = (IndexTuple ) PageGetItem (state -> target , itemid );
849-
850- ereport (ERROR ,
851- (errcode (ERRCODE_INDEX_CORRUPTED ),
852- errmsg ("wrong number of high key index tuple attributes in index \"%s\"" ,
853- RelationGetRelationName (state -> rel )),
854- errdetail_internal ("Index block=%u natts=%u block type=%s page lsn=%X/%X." ,
855- state -> targetblock ,
856- BTreeTupleGetNAtts (itup , state -> rel ),
857- P_ISLEAF (topaque ) ? "heap" : "index" ,
858- (uint32 ) (state -> targetlsn >> 32 ),
859- (uint32 ) state -> targetlsn )));
850+ /* Verify line pointer before checking tuple */
851+ itemid = PageGetItemIdCareful (state , state -> targetblock ,
852+ state -> target , P_HIKEY );
853+ if (!_bt_check_natts (state -> rel , state -> heapkeyspace , state -> target ,
854+ P_HIKEY ))
855+ {
856+ itup = (IndexTuple ) PageGetItem (state -> target , itemid );
857+ ereport (ERROR ,
858+ (errcode (ERRCODE_INDEX_CORRUPTED ),
859+ errmsg ("wrong number of high key index tuple attributes in index \"%s\"" ,
860+ RelationGetRelationName (state -> rel )),
861+ errdetail_internal ("Index block=%u natts=%u block type=%s page lsn=%X/%X." ,
862+ state -> targetblock ,
863+ BTreeTupleGetNAtts (itup , state -> rel ),
864+ P_ISLEAF (topaque ) ? "heap" : "index" ,
865+ (uint32 ) (state -> targetlsn >> 32 ),
866+ (uint32 ) state -> targetlsn )));
867+ }
860868 }
861869
862870 /*
@@ -876,7 +884,8 @@ bt_target_page_check(BtreeCheckState *state)
876884
877885 CHECK_FOR_INTERRUPTS ();
878886
879- itemid = PageGetItemId (state -> target , offset );
887+ itemid = PageGetItemIdCareful (state , state -> targetblock ,
888+ state -> target , offset );
880889 itup = (IndexTuple ) PageGetItem (state -> target , itemid );
881890 tupsize = IndexTupleSize (itup );
882891
@@ -1121,7 +1130,9 @@ bt_target_page_check(BtreeCheckState *state)
11211130 OffsetNumberNext (offset ));
11221131
11231132 /* Reuse itup to get pointed-to heap location of second item */
1124- itemid = PageGetItemId (state -> target , OffsetNumberNext (offset ));
1133+ itemid = PageGetItemIdCareful (state , state -> targetblock ,
1134+ state -> target ,
1135+ OffsetNumberNext (offset ));
11251136 itup = (IndexTuple ) PageGetItem (state -> target , itemid );
11261137 nhtid = psprintf ("(%u,%u)" ,
11271138 ItemPointerGetBlockNumberNoCheck (& (itup -> t_tid )),
@@ -1406,7 +1417,8 @@ bt_right_page_check_scankey(BtreeCheckState *state)
14061417 if (P_ISLEAF (opaque ) && nline >= P_FIRSTDATAKEY (opaque ))
14071418 {
14081419 /* Return first data item (if any) */
1409- rightitem = PageGetItemId (rightpage , P_FIRSTDATAKEY (opaque ));
1420+ rightitem = PageGetItemIdCareful (state , targetnext , rightpage ,
1421+ P_FIRSTDATAKEY (opaque ));
14101422 }
14111423 else if (!P_ISLEAF (opaque ) &&
14121424 nline >= OffsetNumberNext (P_FIRSTDATAKEY (opaque )))
@@ -1415,8 +1427,8 @@ bt_right_page_check_scankey(BtreeCheckState *state)
14151427 * Return first item after the internal page's "negative infinity"
14161428 * item
14171429 */
1418- rightitem = PageGetItemId ( rightpage ,
1419- OffsetNumberNext (P_FIRSTDATAKEY (opaque )));
1430+ rightitem = PageGetItemIdCareful ( state , targetnext , rightpage ,
1431+ OffsetNumberNext (P_FIRSTDATAKEY (opaque )));
14201432 }
14211433 else
14221434 {
@@ -1576,7 +1588,8 @@ bt_downlink_check(BtreeCheckState *state, BTScanInsert targetkey,
15761588 if (offset_is_negative_infinity (copaque , offset ))
15771589 continue ;
15781590
1579- if (!invariant_l_nontarget_offset (state , targetkey , child , offset ))
1591+ if (!invariant_l_nontarget_offset (state , targetkey , childblock , child ,
1592+ offset ))
15801593 ereport (ERROR ,
15811594 (errcode (ERRCODE_INDEX_CORRUPTED ),
15821595 errmsg ("down-link lower bound invariant violated for index \"%s\"" ,
@@ -1687,7 +1700,8 @@ bt_downlink_missing_check(BtreeCheckState *state)
16871700 RelationGetRelationName (state -> rel ));
16881701
16891702 level = topaque -> btpo .level ;
1690- itemid = PageGetItemId (state -> target , P_FIRSTDATAKEY (topaque ));
1703+ itemid = PageGetItemIdCareful (state , state -> targetblock , state -> target ,
1704+ P_FIRSTDATAKEY (topaque ));
16911705 itup = (IndexTuple ) PageGetItem (state -> target , itemid );
16921706 childblk = BTreeInnerTupleGetDownLink (itup );
16931707 for (;;)
@@ -1711,7 +1725,8 @@ bt_downlink_missing_check(BtreeCheckState *state)
17111725 level - 1 , copaque -> btpo .level )));
17121726
17131727 level = copaque -> btpo .level ;
1714- itemid = PageGetItemId (child , P_FIRSTDATAKEY (copaque ));
1728+ itemid = PageGetItemIdCareful (state , childblk , child ,
1729+ P_FIRSTDATAKEY (copaque ));
17151730 itup = (IndexTuple ) PageGetItem (child , itemid );
17161731 childblk = BTreeInnerTupleGetDownLink (itup );
17171732 /* Be slightly more pro-active in freeing this memory, just in case */
@@ -1760,7 +1775,7 @@ bt_downlink_missing_check(BtreeCheckState *state)
17601775 */
17611776 if (P_ISHALFDEAD (copaque ) && !P_RIGHTMOST (copaque ))
17621777 {
1763- itemid = PageGetItemId ( child , P_HIKEY );
1778+ itemid = PageGetItemIdCareful ( state , childblk , child , P_HIKEY );
17641779 itup = (IndexTuple ) PageGetItem (child , itemid );
17651780 if (BTreeTupleGetTopParent (itup ) == state -> targetblock )
17661781 return ;
@@ -2087,17 +2102,23 @@ offset_is_negative_infinity(BTPageOpaque opaque, OffsetNumber offset)
20872102 * Does the invariant hold that the key is strictly less than a given upper
20882103 * bound offset item?
20892104 *
2105+ * Verifies line pointer on behalf of caller.
2106+ *
20902107 * If this function returns false, convention is that caller throws error due
20912108 * to corruption.
20922109 */
20932110static inline bool
20942111invariant_l_offset (BtreeCheckState * state , BTScanInsert key ,
20952112 OffsetNumber upperbound )
20962113{
2114+ ItemId itemid ;
20972115 int32 cmp ;
20982116
20992117 Assert (key -> pivotsearch );
21002118
2119+ /* Verify line pointer before checking tuple */
2120+ itemid = PageGetItemIdCareful (state , state -> targetblock , state -> target ,
2121+ upperbound );
21012122 /* pg_upgrade'd indexes may legally have equal sibling tuples */
21022123 if (!key -> heapkeyspace )
21032124 return invariant_leq_offset (state , key , upperbound );
@@ -2116,13 +2137,11 @@ invariant_l_offset(BtreeCheckState *state, BTScanInsert key,
21162137 if (cmp == 0 )
21172138 {
21182139 BTPageOpaque topaque ;
2119- ItemId itemid ;
21202140 IndexTuple ritup ;
21212141 int uppnkeyatts ;
21222142 ItemPointer rheaptid ;
21232143 bool nonpivot ;
21242144
2125- itemid = PageGetItemId (state -> target , upperbound );
21262145 ritup = (IndexTuple ) PageGetItem (state -> target , itemid );
21272146 topaque = (BTPageOpaque ) PageGetSpecialPointer (state -> target );
21282147 nonpivot = P_ISLEAF (topaque ) && upperbound >= P_FIRSTDATAKEY (topaque );
@@ -2145,6 +2164,9 @@ invariant_l_offset(BtreeCheckState *state, BTScanInsert key,
21452164 * Does the invariant hold that the key is less than or equal to a given upper
21462165 * bound offset item?
21472166 *
2167+ * Caller should have verified that upperbound's item pointer is consistent
2168+ * using PageGetItemIdCareful() call.
2169+ *
21482170 * If this function returns false, convention is that caller throws error due
21492171 * to corruption.
21502172 */
@@ -2165,6 +2187,9 @@ invariant_leq_offset(BtreeCheckState *state, BTScanInsert key,
21652187 * Does the invariant hold that the key is strictly greater than a given lower
21662188 * bound offset item?
21672189 *
2190+ * Caller should have verified that lowerbound's item pointer is consistent
2191+ * using PageGetItemIdCareful() call.
2192+ *
21682193 * If this function returns false, convention is that caller throws error due
21692194 * to corruption.
21702195 */
@@ -2199,19 +2224,24 @@ invariant_g_offset(BtreeCheckState *state, BTScanInsert key,
21992224 *
22002225 * Caller's non-target page is a child page of the target, checked as part of
22012226 * checking a property of the target page (i.e. the key comes from the
2202- * target).
2227+ * target). Verifies line pointer on behalf of caller.
22032228 *
22042229 * If this function returns false, convention is that caller throws error due
22052230 * to corruption.
22062231 */
22072232static inline bool
22082233invariant_l_nontarget_offset (BtreeCheckState * state , BTScanInsert key ,
2209- Page nontarget , OffsetNumber upperbound )
2234+ BlockNumber nontargetblock , Page nontarget ,
2235+ OffsetNumber upperbound )
22102236{
2237+ ItemId itemid ;
22112238 int32 cmp ;
22122239
22132240 Assert (key -> pivotsearch );
22142241
2242+ /* Verify line pointer before checking tuple */
2243+ itemid = PageGetItemIdCareful (state , nontargetblock , nontarget ,
2244+ upperbound );
22152245 cmp = _bt_compare (state -> rel , key , nontarget , upperbound );
22162246
22172247 /* pg_upgrade'd indexes may legally have equal sibling tuples */
@@ -2221,14 +2251,12 @@ invariant_l_nontarget_offset(BtreeCheckState *state, BTScanInsert key,
22212251 /* See invariant_l_offset() for an explanation of this extra step */
22222252 if (cmp == 0 )
22232253 {
2224- ItemId itemid ;
22252254 IndexTuple child ;
22262255 int uppnkeyatts ;
22272256 ItemPointer childheaptid ;
22282257 BTPageOpaque copaque ;
22292258 bool nonpivot ;
22302259
2231- itemid = PageGetItemId (nontarget , upperbound );
22322260 child = (IndexTuple ) PageGetItem (nontarget , itemid );
22332261 copaque = (BTPageOpaque ) PageGetSpecialPointer (nontarget );
22342262 nonpivot = P_ISLEAF (copaque ) && upperbound >= P_FIRSTDATAKEY (copaque );
@@ -2426,6 +2454,55 @@ bt_mkscankey_pivotsearch(Relation rel, IndexTuple itup)
24262454 return skey ;
24272455}
24282456
2457+ /*
2458+ * PageGetItemId() wrapper that validates returned line pointer.
2459+ *
2460+ * Buffer page/page item access macros generally trust that line pointers are
2461+ * not corrupt, which might cause problems for verification itself. For
2462+ * example, there is no bounds checking in PageGetItem(). Passing it a
2463+ * corrupt line pointer can cause it to return a tuple/pointer that is unsafe
2464+ * to dereference.
2465+ *
2466+ * Validating line pointers before tuples avoids undefined behavior and
2467+ * assertion failures with corrupt indexes, making the verification process
2468+ * more robust and predictable.
2469+ */
2470+ static ItemId
2471+ PageGetItemIdCareful (BtreeCheckState * state , BlockNumber block , Page page ,
2472+ OffsetNumber offset )
2473+ {
2474+ ItemId itemid = PageGetItemId (page , offset );
2475+
2476+ if (ItemIdGetOffset (itemid ) + ItemIdGetLength (itemid ) >
2477+ BLCKSZ - sizeof (BTPageOpaqueData ))
2478+ ereport (ERROR ,
2479+ (errcode (ERRCODE_INDEX_CORRUPTED ),
2480+ errmsg ("line pointer points past end of tuple space in index \"%s\"" ,
2481+ RelationGetRelationName (state -> rel )),
2482+ errdetail_internal ("Index tid=(%u,%u) lp_off=%u, lp_len=%u lp_flags=%u." ,
2483+ block , offset , ItemIdGetOffset (itemid ),
2484+ ItemIdGetLength (itemid ),
2485+ ItemIdGetFlags (itemid ))));
2486+
2487+ /*
2488+ * Verify that line pointer isn't LP_REDIRECT or LP_UNUSED, since nbtree
2489+ * never uses either. Verify that line pointer has storage, too, since
2490+ * even LP_DEAD items should within nbtree.
2491+ */
2492+ if (ItemIdIsRedirected (itemid ) || !ItemIdIsUsed (itemid ) ||
2493+ ItemIdGetLength (itemid ) == 0 )
2494+ ereport (ERROR ,
2495+ (errcode (ERRCODE_INDEX_CORRUPTED ),
2496+ errmsg ("invalid line pointer storage in index \"%s\"" ,
2497+ RelationGetRelationName (state -> rel )),
2498+ errdetail_internal ("Index tid=(%u,%u) lp_off=%u, lp_len=%u lp_flags=%u." ,
2499+ block , offset , ItemIdGetOffset (itemid ),
2500+ ItemIdGetLength (itemid ),
2501+ ItemIdGetFlags (itemid ))));
2502+
2503+ return itemid ;
2504+ }
2505+
24292506/*
24302507 * BTreeTupleGetHeapTID() wrapper that lets caller enforce that a heap TID must
24312508 * be present in cases where that is mandatory.
0 commit comments