BUG #19607: Bug 18: `pg_surgery` infinite loop in `heap_force_common`
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.
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:t253297psql -h localhost -U postgresBuilt 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.gitIn a checkout you already have, add the fork once:
git remote add hackorum https://github.com/hackorum-dev/postgres.gitthen, for this patchset and every later one:
git fetch hackorum t253297_4 && git checkout t253297_4Patchset v4 (message #4) is on t253297_4
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.
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.
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.
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
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/