BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`

Started by PG Bug reporting form21 days ago5 messagesbugs
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

won't retrysuccessCI history

This thread has been committed, so CI has stopped here. Anything below is the last result it produced.

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253297
psql -h localhost -U postgres

Built from patchset v4 (message #4), August 03, 2026 at 02:20 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253297_4 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253297_4 && git checkout t253297_4

Patchset v4 (message #4) is on t253297_4

Jump to latest
#1PG 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.

#2Andrey Rachitskiy
pl0h0yp1@gmail.com
In reply to: PG Bug reporting form (#1)
Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`

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>:

Show quoted text

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:

t253297_2
0001-pg_surgery-Fix-infinite-loop-on-large-tid-arrays-BUG-19607.patchtext/x-patch; charset=US-ASCII; name=0001-pg_surgery-Fix-infinite-loop-on-large-tid-arrays-BUG-19607.patchDownload+3-3
#3Andrey Borodin
amborodin@acm.org
In reply to: Andrey Rachitskiy (#2)
Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`

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.

#4Andrey Rachitskiy
pl0h0yp1@gmail.com
In reply to: Andrey Borodin (#3)
Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`

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:

t253297_4
v2-0001-pg_surgery-Fix-infinite-loop-on-large-tid-arrays-BUG-19607.patchtext/x-patch; charset=US-ASCII; name=v2-0001-pg_surgery-Fix-infinite-loop-on-large-tid-arrays-BUG-19607.patchDownload+28-3
#5Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andrey Rachitskiy (#4)
Re: BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`

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/