Skip to content

Fix decoder panics on malformed frame headers and lengths (v4 backport) - #300

Open
plorenz wants to merge 3 commits into
release-v4.xfrom
fix/v4-reply-for-nil-deref
Open

Fix decoder panics on malformed frame headers and lengths (v4 backport)#300
plorenz wants to merge 3 commits into
release-v4.xfrom
fix/v4-reply-for-nil-deref

Conversation

@plorenz

@plorenz plorenz commented Aug 24, 2026

Copy link
Copy Markdown
Member

Fixes #302 and #304. Backport of #299 to the v4 release line, so ziti 1.6.x and
2.0.x can pick it up.

Three defects in the V2 frame decoder, all of which let a peer panic the rx
goroutine, which has no recover above it.

ReplyFor cached as a nil pointer (#302). A ReplyFor header present but not
four bytes wide left the cached replyFor unset, because the fallback that
defaults it to -1 tested the header's byte slice rather than the pointer field
it was defaulting. ReplyFor, IsReply and IsReplyingTo then dereferenced
nil, and IsReply runs on every inbound message.

Header length truncated to int (#304). unmarshalHeaders compared
i + 8 + int(length) against the buffer length. Where int is 32 bits,
int(length) is negative above MaxInt32, so the check passed and the slice
panicked. Now compared as uint64.

Header and body lengths summed as uint32 (#304). unmarshalV2 allocated
make([]byte, headersLength+bodyLength), and the wrapped sum satisfied every
check that followed. Now summed as uint64 and rejected if it will not fit an
int32.

The ReplyFor and uint32-wrap defects are reachable only by authenticated peers,
behind the bind handler. The int-truncation defect is reachable through
rxHello, before any authentication, on 32-bit builds only.

Same changes as #299, adapted to this branch's logging and header constants.
Verified on amd64 and under GOARCH=386.

Backport of #301 to the v4 release line.

- assigns the cached replyFor value on every path through cacheReplyFor, so
  ReplyFor, IsReply and IsReplyingTo cannot dereference a nil pointer when the
  ReplyFor header is present but not four bytes wide
- rejects a wrong-width ReplyFor header in unmarshalHeaders, so a malformed
  frame is dropped rather than silently handled as a non-reply
- adds coverage for malformed, well-formed and absent ReplyFor headers, and for
  the unmarshal rejection
@plorenz
plorenz force-pushed the fix/v4-reply-for-nil-deref branch from 9b2e1b8 to 36a1b2e Compare August 24, 2026 20:04
Backport of #303 to the v4 release line.

- compares the header end offset as uint64 in unmarshalHeaders, so a declared
  length above MaxInt32 is rejected rather than converted to a negative int on a
  32-bit platform and then panicking on the slice
- sums the header and body lengths as uint64 in unmarshalV2 and rejects a total
  that will not fit an int32, so the uint32 sum can no longer wrap to a value
  that satisfies every subsequent check
- adds regression coverage for declared lengths that overflow an int and for
  sums that wrap or exceed MaxInt32

The int32 bound is a representability limit so the total is exact on 32-bit
platforms, not a policy limit on message size.
@plorenz plorenz changed the title Fix nil pointer dereference on a malformed ReplyFor header (v4 backport) Fix decoder panics on malformed frame headers and lengths (v4 backport) Aug 24, 2026
- rewrites the added regression-test comments to describe the malformed input
  and the required outcome, leaving what the code used to do to git history
- keeps the GOARCH=386 caveat, which is a live limit on what the test exercises
  rather than a note about a past change
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