Repository navigation
fix: harden Huffman edge cases after security review - #11
Merged
ChillerDragon merged 3 commits intoAug 16, 2026
Merged
Conversation
DecompressTo could hand make() a capacity larger than MaxInt on a 32 bit platform: initCap is clamped to maxAlloc, but len(dst) + initCap was not, and make() panics rather than failing gracefully on a size it cannot represent. The total is now clamped too. The sizing arithmetic is extracted into decompressInitCap, growCap and compressBufSize, parameterised by the platform limit, so the 32 bit behaviour can be unit-tested on a 64 bit host instead of only being reachable on real hardware. Adds compile-time assertions pinning maxAlloc to exactly MaxInt on every target. Verified they are not vacuous: breaking maxAlloc fails the build on both 32 and 64 bit. Builds and vets cleanly for 386, arm, mips, mipsle (big-endian), windows/386, freebsd/386 and s390x. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Preserve Reader bit state across short destination buffers, report short underlying writes, and reject custom dictionaries whose codes cannot fit the uint32 representation instead of emitting corrupt streams.\n\nAlso saturate the remaining sizing arithmetic, avoid int overflow in the shrink and refill checks, and add bounded malformed-input fuzz coverage.
ChillerDragon
approved these changes
Aug 16, 2026
ChillerDragon
left a comment
Member
There was a problem hiding this comment.
I don't understand any of this, write some tests and ship it
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #10 after a security and 32-bit compatibility review.
Findings and fixes
Teeworlds/DDNet assessment
The default dictionary is unchanged and tops out at 15 bits. The encoded wire format remains unchanged.
I compared this branch against:
Differential checks covered 1,405 payloads per implementation: every length from 0 through the 1,400-byte network packet limit plus all-symbol, all-zero, and all-0xff cases.
No memory-safety issue is reachable through the Teeworlds/DDNet packet path: both implementations cap UDP packets at 1,400 bytes, and the Go decoder now has a hard one-symbol-per-input-bit expansion bound. Protocol integrations should still enforce their normal decompressed packet-size limit, as the generic Go slice API does not impose the C++ caller-buffer limit itself.
Performance follow-up
I rebuilt merged #10 (
36bf501) and the hardened codec (6dcbb68) with identical benchmark/corpus files, then ran six 250 ms samples per revision in alternating base/head and head/base order on Apple M2 Pro, darwin/arm64, Go 1.26.6.Full results and the reproducible isolated runner are in
BENCHMARKS.mdandmake benchcmp.Verification