github hellobertrand/zxc 0.13.3
ZXC v0.13.3

latest release: v0.13.3
5 hours ago

Release Notes

v0.13.3 is a hardening and packaging release.

The headline isn't really in the code: ZXC is now enrolled in Google OSS-Fuzz, which runs five harnesses around the clock. It has already turned up two decoder edge cases, both fixed here. Neither is reachable from an archive ZXC itself produced — you need a hand-crafted file to hit them — but a decompressor that can be walked out of its buffer is a decompressor worth fixing.

The rest is housekeeping. The GHI header's enc_off field is now formally unused, which is the ambiguity that let the first bug through; match-offset validation lost a counter it never needed; and static linking on Windows works again.

Nothing changes in the format or the ABI. v0.13.3 reads and writes the same format v7 archives as the rest of the v0.13.x line, and the soname is still libzxc.so.4.

Drop-in for v0.13.x. No format break, no ABI change, nothing to re-link. Archives move both ways between v0.13.3 and any earlier v0.13.x release. One detail worth knowing: at levels 1 and 2 the encoder now writes the unused enc_off GHI header field as 0 rather than 1, so output at those levels is no longer byte-for-byte what v0.13.2 produced. It is still standard format v7, and every v0.13.x release reads it. Levels 3-7 are byte-identical.

Reliability & Security Fixes

Out-of-bounds read on forged GHI offsets (decoder)

The GHI safe loop took its offset-validation threshold from the enc_off header field: 256 bytes when enc_off == 1. That field never described GHI offsets, which live inline in the sequence word and are always 16 bits wide. So a crafted archive could claim enc_off = 1, carry a much larger offset, and once 256 bytes had been written the loop simply stopped checking — and the copy read from before the start of the output buffer. The threshold is now the 16-bit bound, unconditionally. Found by OSS-Fuzz (544800535). (#367)

Output-buffer overflow on escaped literal lengths (decoder)

When a sequence escapes its literal length into a varint, the GLO and GHI decode macros used to check that the literals fit in the remaining output space — and nothing else. They ignored the match length that follows, and the output still owed by the rest of the batch. A crafted archive could clear that check and then write past the end of the destination buffer. Both macros now weigh literals, match length and the batch remainder together, and the trailing-literal copy has an explicit guard in front of it. Found by OSS-Fuzz (546426939). (#369)

enc_off is unused in GHI blocks and must be ignored

#367 was really a spec problem, so the field is now nailed down on both sides. The compressor always writes enc_off = 0 in GHI headers, and FORMAT.md says decoders must ignore it. GHI has no offset stream and its sequence words always carry 16-bit offsets, so there is nothing for the field to bound. (#368)

Stale dictionary size on a reused compression context

zxc_compress_cctx() never reset dict_size. Reuse a context after a dictionary compression and the old dictionary size came along for the ride into the next, plain one. It is reset on every call now. (#351)

Performance

Leaner match-offset validation

Match-offset validation used to lean on a running count of bytes written, updated on every single sequence. That counter is gone. The decoder now compares the match source directly to the start of the output buffer, or of the dictionary prefix when one is attached. Same guarantee, less bookkeeping in the hot loop. (#368)

Build, Packaging & Portability

Windows static-library linking

__declspec(dllimport) and static libraries don't get along on Windows: the link fails outright. ZXC_DLL_IMPORT is opt-in from now on, so plain declarations link against both the DLL and the static library. CMake, Meson and pkg-config each propagate the right define — ZXC_DLL_IMPORT for shared, ZXC_STATIC_DEFINE for static — and a new packaging workflow builds a real consumer against every combination on Linux and Windows, so it stays fixed. (#350)

CMake build modularized

CMakeLists.txt had grown past 700 lines. It is now split into focused modules under cmake/ — version, options, platform detection, compiler flags, dependencies, SIMD variants, tests, docs, formatting, WASM, install, summary — with the top-level file left as the orchestrator. Consumers see no difference. (#347)

Meson CLI parity

The Meson build now passes the MSVC compiler and linker flags CMake already had, and installs the unzxc symlink on POSIX systems. (#348)

Quality & Docs

  • Continuous fuzzing moved to Google OSS-Fuzz. Five harnesses — roundtrip, decompress, streaming, seekable, dictionary — run there around the clock. The same harnesses still run under ClusterFuzzLite (ASan + UBSan) on every pull request that touches the library. README badge and workflow triggers updated to match.
  • Public headers and internal comments got a pass: Doxygen blocks tightened, internal explanations corrected where they had drifted from the code, clang-format extended to tests/. (#366)
  • The level-ordered layout behind the PivCo-Huffman section codec is credited to Marcin Żukowski (@MarcinZukowski), and the joint flat/length nudge to Dougall Johnson (@dougallj). Both are implemented independently here.
  • Dependency bumps across the Rust, Python and Node.js wrappers, the fuzzing base image and the GitHub Actions workflows.

Changelog

  • api: Refine comments for improved clarity (#366)
  • perf: Optimizes decode offset validation and clarifies GHI format (#368)
  • cli: Add MSVC CLI build configuration and unzxc alias (#348)
  • build: Tag the Go submodule on release (#371)
  • build: Publish the WebAssembly build to npm (#372)
  • build: Update node.js wrapper dependencies
  • build: bump scikit-build-core from 0.12.2 to 1.0.3 in /wrappers/python (#362)
  • build: bump cibuildwheel from 4.1.0 to 4.1.1 in /wrappers/python (#360)
  • build: bump setuptools-scm from 10.2.0 to 10.2.1 in /wrappers/python (#361)
  • build: Remove scheduled workflow trigger
  • build: bump cc from 1.2.65 to 1.4.0 in /wrappers/rust (#355)
  • build: bump thiserror from 2.0.18 to 2.0.19 in /wrappers/rust (#354)
  • build: bump libc from 0.2.186 to 0.2.189 in /wrappers/rust (#357)
  • build: bump vitest from 4.1.9 to 4.1.10 in /wrappers/nodejs (#358)
  • build: Reset dict_size in compression context (#351)
  • build: Prevent Windows static library linking failures (#350)
  • build: Modularize CMakeLists.txt into dedicated modules (#347)
  • doc: Update fuzzing status and details in README
  • fix: Ensure correct offset validation bounds for GHI decompression (ossfuzz #544800535) (#367)
  • fix: Enhance output buffer overflow checks during decompression (ossfuzz #546426939) (#369)
  • misc: Add PivCo-Huffman attribution for algorithm elements
  • misc: bump meson from 1.11.1 to 1.11.2 in /.github/requirements (#363)
  • misc: bump oss-fuzz-base/base-builder in /.clusterfuzzlite (#359)
  • misc: bump cross-platform-actions/action (#365)
  • misc: bump github/codeql-action/upload-sarif (#364)
  • misc: bump actions/checkout from 6.0.2 to 7.0.1 (#356)
  • misc: bump softprops/action-gh-release from 3.0.1 to 3.0.2 (#353)
  • misc: bump actions/setup-python from 6.3.0 to 7.0.0 (#352)

Full Changelog: v0.13.2...v0.13.3

Don't miss a new zxc release

NewReleases is sending notifications on new releases.