Skip to content

Reject malformed element counts in message decoders - #451

Open
gaoflow wants to merge 3 commits into
sunng87:masterfrom
gaoflow:fix-signed-count-capacity-overflow
Open

Reject malformed element counts in message decoders#451
gaoflow wants to merge 3 commits into
sunng87:masterfrom
gaoflow:fix-signed-count-capacity-overflow

Conversation

@gaoflow

@gaoflow gaoflow commented Jul 29, 2026

Copy link
Copy Markdown

Several message decoders read a repeated-element count as a signed i16/i32 and hand it to Vec::with_capacity before reading any element. A negative count (0xffff as i16 = -1) sign-extends to usize::MAX and panics with capacity overflow, so a ~7-byte crafted message crashes whoever decodes it — the shipped client/proxy on a backend message, or the server on Bind.

Repro (debug and release):
PgWireBackendMessage::decode(&mut BytesMut::from(&b"T\0\0\0\x06\xff\xff"[..]), &ctx)capacity overflow.

#192 fixed this for Parse, Bind and ParameterDescription by widening the count to u16 but stopped short of the siblings that share the shape. This completes it for the six missed: RowDescription, CopyInResponse/CopyOutResponse/CopyBothResponse, NegotiateProtocolVersion (re-introduced in #281), and Bind's result_column_format_codes (#192 widened the other two counts in that same function, not this third).

The counts are read unsigned and routed through one shared codec::read_count that rejects a count larger than the remaining bytes, turning a malformed message into a decode Err. A single helper also keeps the pattern from silently reappearing as it did in #281; and reading unsigned alone isn't enough for the i32 option count, where u32::MAX still forces a huge allocation, so the bound is applied uniformly. The four counts #192 already widened are left untouched.

Tests exercise all six sites through the public decode API, plus zero-count and well-formed positive-count controls.

Several decoders read a repeated-element count as a signed i16/i32 and
pass it to Vec::with_capacity before reading any element. A negative count
(0xffff read as i16 = -1) sign-extends to usize::MAX and panics with
"capacity overflow", so a short crafted message crashes the peer decoding
it (the shipped client/proxy on backend messages, the server on Bind).

sunng87#192 fixed this for Parse, Bind and ParameterDescription by widening the
count to u16 but left RowDescription, the three Copy*Response messages,
NegotiateProtocolVersion, and Bind's result_column_format_codes. Read
those counts as unsigned and route them through a shared codec::read_count
that rejects a count exceeding the bytes remaining, turning a malformed
message into a decode error instead of a panic.
Comment thread src/messages/codec.rs Outdated
Comment thread src/messages/codec.rs Outdated
Comment thread src/messages/copy.rs Outdated
@gaoflow

gaoflow commented Aug 1, 2026

Copy link
Copy Markdown
Author

Updated in 7eff654: CopyInResponse now keeps the wire count as i16 and only coerces it to usize for ensure_count/capacity. cargo test passes (150).

Counts on the wire are element counts, not byte counts: Bind's result
format codes are Int16s, so a count that fits the remaining bytes can
still overrun the buffer when read as 2-byte elements. ensure_count now
takes the per-element size and rejects count * size > remaining.
@gaoflow

gaoflow commented Aug 1, 2026

Copy link
Copy Markdown
Author

Updated in e68abf0: CopyInResponse now keeps the wire count as i16 and only coerces it to usize for ensure_count and capacity. cargo test passes (150).

@gaoflow
gaoflow force-pushed the fix-signed-count-capacity-overflow branch from e6dac70 to e68abf0 Compare August 1, 2026 13:08
Comment thread src/messages/copy.rs
fn decode_body(buf: &mut BytesMut, _len: usize, _ctx: &DecodeContext) -> PgWireResult<Self> {
let format = buf.get_i8();
let columns = buf.get_i16();
let columns = buf.get_u16();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please follow the same pattern for this and all count fields below

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.

2 participants