Skip to content

fix: clamp DecodeUntypedMap allocation at maxMapSize#76

Open
xe-nvdk wants to merge 1 commit into
v6from
fix/untyped-map-alloc-limit
Open

fix: clamp DecodeUntypedMap allocation at maxMapSize#76
xe-nvdk wants to merge 1 commit into
v6from
fix/untyped-map-alloc-limit

Conversation

@xe-nvdk

@xe-nvdk xe-nvdk commented Jun 12, 2026

Copy link
Copy Markdown
Member

Summary

DecodeUntypedMap allocated map[interface{}]interface{} with the attacker-declared capacity — the only map decode path missing the maxMapSize clamp (decodeMapValue, decodeMapStringInterfaceN, and decodeTypedMapN all have it). A map32 header declaring ~4G entries forced a multi-GB upfront map allocation before any payload was read. Found by the internal security review pass on #75.

The fix mirrors the sibling paths exactly: clamp the size hint at maxMapSize (1M) unless DisableAllocLimit(true) is set. The map still grows to the real size if the payload genuinely contains more entries.

Testing

  • TestDecodeUntypedMapHugeDeclaredLen: map32 declaring ~4G entries fails fast (previously attempted a multi-GB allocation).
  • TestDecodeUntypedMap: round-trip coverage for the path (it had none).
  • go test ./... and go test -race ./... pass.

No benchmark changes: the clamp is one branch on a cold path (map header decode).

🤖 Generated with Claude Code

DecodeUntypedMap allocated map[interface{}]interface{} with the
attacker-declared capacity, unlike every sibling map decode path
(decodeMapValue, decodeMapStringInterfaceN, decodeTypedMapN), which
clamp the size hint at maxMapSize unless DisableAllocLimit is set. A
map32 header declaring ~4G entries forced a multi-GB upfront map
allocation before any payload was read.

Found by internal security review of #63.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request limits the initial allocation size of untyped maps during decoding to prevent excessive memory usage when a large map size is declared. Feedback focuses on 32-bit architecture compatibility: first, a signed integer overflow can bypass the nil map check and cause a panic in make, which requires adding a check for n < -1; second, the test case using 0xffffffff will overflow to -1 on 32-bit systems and should be updated to 0x7fffffff to avoid false positives.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread decode_map.go
Comment on lines +224 to +229
ln := n
if d.flags&disableAllocLimitFlag == 0 {
ln = min(ln, maxMapSize)
}

m := make(map[interface{}]interface{}, ln)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

On 32-bit architectures, an extremely large declared map size (e.g., 0x80000000 / 2147483648) can overflow the signed 32-bit int and result in a negative value other than -1 (e.g., -2147483648). This bypasses the n == -1 check, leading to a negative ln value and causing make(map[interface{}]interface{}, ln) to panic with panic: size out of range.

To prevent this panic, we should check if n < -1 and return an error.

Note: The other map decoding paths (decodeMapValue, decodeMapStringStringPtr, and decodeTypedMapN) also suffer from this same overflow panic on 32-bit systems and should be updated similarly.

	if n < -1 {
		return nil, fmt.Errorf("msgpack: invalid map length: %d", n)
	}

	ln := n
	if d.flags&disableAllocLimitFlag == 0 {
		ln = min(ln, maxMapSize)
	}

	m := make(map[interface{}]interface{}, ln)

Comment thread msgpack_test.go
// hint must be clamped at maxMapSize before allocation (with the old
// code this allocated a multi-GB map upfront), then fail decoding the
// first key.
data := []byte{0xdf, 0xff, 0xff, 0xff, 0xff}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

On 32-bit architectures, 0xffffffff (4294967295) overflows the signed 32-bit int and becomes -1. This causes DecodeMapLen() to return -1, which is interpreted as a nil map, making DecodeUntypedMap() return nil, nil without an error. As a result, this test will fail on 32-bit platforms because err is nil.

Using 0x7fffffff (2147483647) instead avoids the overflow on 32-bit systems while still being large enough to trigger the allocation clamp and fail decoding due to the missing payload.

Suggested change
data := []byte{0xdf, 0xff, 0xff, 0xff, 0xff}
data := []byte{0xdf, 0x7f, 0xff, 0xff, 0xff}

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