| From: | Matt Suiche <matt(at)tolmo(dot)com> |
|---|---|
| To: | pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Subject: | [PATCH] Harden two-phase GID handling and fork-number validation in WAL replay/decode paths |
| Date: | 2026-07-27 08:06:55 |
| Message-ID: | CAGf6Lfx2kbQfcEnCi99V2i65JSWD6ij_E29F+UkY=TyMUyeG6A@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hello,
While fuzzing WAL replay and decode paths on master, we found that the
two-phase redo code and several fork-number consumers trust
record-supplied values that can, in crafted or corrupted WAL, exceed
the bounds the rest of the code assumes. The write-time path already
rejects oversized two-phase GIDs (twophase.c:371), but the replay and
restore paths do not re-validate, and `gxact->gid` (GIDSIZE bytes) is
filled with plain `strcpy`. Similarly, fork numbers from records index
the 4-entry `forkNames[]` without range checks on some paths.
Per a suggestion on the security list (where the WAL-trust posture was
noted, but hardening the two-phase replay code was encouraged), here
are two small patches:
- **0001-Validate-two-phase-GID-length-in-replay-paths.patch**
Reject `gidlen == 0 || gidlen >= GIDSIZE` in `PrepareRedoAdd()`,
`RecoverPreparedTransactions()` (state-file restore), and
`ParsePrepareRecord()` (the same pattern into a stack buffer in
xactdesc.c), and replace the `strcpy` calls with `strlcpy`.
- **0002-Validate-fork-numbers-in-WAL-decode-paths.patch**
Range-check the block-header fork nibble in `DecodeXLogRecord()`
(`report_invalid_record`, consistent with neighbouring checks), and
clamp defensively in `GetRelationPath()`, which is shared
frontend/backend code and the common sink for `forkNames[]`
indexing (e.g. from `xl_smgr_create.forkNum`).
Without these checks, a malformed `XLOG_XACT_PREPARE` record overflows
`gxact->gid` into neighbouring two-phase shared memory during redo
(reproduced: multi-KB overwrite, freelist corruption), and an
out-of-range fork number reads outside `forkNames[]`. With them, both
inputs are rejected cleanly at replay/decode time (in redo, the ERROR
is promoted to FATAL, aborting recovery rather than corrupting state).
Verified against master (38afc3dcb): the tree builds cleanly,
`PREPARE TRANSACTION` / `COMMIT PREPARED` / recovery of prepared
transactions work normally, and crafted records that previously
corrupted shared memory are now rejected with the new error messages.
Best regards,
--
*Matt Suiche*
*Tolmo, Inc.*
| Attachment | Content-Type | Size |
|---|---|---|
| 0002-Validate-fork-numbers-in-WAL-decode-paths.patch | application/octet-stream | 1.6 KB |
| 0001-Validate-two-phase-GID-length-in-replay-paths.patch | application/octet-stream | 3.1 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Daniel Gustafsson | 2026-07-27 08:39:23 | Re: [PATCH] Rename "getdatabaseencoding()" to "pg_database_encoding()", and document |
| Previous Message | Michael Paquier | 2026-07-27 07:47:00 | Re: [PATCH v1] Fix propagation of indimmediate flag in index_create_copy |