Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion decode_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,12 @@ func (d *Decoder) DecodeUntypedMap() (map[interface{}]interface{}, error) {
return nil, nil
}

m := make(map[interface{}]interface{}, n)
ln := n
if d.flags&disableAllocLimitFlag == 0 {
ln = min(ln, maxMapSize)
}

m := make(map[interface{}]interface{}, ln)
Comment on lines +253 to +258

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)


for i := 0; i < n; i++ {
mk, err := d.decodeInterfaceCond()
Expand Down
40 changes: 40 additions & 0 deletions msgpack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"math"
"reflect"
"runtime"
"testing"
"time"

Expand Down Expand Up @@ -75,6 +76,45 @@ func (t *MsgpackTest) TestLargeString() {
t.Equal(dst, src)
}

func (t *MsgpackTest) TestDecodeUntypedMapHugeDeclaredLen() {
// A map32 header declaring ~2G entries with no payload: the map size
// 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.
//
// 0x7fffffff rather than 0xffffffff: the latter is now rejected by the
// int-overflow check on 32-bit builds before reaching the clamp, so it
// would pass for the wrong reason. This value fits in an int on every
// platform and exercises the clamp itself.
data := []byte{0xdf, 0x7f, 0xff, 0xff, 0xff}

var before, after runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&before)

dec := msgpack.NewDecoder(bytes.NewReader(data))
_, err := dec.DecodeUntypedMap()
t.NotNil(err)

runtime.ReadMemStats(&after)
// The clamp caps the hint at maxMapSize (1M entries). Without it the
// declared ~2G entries are allocated upfront -- hundreds of MB and tens
// of seconds. Allow generous headroom while still failing loudly if the
// clamp is removed.
if alloc := after.TotalAlloc - before.TotalAlloc; alloc > 256<<20 {
t.Failf("allocation not clamped", "decode allocated %d MiB, want < 256 MiB", alloc>>20)
}
}

func (t *MsgpackTest) TestDecodeUntypedMap() {
in := map[interface{}]interface{}{int8(1): "one", "two": int8(2)}
t.Nil(t.enc.Encode(in))

out, err := t.dec.DecodeUntypedMap()
t.Nil(err)
t.Equal(in, out)
}

func (t *MsgpackTest) TestSliceOfStructs() {
in := []*nameStruct{{"hello"}}
var out []*nameStruct
Expand Down
Loading