| From: | PG Bug reporting form <noreply(at)postgresql(dot)org> |
|---|---|
| To: | pgsql-bugs(at)lists(dot)postgresql(dot)org |
| Cc: | 3020001251(at)tju(dot)edu(dot)cn |
| Subject: | BUG #19528: Assert failure in generate_normalized_query() via Squashed Array Literals |
| Date: | 2026-06-19 04:40:56 |
| Message-ID: | 19528-7290dd7e6f7dcc22@postgresql.org |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
The following bug has been logged on the website:
Bug reference: 19528
Logged by: Yuelin Wang
Email address: 3020001251(at)tju(dot)edu(dot)cn
PostgreSQL version: 19beta1
Operating system: Linux (Ubuntu 24.04, x86_64)
Description:
The buffer is allocated at line 2841 with a per-location budget of 10 bytes:
```c
norm_query_buflen = query_len + jstate->clocations_count * 10;
```
The comment explains the budget: a `$n` placeholder is at most 11 bytes and
the original constant is at least 1 byte, so net expansion is at most 10
bytes per location. This holds for non-squashed constants.
For squashed array elements, the sprintf at line 2883 writes `"$N /*, ...
*/"` instead of just `"$N"`:
```c
n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d%s",
num_constants_replaced + 1 +
jstate->highest_extern_param_id,
locs[i].squashed ? " /*, ... */" : "");
```
`" /*, ... */"` is 11 bytes, so a squashed entry writes `"$N"` (2 bytes)
plus `" /*, ... */"` (11 bytes) = 13 bytes to replace a 1-byte constant, a
net expansion of 12 bytes. The budget of 10 bytes is exceeded by 2 bytes per
squashed location. With N squashed array elements the buffer overflows by 2N
bytes.
Any `ARRAY[a,b]` literal records its second element as a squashed
`clocations` entry. Ten such arrays produce a 20-byte overflow, which is
sufficient to trigger the Assert at line 2908:
```
TRAP: failed Assert("n_quer_loc <= norm_query_buflen"),
File: "pg_stat_statements.c", Line: 2908
```
In a Release build (assertions disabled) the overflow is a true heap write
past the `palloc` allocation, corrupting adjacent allocator metadata.
### Reproduction
`pg_stat_statements` must be listed in `shared_preload_libraries`. The
triggering role holds no special privileges beyond `LOGIN`.
```sql
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE ROLE vuln_004_lowpriv LOGIN PASSWORD 'vuln004';
GRANT pg_read_all_stats TO vuln_004_lowpriv;
SET ROLE vuln_004_lowpriv;
SELECT ARRAY[1,2], ARRAY[1,2], ARRAY[1,2], ARRAY[1,2], ARRAY[1,2],
ARRAY[1,2], ARRAY[1,2], ARRAY[1,2], ARRAY[1,2], ARRAY[1,2];
```
### Observed Output
psql output:
```
SET
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost
```
Server log (PostgreSQL 19beta1, cassert build, 2026-06-19):
```
TRAP: failed Assert("n_quer_loc <= norm_query_buflen"), File:
"pg_stat_statements.c", Line: 2908, PID: 2671635
postgres: yuelinwang postgres [local]
SELECT(ExceptionalCondition+0x103)[0x5602a92f55b8]
/data/.../pg_stat_statements.so(+0x655b)[0x7d147957e55b]
/data/.../pg_stat_statements.so(+0x9ef7)[0x7d1479581ef7]
/data/.../pg_stat_statements.so(+0xb3ba)[0x7d14795833ba]
postgres: yuelinwang postgres [local]
SELECT(parse_analyze_fixedparams+0x120)[...]
postgres: yuelinwang postgres [local] SELECT(PostgresMain+0x1142)[...]
LOG: client backend (PID 2671635) was terminated by signal 6: Aborted
DETAIL: Failed process was running: SELECT ARRAY[1,2], ARRAY[1,2],
ARRAY[1,2], ARRAY[1,2], ARRAY[1,2],
ARRAY[1,2], ARRAY[1,2], ARRAY[1,2], ARRAY[1,2], ARRAY[1,2];
LOG: terminating any other active server processes
LOG: all server processes terminated; reinitializing
LOG: database system was interrupted; last known up at 2026-06-19 12:34:57
+08
LOG: database system was not properly shut down; automatic recovery in
progress
LOG: database system is ready to accept connections
```
### Expected vs Actual
| Step | Expected | Actual |
|---|---|---|
| SELECT with 10 ARRAY[1,2] literals | returns one row of 10 arrays |
backend crashes (SIGABRT signal 6) |
| Server log | nothing | `TRAP: failed Assert("n_quer_loc <=
norm_query_buflen"), File: "pg_stat_statements.c", Line: 2908` |
| Server state | unaffected | crash recovery triggered, postmaster
reinitializes |
| Connection | stays open | `server closed the connection unexpectedly` |
### Fix
Account for the extra 11 bytes appended when a squashed suffix is written.
One correct approach:
```c
norm_query_buflen = query_len + jstate->clocations_count *
(10 + (jstate->has_squashed_lists ? 11 : 0));
```
A simpler conservative fix that covers the worst case (`"$2147483648 /*, ...
*/"` = 22 bytes replacing a 1-byte constant, net 21 bytes):
```c
norm_query_buflen = query_len + jstate->clocations_count * 22;
```
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Hüseyin Demir | 2026-06-19 06:25:47 | Re: BUG #19483: pg_upgrade fails with orphan records in pg_init_priv catalog table |
| Previous Message | 王跃林 | 2026-06-18 18:19:50 | Re: BUG #19525: In `contrib/dict_int`, handling a token whose first byte is a null byte causes `pnstrdup()` . |