| From: | David K <dkarapetyan(at)gmail(dot)com> |
|---|---|
| To: | pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Subject: | Bug: XLogReader mishandles oversized multi-page xl_tot_len (potential memory corruption) |
| Date: | 2026-07-25 20:44:32 |
| Message-ID: | CALmTjZBddzeGVU2o1JNZb3mPvV68PqDskWPAUQBVb-35yVkiwA@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi,
An automated AI review of the WAL reader found that XLogReader does not
enforce XLogRecordMaxSize on xl_tot_len. The insert path has a check:
XLogRecordAssemble() rejects total_len > XLogRecordMaxSize
but the reader only checks a minimum length. That asymmetry allows a
crafted or corrupted multi-page record reassembly to overflow.
More details
---
A multi-page record with:
- xl_tot_len near UINT32_MAX (e.g. 0xFFFFF000), far above
XLogRecordMaxSize (1020 MB)
- consistent XLP_FIRST_IS_CONTRECORD / xlp_rem_len on continuation pages
is not rejected early. allocate_recordbuf() then rounds the length with
uint32 arithmetic:
newSize += XLOG_BLCKSZ - (newSize % XLOG_BLCKSZ);
For lengths near UINT32_MAX this wraps around to a small buffer (~40 kB).
Later reassembly steps either:
- under USE_ASSERT_CHECKING: abort on
Assert(gotlen <= lengthof(save_copy))
in XLogDecodeNextRecord (save_copy is only 2 * XLOG_BLCKSZ), or
- without asserts: stack/heap overflow while copying contrecord data
(overflowing the 2-page save_copy on the stack)
CRC validation happens after reassembly has allocated and copied based on
xl_tot_len, so a valid CRC is not required (only a plausible header and
matching contrecord lengths).
Impact
---
This does not affect replay of valid WAL produced by a healthy primary. It
affects consumers of untrusted or corrupt multi-page WAL, including:
- crash recovery / archive recovery if such WAL is present
- physical standbys replaying bad segments
- tools such as pg_waldump that use the same frontend XLogReader
Fix
---
1. Reject xl_tot_len > XLogRecordMaxSize in ValidXLogRecordHeader(), and on
the partial-header path before multi-page reassembly starts (symmetric with
XLogRecordAssemble()).
2. Compute reassembly buffer sizes with size_t in allocate_recordbuf() so
near-UINT32_MAX lengths cannot wrap even if a caller forgets the bound.
With the fix, the reader returns an error, e.g.:
invalid record length at 0/00000028: expected at most 1069547520, got
4294963200
Test
---
A small frontend module exercises the reader with in-memory crafted pages:
src/test/modules/test_xlogreader/
make -C src/test/modules/test_xlogreader
./src/test/modules/test_xlogreader/test_xlogreader_oversized
Before the fix: abort (cassert) or crash.
After the fix: exits 0 and prints the rejection message above.
Patch
---
Patch against current master is attached
(0001-Fix-XLogReader-mishandling-of-oversized-multi-page-r.patch).
Thanks,
David Karapetyan
dkarapetyan(at)gmail(dot)com
| Attachment | Content-Type | Size |
|---|---|---|
| 0001-Fix-XLogReader-mishandling-of-oversized-multi-page-r.patch | application/octet-stream | 17.2 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Tom Lane | 2026-07-25 21:23:07 | Re: remove_useless_joins vs. bug #19560 |
| Previous Message | Thom Brown | 2026-07-25 20:07:53 | Re: remove_useless_joins vs. bug #19560 |