Skip to content

diam: reject a Message Length below the header length - #260

Open
fridokus wants to merge 1 commit into
fiorix:mainfrom
fridokus:harden-message-length-underflow
Open

fridokus wants to merge 1 commit into
fiorix:mainfrom
fridokus:harden-message-length-underflow

Conversation

@fridokus

@fridokus fridokus commented Aug 25, 2026 •

Copy link
Copy Markdown

Header.DecodeFromBytes accepts a Message Length below the 20 byte header, and readBody then computes int(m.Header.MessageLength - HeaderLength) in unsigned arithmetic. For a declared length of 0 that is uint32(0) - 20, or 4294967276, which readerBufferSlice hands straight to make().

Twenty bytes on the wire allocate very nearly 4 GiB.

Reproducing on main (ad16c7e)

b := []byte{
	0x01, 0x00, 0x00, 0x00, // version 1, Message Length 0
	0x80, 0x00, 0x01, 0x01, // Request, Command Code 257 (CER)
	0x00, 0x00, 0x00, 0x00, // Application ID 0
	0x00, 0x00, 0x00, 0x01, // Hop-by-Hop
	0x00, 0x00, 0x00, 0x01, // End-to-End
}
diam.ReadMessage(bytes.NewReader(b), dict.Default)

Under a 2 GiB address-space cap:

runtime: out of memory: cannot allocate 4294967296-byte block (12124160 in use)
fatal error: out of memory

Uncapped it completes, having allocated 4294970504 bytes in 12.5 s.

Why it is reachable before authentication

The only gate before readBody is FindCommand, which merely requires the command and application to exist in the dictionary. CER — command code 257, application 0 — is the first message of any Diameter connection and satisfies that on any dictionary. So the allocation is reachable from any peer that can open a connection, before capabilities are exchanged and before any handler runs.

recover() does not help: an out-of-memory is a fatal runtime error rather than a panic, so the recover in conn.serve cannot contain it. For a server with a container memory limit, one unauthenticated 20 byte write ends the process.

The fix

RFC 6733 section 3 defines Message Length as the length of the message including the header, so a value below HeaderLength describes no message that can exist. Rejected in Header.DecodeFromBytes, with a second check in readBody covering a Header built by hand rather than decoded from the wire.

Two tests added; both fail without the patch. The boundary is inclusive — a bodyless message is exactly HeaderLength and is still accepted.

Found with go test -fuzz. go test ./... passes on all packages.


Independent of #261 (nil Data dereference) — that one touches a different region of message.go, and the two branches auto-merge. Either can go first.

RFC 6733 section 3 defines Message Length as the length of the message
including the 20 byte header, so a smaller value describes no message that can
exist. DecodeFromBytes accepted it anyway, and readBody then computed
int(Header.MessageLength - HeaderLength) in unsigned arithmetic: for a declared
length of 0 that is uint32(0)-20, or 4294967276, which readerBufferSlice hands
straight to make().

Twenty bytes on the wire therefore allocate very nearly 4 GiB, measured at
4294970504 bytes and 12.5s on this commit's parent. The only gate before
readBody is FindCommand, which merely requires the command and application to
exist in the dictionary, and CER — command code 257, application 0, the first
message of any Diameter connection — satisfies that on any dictionary. So the
allocation is reachable from any peer that can open a connection, before
capabilities are exchanged and before any handler runs. An out-of-memory is a
fatal runtime error rather than a panic, so the recover in conn.serve cannot
contain it, and under a container memory limit the process dies.

Rejected in Header.DecodeFromBytes, with a second check in readBody to cover a
Header that was built by hand rather than decoded from the wire.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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