agora inbox for pgsql-bugs@postgresql.org
help / color / mirror / Atom feedBUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`
5+ messages / 4 participants
[nested] [flat]
* BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`
@ 2026-08-03 08:40 PG Bug reporting form <noreply@postgresql.org>
2026-08-03 13:35 ` Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` Andrey Rachitskiy <pl0h0yp1@gmail.com>
0 siblings, 1 reply; 5+ messages in thread
From: PG Bug reporting form @ 2026-08-03 08:40 UTC (permalink / raw)
To: pgsql-bugs@lists.postgresql.org; +Cc: 1217816127@qq.com
The following bug has been logged on the website:
Bug reference: 19607
Logged by: Yuelin Wang
Email address: 1217816127@qq.com
PostgreSQL version: 19beta2
Operating system: Linux (Ubuntu 24.04, x86_64)
Description:
### Summary
In `contrib/pg_surgery/heap_surgery.c`, a huge TID array can truncate an
index into `OffsetNumber`. The loop no longer reaches its end condition and
the statement keeps running until cancellation. This is a SQL reachable
denial of service when `pg_surgery` is installed.
### PoC
SQL script:
```sql
CREATE EXTENSION IF NOT EXISTS pg_surgery;
CREATE TABLE vuln_surgery_loop(a int);
INSERT INTO vuln_surgery_loop
SELECT g FROM generate_series(1, 300) AS g;
SET statement_timeout = '15s';
SELECT heap_force_kill(
'vuln_surgery_loop'::regclass,
ARRAY(
SELECT '(0,1)'::tid
FROM generate_series(1, 65536)
)
);
RESET statement_timeout;
```
### Result
The call remains active until `statement_timeout`. A finite array pass of
this size should complete quickly, so the timeout confirms the integer
truncation induced infinite loop.
^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`
2026-08-03 08:40 BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` PG Bug reporting form <noreply@postgresql.org>
@ 2026-08-03 13:35 ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-03 13:44 ` Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` Andrey Borodin <x4mmm@yandex-team.ru>
0 siblings, 1 reply; 5+ messages in thread
From: Andrey Rachitskiy @ 2026-08-03 13:35 UTC (permalink / raw)
To: 1217816127@qq.com; pgsql-bugs@lists.postgresql.org
Hi, Yuelin!
Thanks for the report.
The problem is that heap_force_common() walks the caller-supplied tid[]
with OffsetNumber indexes. OffsetNumber is a uint16, while ntids is an
int, so an array longer than 65535 makes the next-index update wrap.
The outer loop then never reaches next_start_ptr == ntids and keeps
reprocessing the same page until cancel. Those variables have been
OffsetNumber since pg_surgery was added in 34a947ca13e.
Patch attached.
пн, 3 авг. 2026 г. в 16:56, PG Bug reporting form <noreply@postgresql.org>:
> The following bug has been logged on the website:
>
> Bug reference: 19607
> Logged by: Yuelin Wang
> Email address: 1217816127@qq.com
> PostgreSQL version: 19beta2
> Operating system: Linux (Ubuntu 24.04, x86_64)
> Description:
>
> ### Summary
>
> In `contrib/pg_surgery/heap_surgery.c`, a huge TID array can truncate an
> index into `OffsetNumber`. The loop no longer reaches its end condition and
> the statement keeps running until cancellation. This is a SQL reachable
> denial of service when `pg_surgery` is installed.
>
> ### PoC
>
> SQL script:
>
> ```sql
> CREATE EXTENSION IF NOT EXISTS pg_surgery;
>
> CREATE TABLE vuln_surgery_loop(a int);
> INSERT INTO vuln_surgery_loop
> SELECT g FROM generate_series(1, 300) AS g;
>
> SET statement_timeout = '15s';
>
> SELECT heap_force_kill(
> 'vuln_surgery_loop'::regclass,
> ARRAY(
> SELECT '(0,1)'::tid
> FROM generate_series(1, 65536)
> )
> );
>
> RESET statement_timeout;
> ```
>
> ### Result
>
> The call remains active until `statement_timeout`. A finite array pass of
> this size should complete quickly, so the timeout confirms the integer
> truncation induced infinite loop.
>
>
>
>
>
Attachments:
[text/x-patch] 0001-pg_surgery-Fix-infinite-loop-on-large-tid-arrays-BUG-19607.patch (2.0K, ../../CAB8bMiuSh6QDuR=yYnNJw-76r5bTUeMOwFMwMhbg15BmTfw8Kw@mail.gmail.com/3-0001-pg_surgery-Fix-infinite-loop-on-large-tid-arrays-BUG-19607.patch)
download | inline diff:
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,
^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`
2026-08-03 08:40 BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` PG Bug reporting form <noreply@postgresql.org>
2026-08-03 13:35 ` Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` Andrey Rachitskiy <pl0h0yp1@gmail.com>
@ 2026-08-03 13:44 ` Andrey Borodin <x4mmm@yandex-team.ru>
2026-08-03 14:08 ` Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` Andrey Rachitskiy <pl0h0yp1@gmail.com>
0 siblings, 1 reply; 5+ messages in thread
From: Andrey Borodin @ 2026-08-03 13:44 UTC (permalink / raw)
To: Andrey Rachitskiy <pl0h0yp1@gmail.com>; +Cc: 1217816127@qq.com; pgsql-bugs@lists.postgresql.org
> On 3 Aug 2026, at 18:35, Andrey Rachitskiy <pl0h0yp1@gmail.com> wrote:
>
> Patch attached.
While fix looks correct to me, I'd suggest adding a small regression test.
Best regards, Andrey Borodin.
^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`
2026-08-03 08:40 BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` PG Bug reporting form <noreply@postgresql.org>
2026-08-03 13:35 ` Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-03 13:44 ` Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` Andrey Borodin <x4mmm@yandex-team.ru>
@ 2026-08-03 14:08 ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-04 09:48 ` Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 1 reply; 5+ messages in thread
From: Andrey Rachitskiy @ 2026-08-03 14:08 UTC (permalink / raw)
To: Andrey Borodin <x4mmm@yandex-team.ru>; +Cc: 1217816127@qq.com; pgsql-bugs@lists.postgresql.org
Andrey, thanks for the review.
v2 adds a small regress case based on the report (65536 identical
TIDs). Without the fix that call never returns.
пн, 3 авг. 2026 г. в 18:44, Andrey Borodin <x4mmm@yandex-team.ru>:
>
>
> > On 3 Aug 2026, at 18:35, Andrey Rachitskiy <pl0h0yp1@gmail.com> wrote:
> >
> > Patch attached.
>
> While fix looks correct to me, I'd suggest adding a small regression test.
>
>
> Best regards, Andrey Borodin.
>
--
Regards,
Rachitskiy Andrey
Attachments:
[text/x-patch] v2-0001-pg_surgery-Fix-infinite-loop-on-large-tid-arrays-BUG-19607.patch (3.6K, ../../CAB8bMisnzAbd_xD4VK5fgjEwNL4X0ssZSSqsG1VDEcRMQVugJw@mail.gmail.com/3-v2-0001-pg_surgery-Fix-infinite-loop-on-large-tid-arrays-BUG-19607.patch)
download | inline diff:
From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Mon, 3 Aug 2026 13:18:00 +0000
Subject: [PATCH v2] 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.
A regress case based on the report is included.
Bug: #19607
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
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/expected/heap_surgery.out b/contrib/pg_surgery/expected/heap_surgery.out
index df7d13b0908..8997caf3ee3 100644
--- a/contrib/pg_surgery/expected/heap_surgery.out
+++ b/contrib/pg_surgery/expected/heap_surgery.out
@@ -134,6 +134,23 @@ select heap_force_kill('htab2'::regclass, ARRAY['(0, 3)']::tid[]);
(1 row)
+-- a tid[] larger than 65535 entries must still finish
+create temp table htab3(a int);
+insert into htab3 values (1);
+select heap_force_kill(
+ 'htab3'::regclass,
+ array(select '(0,1)'::tid from generate_series(1, 65536)));
+ heap_force_kill
+-----------------
+
+(1 row)
+
+select count(*) from htab3;
+ count
+-------
+ 0
+(1 row)
+
-- materialized view.
-- note that we don't commit the transaction, so autovacuum can't interfere.
begin;
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,
diff --git a/contrib/pg_surgery/sql/heap_surgery.sql b/contrib/pg_surgery/sql/heap_surgery.sql
index 6526b27535d..ff4474bddd7 100644
--- a/contrib/pg_surgery/sql/heap_surgery.sql
+++ b/contrib/pg_surgery/sql/heap_surgery.sql
@@ -65,6 +65,14 @@ select heap_force_kill('htab2'::regclass, ARRAY[NULL]::tid[]);
-- but we should be able to kill the one tuple we have
select heap_force_kill('htab2'::regclass, ARRAY['(0, 3)']::tid[]);
+-- a tid[] larger than 65535 entries must still finish
+create temp table htab3(a int);
+insert into htab3 values (1);
+select heap_force_kill(
+ 'htab3'::regclass,
+ array(select '(0,1)'::tid from generate_series(1, 65536)));
+select count(*) from htab3;
+
-- materialized view.
-- note that we don't commit the transaction, so autovacuum can't interfere.
begin;
^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`
2026-08-03 08:40 BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` PG Bug reporting form <noreply@postgresql.org>
2026-08-03 13:35 ` Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-03 13:44 ` Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` Andrey Borodin <x4mmm@yandex-team.ru>
2026-08-03 14:08 ` Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` Andrey Rachitskiy <pl0h0yp1@gmail.com>
@ 2026-08-04 09:48 ` Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 5+ messages in thread
From: Álvaro Herrera @ 2026-08-04 09:48 UTC (permalink / raw)
To: Andrey Rachitskiy <pl0h0yp1@gmail.com>; +Cc: Andrey Borodin <x4mmm@yandex-team.ru>; 1217816127@qq.com; pgsql-bugs@lists.postgresql.org
On 2026-Aug-03, Andrey Rachitskiy wrote:
> Andrey, thanks for the review.
>
> v2 adds a small regress case based on the report (65536 identical
> TIDs). Without the fix that call never returns.
Thanks, looks good, pushed.
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"XML!" Exclaimed C++. "What are you doing here? You're not a programming
language."
"Tell that to the people who use me," said XML.
https://burningbird.net/the-parable-of-the-languages/
^ permalink raw reply [nested|flat] 5+ messages in thread
end of thread, other threads:[~2026-08-04 09:48 UTC | newest]
Thread overview: 5+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-08-03 08:40 BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common` PG Bug reporting form <noreply@postgresql.org>
2026-08-03 13:35 ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-03 13:44 ` Andrey Borodin <x4mmm@yandex-team.ru>
2026-08-03 14:08 ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-04 09:48 ` Álvaro Herrera <alvherre@kurilemu.de>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox