From 2bd19cf6f0f394b7b467aa76bc5c05e5ce87b760 Mon Sep 17 00:00:00 2001 From: "Chris (ChrisJr404)" <11917633+ChrisJr404@users.noreply.github.com> Date: Fri, 21 Aug 2026 09:01:57 -0400 Subject: [PATCH] Guard against truncated and malformed string lengths in Decode Decode panicked with makeslice: len out of range on tiny malformed inputs such as "-1:" (negative length) or "900000000000000000:" (length larger than the slice allocator allows). The string branch of the incremental unmarshaler parsed an attacker-controlled length and immediately called make([]byte, stringLength) with no validation, so a crafted length crashed the decoder or exhausted memory. Reject negative lengths and copy the string incrementally with io.CopyN so the buffer only grows to the number of bytes actually available, reporting a truncated string as an error instead of pre-allocating an untrusted size. --- decode_stringlen_test.go | 36 ++++++++++++++++++++++++++++++++++++ incswparse.go | 17 +++++++++++++---- 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 decode_stringlen_test.go diff --git a/decode_stringlen_test.go b/decode_stringlen_test.go new file mode 100644 index 0000000..f347b8d --- /dev/null +++ b/decode_stringlen_test.go @@ -0,0 +1,36 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bencode + +import ( + "bytes" + "strings" + "testing" +) + +// A malformed string length (negative, or so large it overflows the slice +// allocator) must be reported as an error, not crash the decoder. +func TestDecodeMalformedStringLength(t *testing.T) { + for _, in := range []string{ + "-1:", + "900000000000000000:", + "5:abc", // truncated: claims 5 bytes, only 3 present + } { + if _, err := Decode(strings.NewReader(in)); err == nil { + t.Errorf("Decode(%q) = nil error, want error", in) + } + } +} + +// A well-formed string must still decode correctly after the length check. +func TestDecodeStringRoundTrip(t *testing.T) { + got, err := Decode(bytes.NewReader([]byte("5:hello"))) + if err != nil { + t.Fatalf("Decode: unexpected error: %v", err) + } + if got != "hello" { + t.Errorf("Decode = %q, want %q", got, "hello") + } +} diff --git a/incswparse.go b/incswparse.go index 2513872..fc6c938 100644 --- a/incswparse.go +++ b/incswparse.go @@ -4,6 +4,8 @@ import ( "bufio" "bytes" "errors" + "fmt" + "io" "strconv" ) @@ -105,12 +107,19 @@ func unmarshal(data *bufio.Reader) (interface{}, error) { if err != nil { return nil, err } + if stringLength < 0 { + return nil, fmt.Errorf("bad string length: %d", stringLength) + } - buf := make([]byte, stringLength) - - _, err = readAtLeast(data, buf, int(stringLength)) + var buf bytes.Buffer + if _, err = io.CopyN(&buf, data, stringLength); err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + return nil, err + } - return string(buf), err + return buf.String(), nil } }