Skip to content

Fix panic on malformed string length in Decode - #22

Open
ChrisJr404 wants to merge 1 commit into
jackpal:masterfrom
ChrisJr404:fix-decode-string-length-panic
Open

Fix panic on malformed string length in Decode#22
ChrisJr404 wants to merge 1 commit into
jackpal:masterfrom
ChrisJr404:fix-decode-string-length-panic

Conversation

@ChrisJr404

Copy link
Copy Markdown

Decode panics with makeslice: len out of range on tiny malformed inputs, because the string branch of the incremental unmarshaler allocates an attacker-controlled length before validating it.

Failure mode

The string case in unmarshal (incswparse.go) parses a length prefix and immediately does make([]byte, stringLength) with no bounds check:

stringLength, err := strconv.ParseInt(string(stringLengthBuffer), 10, 64)
...
buf := make([]byte, stringLength)
_, err = readAtLeast(data, buf, int(stringLength))

A negative length or one larger than the slice allocator allows crashes the decoder (makeslice: len out of range); a large positive length can also exhaust memory since the full size is allocated up front from a stream whose real length is unknown. The parse.go decoder used by Unmarshal already rejects negative lengths, but the Decode path (which uses the incremental unmarshaler) does not.

Repro

bencode.Decode(strings.NewReader("-1:"))                  // panic: makeslice: len out of range
bencode.Decode(strings.NewReader("900000000000000000:")) // panic: makeslice: len out of range

Fix

Reject negative lengths, and copy the string with io.CopyN into a bytes.Buffer so the buffer only grows to the number of bytes actually available; a truncated string is reported as io.ErrUnexpectedEOF instead of pre-allocating an untrusted size. Well-formed strings decode unchanged.

Tests

Added decode_stringlen_test.go covering negative, oversized, and truncated lengths (all now return an error rather than panicking) plus a round-trip check that valid strings still decode. Fails before the change (panics), passes after; full go test ./... is green.

Comment thread incswparse.go Outdated
return nil, err
}
if stringLength < 0 {
return nil, errors.New("bad string length")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Include the stringLength in the error string, so that the error is more informative.

Comment thread incswparse.go Outdated
buf := make([]byte, stringLength)

_, err = readAtLeast(data, buf, int(stringLength))
// Read exactly stringLength bytes. Copy incrementally rather than

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Avoid wordy comments like this. Is it possible that you are using a LLM to generate your code? If so, instruct it to use the correct go style, and manually fix up your commits to remove comments like this.

LLMs like to generate comments like this, to explain to the user what the code is doing, but it just clutters the code without adding any value.

@jackpal jackpal left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Overall looks promising

@ChrisJr404

Copy link
Copy Markdown
Author

Thanks, added the length to the error and trimmed the noisy comments down to match the file's style.

Comment thread decode_stringlen_test.go Outdated
"testing"
)

// A malformed string length (negative, or so large it overflows the slice

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

That's too brief -- please bring back the original commits comments on the tests.

Comment thread incswparse.go Outdated
// declares a huge string. io.CopyN only grows the buffer to the number
// of bytes actually available, and reports a truncated string as an
// error.
// Copy incrementally so an untrusted length can't force a huge

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Please remove this comment, it doesn't add anything.

@jackpal

jackpal commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Thanks! Could you update the pull request to include the latest round of feedback on comments, and also squash the three commits together to have just one commit?

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.
@ChrisJr404
ChrisJr404 force-pushed the fix-decode-string-length-panic branch from 0862321 to 2bd19cf Compare August 21, 2026 13:02
@ChrisJr404

Copy link
Copy Markdown
Author

Done, brought the test comments back, removed the one on incswparse.go, and squashed it to a single commit.

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.

2 participants