From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Mon, 3 Aug 2026 13:18:00 +0000
Subject: [PATCH] pg_surgery: Fix infinite loop on large tid arrays

heap_force_common() tracked the current position in the caller-supplied
tid[] using OffsetNumber.  That type is a uint16, so when the array
held more than 65535 entries the updated index wrapped and the outer
loop never reached ntids.  A SQL call with a sufficiently large tid[]
could then run until cancelled.

Fix by tracking the tid[] position with int instead of OffsetNumber.

Bug: #19607
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Reported-by: Yuelin Wang <1217816127@qq.com>
Discussion: https://postgr.es/m/19607-2f256a66481c514b@postgresql.org
Backpatch-through: 14

---
diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c
index 181b7d1e210..51f3f3c49eb 100644
--- a/contrib/pg_surgery/heap_surgery.c
+++ b/contrib/pg_surgery/heap_surgery.c
@@ -44,7 +44,7 @@ static Datum heap_force_common(FunctionCallInfo fcinfo,
 							   HeapTupleForceOption heap_force_opt);
 static void sanity_check_tid_array(ArrayType *ta, int *ntids);
 static BlockNumber find_tids_one_page(ItemPointer tids, int ntids,
-									  OffsetNumber *next_start_ptr);
+									  int *next_start_ptr);
 
 /*-------------------------------------------------------------------------
  * heap_force_kill()
@@ -91,7 +91,7 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 	int			ntids,
 				nblocks;
 	Relation	rel;
-	OffsetNumber curr_start_ptr,
+	int			curr_start_ptr,
 				next_start_ptr;
 	bool		include_this_tid[MaxHeapTuplesPerPage];
 
@@ -413,7 +413,7 @@ sanity_check_tid_array(ArrayType *ta, int *ntids)
  * ------------------------------------------------------------------------
  */
 static BlockNumber
-find_tids_one_page(ItemPointer tids, int ntids, OffsetNumber *next_start_ptr)
+find_tids_one_page(ItemPointer tids, int ntids, int *next_start_ptr)
 {
 	int			i;
 	BlockNumber prev_blkno,
