| From: | Gaurav Singh <gaurav(dot)singh(at)yugabyte(dot)com> |
|---|---|
| To: | pgsql-bugs(at)lists(dot)postgresql(dot)org |
| Subject: | Memory leak in pg_stat_statements when qtext file contains invalid encoding |
| Date: | 2026-03-27 07:54:11 |
| Message-ID: | CAEcQ1bYR9s4eQLFDjzzJHU8fj-MTbmRpW-9J-r2gsCn+HEsynw@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
I've found a memory leak in contrib/pg_stat_statements that occurs when the
query text file (pgss_query_texts.stat) contains an invalid byte sequence.
Each call to pg_stat_statements leaks the entire malloc'd file buffer and
fails to release the LWLock.PostgreSQL version: Discovered against PG 15.12,
verified also present in PG 18 (HEAD as of 2026-03-26). The code path is
unchanged between versions.Platform: macOS 15.7.3 (aarch64).Although the
cause of corruption inpgss_query_texts.statis unknown, we have seen this
twice. Similar cases were found online.Eg:ERROR: invalid byte sequence for
encoding "UTF8": 0x00 in pg_stat_statements - Database Administrators Stack
Exchange
<https://dba.stackexchange.com/questions/306293/error-invalid-byte-sequence-for-encoding-utf8-0x00-in-pg-stat-statements#:~:text=Most%20of%20the%20existing%20advice,and%20replace%20but%20no%20luck>
.How to reproduce:
-- 1. Enable pg_stat_statements
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
SELECT pg_stat_statements_reset();
-- 2. Populate with many unique queries to make the qtext file large.
-- Each CTE name is padded to 63 chars (NAMEDATALEN - 1) and repeated
-- in self-joins so the normalized text is ~300 bytes per entry.
-- Run this for i = 1..2000:
WITH q_1___________________________________________ AS (SELECT 1)
SELECT * FROM q_1___________________________________________ a1,
q_1___________________________________________ a2,
q_1___________________________________________ a3;
-- (repeat with q_2, q_3, ... q_2000)
# 3. Corrupt the qtext file with a null byte.
DATA_DIR=$(psql -t -A -c "SHOW data_directory;")
printf '\x00' | dd of="$DATA_DIR/pg_stat_tmp/pgss_query_texts.stat" \
bs=1 seek=500 count=1 conv=notrunc 2>/dev/null
-- 4. Verify corruption causes the expected error:
SELECT count(*) FROM pg_stat_statements;
-- ERROR: invalid byte sequence for encoding "UTF8": 0x00
# 5. In a single psql session, run the query 2000 times and monitor
# the backend's RSS:
BACKEND_PID=$(psql -t -A -c "SELECT pg_backend_pid();")
for i in $(seq 1 2000); do
psql -c "SELECT count(*) FROM pg_stat_statements;" 2>/dev/null
done &
# Monitor in another terminal:
watch -n 2 "ps -o rss= -p $BACKEND_PID"
Output I got:RSS grows linearly with each failing query. With a ~600 KB qtext
file and 2000 iterations, the backend's RSS grew by approximately 1.2 GB:
Time(s) RSS (KB) RSS (MB)
0s 68864 67
4s 95712 93
8s 156736 153
12s 232256 226
...
38s 756800 739
42s 907264 885
46s 1052864 1028
50s 1193024 1164
54s 1281280 1251
Leak per error is approximately equal to the qtext file size (~600 KB),
confirming the file buffer is leaked on every call.
Output I expected:RSS should remain approximately constant. Each call should
either succeed or fail cleanly without leaking memory. The LWLock should
always be released.Root cause:
- In pg_stat_statements_internal(), the function acquires pgss->lock and
may malloc a file buffer via qtext_load_file().
- Later, pg_any_to_server() is called inside the hash iteration loop.
- If the qtext file contains an invalid encoding, pg_any_to_server calls
ereport(ERROR) which longjmps out of the function.
- The cleanup code at the bottom of the function is never reached.
LWLockRelease(pgss->lock);
if (qbuffer)
free(qbuffer);
On every subsequent call, the malloc'd buffer (the entire file contents) is
leaked, and the LWLock release is also skipped.Proposed fix:Wrap the
hash iteration
loop in PG_TRY/PG_FINALLY so that the lock release and buffer free happen
even on the error path:
PG_TRY();
{
hash_seq_init(&hash_seq, pgss_hash);
while ((entry = hash_seq_search(&hash_seq)) != NULL)
{
/* ... existing loop body unchanged ... */
}
}
PG_FINALLY();
{
LWLockRelease(pgss->lock);
if (qbuffer)
free(qbuffer);
}
PG_END_TRY();
Gaurav Singh
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Gaurav Singh | 2026-03-27 08:03:41 | Memory leak in pg_stat_statements when query text file contains invalid encoding |
| Previous Message | Vishal Prasanna | 2026-03-27 05:54:08 | RE: [BUG] Assert failure in ReorderBufferReturnTXN during logical decoding due to leaked specinsert change |