Skip to content

fix: zero-length buffer panic and invalid empty-input compression - #2

Open
AskAlexSharov wants to merge 1 commit into
mainfrom
fix/cgo-binding-bugs
Open

fix: zero-length buffer panic and invalid empty-input compression#2
AskAlexSharov wants to merge 1 commit into
mainfrom
fix/cgo-binding-bugs

Conversation

@AskAlexSharov

@AskAlexSharov AskAlexSharov commented Jul 23, 2026

Copy link
Copy Markdown

Two correctness bugs in the CGo bindings, each with a regression test.

Bug 1 — zero-length dst panics instead of returning an error

Every compress/decompress method guarded only empty src, then took &dst[0] unconditionally:

if len(src) == 0 { return 0, nil }
... unsafe.Pointer(&dst[0]) ...   // panics when len(dst) == 0

So any call with a zero-length output buffer panicked with index out of range instead of the documented "output buffer too small" error. This is reachable through the public APIGzipDecompress(compressed, 0) allocates make([]byte, 0) and crashed the caller:

libdeflate.GzipDecompress(compressed, 0) // panic: runtime error: index out of range [0] with length 0

Each method now returns an error for an empty dst.

Bug 2 — compressing empty input produced an invalid stream

The compress methods short-circuited empty input to (0, nil), so GzipCompress(nil) returned a zero-byte slice. Zero bytes is not a valid gzip/zlib/DEFLATE stream — a standard decoder rejects it as truncated:

b, _ := libdeflate.GzipCompress(nil, libdeflate.DefaultCompression) // len(b) == 0
gzip.NewReader(bytes.NewReader(b))                                  // io.ErrUnexpectedEOF

The methods now pass empty input through to libdeflate, which emits a valid empty stream (~20 bytes for gzip). libdeflate handles this safely — deflate_compress_none special-cases in_nbytes == 0, and crc32(0, NULL, 0) reads nothing — so a nil data pointer is passed for empty src (taking &src[0] would itself panic).

Tests

  • TestEmpty{Gzip,Zlib,Deflate}IsValidStandardStream — compress empty input and decode it with the standard library (compress/gzip, compress/zlib, compress/flate) to prove the output is a conformant stream, not just round-trippable by this package.
  • TestZeroLengthDstReturnsErrorNotPanic — the six methods and GzipDecompress(_, 0) return errors instead of panicking.

All tests pass under go test -race; gofmt, go vet, and golangci-lint are clean.

Scope

Based on main. The runtime.SetFinalizer work on alex/compressor-finalizer is not included, so the finalizer-vs-KeepAlive premature-free issue is intentionally out of scope here.

1. Zero-length dst buffer panicked instead of returning an error.

   The methods guarded only empty src, then unconditionally took
   &dst[0]. Calling any compress/decompress method with a zero-length
   dst therefore panicked with "index out of range" instead of the
   documented "output buffer too small" error. This was reachable
   through the public API — e.g. GzipDecompress(compressed, 0) builds a
   zero-length buffer via make([]byte, 0) and crashed the caller. Each
   method now returns an error for an empty dst.

2. Compressing empty input produced an invalid (empty) stream.

   The compress methods short-circuited empty src to (0, nil), so
   GzipCompress(nil) returned a zero-byte slice. Zero bytes is not a
   valid gzip/zlib/DEFLATE stream — a standard decoder (including
   compress/gzip) rejects it as truncated input. The methods now pass
   the empty input to libdeflate, which emits a valid empty stream
   (~20 bytes for gzip). A nil data pointer is used for empty src, since
   &src[0] would panic and libdeflate accepts NULL when the length is 0.

Adds tests (edgecases_test.go) that decode our empty-input output with
the standard library (gzip/zlib/flate) to prove the streams are
conformant, and tests that the zero-length dst paths return errors
rather than panicking.
@AskAlexSharov
AskAlexSharov force-pushed the fix/cgo-binding-bugs branch from 20530c4 to 08486e9 Compare July 24, 2026 02:32
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.

1 participant