@@ -155,11 +155,14 @@ static inline bool invariant_g_offset(BtreeCheckState *state, BTScanInsert key,
155155OffsetNumber lowerbound );
156156static inline bool invariant_l_nontarget_offset (BtreeCheckState * state ,
157157BTScanInsert key ,
158+ BlockNumber nontargetblock ,
158159Page nontarget ,
159160OffsetNumber upperbound );
160161static Page palloc_btree_page (BtreeCheckState * state ,BlockNumber blocknum );
161162static inline BTScanInsert bt_mkscankey_pivotsearch (Relation rel ,
162163IndexTuple itup );
164+ static ItemId PageGetItemIdCareful (BtreeCheckState * state ,BlockNumber block ,
165+ Page page ,OffsetNumber offset );
163166static inline ItemPointer BTreeTupleGetHeapTIDCareful (BtreeCheckState * state ,
164167IndexTuple itup ,bool nonpivot );
165168
@@ -710,7 +713,9 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level)
710713ItemId 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 ));
714719itup = (IndexTuple )PageGetItem (state -> target ,itemid );
715720nextleveldown .leftmost = BTreeInnerTupleGetDownLink (itup );
716721nextleveldown .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{
844847ItemId itemid ;
845848IndexTuple 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
877885CHECK_FOR_INTERRUPTS ();
878886
879- itemid = PageGetItemId (state -> target ,offset );
887+ itemid = PageGetItemIdCareful (state ,state -> targetblock ,
888+ state -> target ,offset );
880889itup = (IndexTuple )PageGetItem (state -> target ,itemid );
881890tupsize = IndexTupleSize (itup );
882891
@@ -1121,7 +1130,9 @@ bt_target_page_check(BtreeCheckState *state)
11211130OffsetNumberNext (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 ));
11251136itup = (IndexTuple )PageGetItem (state -> target ,itemid );
11261137nhtid = psprintf ("(%u,%u)" ,
11271138ItemPointerGetBlockNumberNoCheck (& (itup -> t_tid )),
@@ -1406,7 +1417,8 @@ bt_right_page_check_scankey(BtreeCheckState *state)
14061417if (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}
14111423else if (!P_ISLEAF (opaque )&&
14121424nline >=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}
14211433else
14221434{
@@ -1576,7 +1588,8 @@ bt_downlink_check(BtreeCheckState *state, BTScanInsert targetkey,
15761588if (offset_is_negative_infinity (copaque ,offset ))
15771589continue ;
15781590
1579- if (!invariant_l_nontarget_offset (state ,targetkey ,child ,offset ))
1591+ if (!invariant_l_nontarget_offset (state ,targetkey ,childblock ,child ,
1592+ offset ))
15801593ereport (ERROR ,
15811594(errcode (ERRCODE_INDEX_CORRUPTED ),
15821595errmsg ("down-link lower bound invariant violated for index \"%s\"" ,
@@ -1687,7 +1700,8 @@ bt_downlink_missing_check(BtreeCheckState *state)
16871700RelationGetRelationName (state -> rel ));
16881701
16891702level = topaque -> btpo .level ;
1690- itemid = PageGetItemId (state -> target ,P_FIRSTDATAKEY (topaque ));
1703+ itemid = PageGetItemIdCareful (state ,state -> targetblock ,state -> target ,
1704+ P_FIRSTDATAKEY (topaque ));
16911705itup = (IndexTuple )PageGetItem (state -> target ,itemid );
16921706childblk = BTreeInnerTupleGetDownLink (itup );
16931707for (;;)
@@ -1711,7 +1725,8 @@ bt_downlink_missing_check(BtreeCheckState *state)
17111725level - 1 ,copaque -> btpo .level )));
17121726
17131727level = copaque -> btpo .level ;
1714- itemid = PageGetItemId (child ,P_FIRSTDATAKEY (copaque ));
1728+ itemid = PageGetItemIdCareful (state ,childblk ,child ,
1729+ P_FIRSTDATAKEY (copaque ));
17151730itup = (IndexTuple )PageGetItem (child ,itemid );
17161731childblk = 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 */
17611776if (P_ISHALFDEAD (copaque )&& !P_RIGHTMOST (copaque ))
17621777{
1763- itemid = PageGetItemId ( child ,P_HIKEY );
1778+ itemid = PageGetItemIdCareful ( state , childblk , child ,P_HIKEY );
17641779itup = (IndexTuple )PageGetItem (child ,itemid );
17651780if (BTreeTupleGetTopParent (itup )== state -> targetblock )
17661781return ;
@@ -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 ,
20952112OffsetNumber upperbound )
20962113{
2114+ ItemId itemid ;
20972115int32 cmp ;
20982116
20992117Assert (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 */
21022123if (!key -> heapkeyspace )
21032124return invariant_leq_offset (state ,key ,upperbound );
@@ -2116,13 +2137,11 @@ invariant_l_offset(BtreeCheckState *state, BTScanInsert key,
21162137if (cmp == 0 )
21172138{
21182139BTPageOpaque topaque ;
2119- ItemId itemid ;
21202140IndexTuple ritup ;
21212141int uppnkeyatts ;
21222142ItemPointer rheaptid ;
21232143bool nonpivot ;
21242144
2125- itemid = PageGetItemId (state -> target ,upperbound );
21262145ritup = (IndexTuple )PageGetItem (state -> target ,itemid );
21272146topaque = (BTPageOpaque )PageGetSpecialPointer (state -> target );
21282147nonpivot = 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 ;
22112238int32 cmp ;
22122239
22132240Assert (key -> pivotsearch );
22142241
2242+ /* Verify line pointer before checking tuple */
2243+ itemid = PageGetItemIdCareful (state ,nontargetblock ,nontarget ,
2244+ upperbound );
22152245cmp = _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 */
22222252if (cmp == 0 )
22232253{
2224- ItemId itemid ;
22252254IndexTuple child ;
22262255int uppnkeyatts ;
22272256ItemPointer childheaptid ;
22282257BTPageOpaque copaque ;
22292258bool nonpivot ;
22302259
2231- itemid = PageGetItemId (nontarget ,upperbound );
22322260child = (IndexTuple )PageGetItem (nontarget ,itemid );
22332261copaque = (BTPageOpaque )PageGetSpecialPointer (nontarget );
22342262nonpivot = P_ISLEAF (copaque )&& upperbound >=P_FIRSTDATAKEY (copaque );
@@ -2426,6 +2454,55 @@ bt_mkscankey_pivotsearch(Relation rel, IndexTuple itup)
24262454return 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.