Skip to content

Instantly share code, notes, and snippets.

@tazjin
Created August 7, 2025 11:40
Show Gist options
  • Save tazjin/c83381ed2b10192365fe08dfc6b13e56 to your computer and use it in GitHub Desktop.
Save tazjin/c83381ed2b10192365fe08dfc6b13e56 to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"encoding/binary"
"fmt"
"math"
"os"
"github.com/pierrec/lz4"
)
type lz4HeaderSignature uint32
const (
lz4HeaderSignatureV1 lz4HeaderSignature = (1 << 30) + 1
lz4HeaderSignatureV2 lz4HeaderSignature = (1 << 30) + 2
)
const maxLz4BlockSize = 1 << 30
var lz4SignatureV1MaxLength = math.MaxInt32
type lz4Header struct {
Signature lz4HeaderSignature
Size uint32
}
type lz4BlockHeader struct {
CompressedSize uint32
UncompressedSize uint32
}
type lz4DecompressFunc func(src, dst []byte) (int, error)
func lz4BlockDecompress(block []byte, decompress lz4DecompressFunc) ([]byte, error) {
if len(block) == 0 {
return nil, nil
}
if len(block) < 8 {
return nil, fmt.Errorf("unable to decode header: invalid length: %d", len(block))
}
var out bytes.Buffer
h := lz4Header{
Signature: lz4HeaderSignature(binary.LittleEndian.Uint32(block[:4])),
Size: binary.LittleEndian.Uint32(block[4:8]),
}
inputOffset := 8
totalUncompressedSize := uint64(h.Size)
if h.Signature == lz4HeaderSignatureV2 {
if len(block) < 16 {
return nil, fmt.Errorf("unable to decode v2 header: invalid length: %d", len(block))
}
totalUncompressedSize = binary.LittleEndian.Uint64(block[8:16])
inputOffset += 8
}
for inputOffset < len(block) {
if len(block) < inputOffset+8 {
return nil, fmt.Errorf("unable to decode block header: invalid length")
}
blockHeader := lz4BlockHeader{
CompressedSize: binary.LittleEndian.Uint32(block[inputOffset : inputOffset+4]),
UncompressedSize: binary.LittleEndian.Uint32(block[inputOffset+4 : inputOffset+8]),
}
inputOffset += 8
if len(block) < inputOffset+int(blockHeader.CompressedSize) {
return nil, fmt.Errorf("not enough data to decode block: header expects %d bytes, got %d",
blockHeader.CompressedSize, len(block)-inputOffset)
}
compressedBlock := block[inputOffset : inputOffset+int(blockHeader.CompressedSize)]
uncompressedBlock := make([]byte, blockHeader.UncompressedSize)
uncompressedSize, err := decompress(compressedBlock, uncompressedBlock)
if err != nil {
return nil, err
}
if uncompressedSize != int(blockHeader.UncompressedSize) {
return nil, fmt.Errorf("uncompressed block size mismatch: expected %d, got %d",
blockHeader.UncompressedSize, uncompressedSize)
}
if _, err := out.Write(uncompressedBlock); err != nil {
return nil, err
}
inputOffset += int(blockHeader.CompressedSize)
}
if uint64(out.Len()) != totalUncompressedSize {
return nil, fmt.Errorf("uncompressed data size mismatch: expected %d, got %d", out.Len(), h.Size)
}
return out.Bytes(), nil
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: go run reproducer.go <input_file>")
os.Exit(1)
}
// Read input file
data, err := os.ReadFile(os.Args[1])
if err != nil {
fmt.Printf("Error reading file: %v\n", err)
os.Exit(1)
}
fmt.Printf("Read %d bytes from input file\n", len(data))
// Call the decompression that triggers the bug
result, err := lz4BlockDecompress(data, lz4.UncompressBlock)
if err != nil {
fmt.Printf("DECOMPRESSION ERROR: %v\n", err)
os.Exit(1)
}
fmt.Printf("SUCCESS: Decompressed %d bytes\n", len(result))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment