Skip to content

pgwire: make the frame length ceiling a per-call-site argument - #38809

Draft
jubrad wants to merge 1 commit into
MaterializeInc:mainfrom
jubrad:justin/clo-270-right-size-pgwire-frame-bounds
Draft

pgwire: make the frame length ceiling a per-call-site argument#38809
jubrad wants to merge 1 commit into
MaterializeInc:mainfrom
jubrad:justin/clo-270-right-size-pgwire-frame-bounds

Conversation

@jubrad

@jubrad jubrad commented Sep 12, 2026

Copy link
Copy Markdown
Member

Issue

parse_frame_len applies one ceiling, MAX_FRAME_SIZE (64 MiB), to three call sites whose requirements differ by orders of magnitude. It is sized for bulk COPY traffic, which is the only path that needs it. A startup frame is well under 10 KB, and balancerd's Codec only ever decodes a credential, so both are bounded far above anything they legitimately carry.

Linear: CLO-270

Solution

The ceiling becomes an argument to parse_frame_len, so each call site states the bound it needs and a caller added later cannot inherit 64 MiB by default.

site bound change
balancerd startup MAX_STARTUP_FRAME_SIZE, 10,000 bytes tightened
balancerd Codec MAX_PREAUTH_FRAME_SIZE, 16 KiB tightened
environmentd startup MAX_FRAME_SIZE unchanged
environmentd Codec MAX_FRAME_SIZE unchanged

Scope. This PR tightens balancerd only. Both environmentd call sites pass MAX_FRAME_SIZE, the exact value parse_frame_len used before, so their behavior is unchanged and the diff there is mechanical. Tightening environmentd needs a phase-dependent bound on Codec, since one instance serves a connection both before and after authentication and no single constant suits both, plus an allowance for the parameters balancerd appends when forwarding startup. That is a follow-up PR.

10,000 bytes matches PostgreSQL's MAX_STARTUP_PACKET_LENGTH, so any client that can complete a PostgreSQL handshake can complete this one.

16 KiB rather than ~1 KB for balancerd's Codec because an authenticator may accept a bearer token rather than an app password, and those run to several KB. That codec decodes at most one frame per connection, the credential, before copy_bidirectional splices the connection and the rest is proxied unframed.

Testing

New unit tests in mz-pgwire-common cover a frame over the budget being rejected before any buffer is sized, a frame at the budget still decoding, and the buffer a declared frame produces being bounded by the budget.

A new integration test in mz-balancerd drives a real listener and asserts an oversized declaration is refused while one at the budget is still served, so the rejection is the budget doing its job rather than the listener refusing everything.

Release notes

This release will reject pgwire startup messages larger than 10,000 bytes at the balancer, matching PostgreSQL's limit.

🤖 Generated with Claude Code

`parse_frame_len` applied one ceiling, `MAX_FRAME_SIZE` (64 MiB), to three
call sites whose requirements differ by orders of magnitude. It is sized
for bulk `COPY` traffic, which is the only path that needs it.

The ceiling is now an argument, so each site states the bound it needs and
a caller added later cannot inherit 64 MiB by default:

* balancerd's startup path takes `MAX_STARTUP_FRAME_SIZE`, 10,000 bytes,
  matching PostgreSQL's `MAX_STARTUP_PACKET_LENGTH`.
* balancerd's `Codec` takes `MAX_PREAUTH_FRAME_SIZE`, 16 KiB. It decodes
  at most one frame per connection, the credential, before
  `copy_bidirectional` splices the connection and the rest is proxied
  unframed.
* environmentd's two call sites pass `MAX_FRAME_SIZE`, the value
  `parse_frame_len` used before, so their behavior is unchanged.

Part of: CLO-270

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jubrad
jubrad force-pushed the justin/clo-270-right-size-pgwire-frame-bounds branch from 6e912e4 to 8bdd7ba Compare September 13, 2026 03:24
@jubrad jubrad changed the title pgwire: bound startup and pre-auth frame sizes per call site pgwire: make the frame length ceiling a per-call-site argument Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant