Files
Eremey Valetov ad923d7ea0
Some checks failed
Build / Linux (push) Has been cancelled
Build / Windows (MSVC) (push) Has been cancelled
Build / macOS (push) Has been cancelled
Build / libarchive plugin (push) Has been cancelled
Build / DOS (DJGPP) (push) Has been cancelled
Docs / build (push) Has been cancelled
Docs / deploy (push) Has been cancelled
fix heap overflow parsing a damaged central directory
A crafted archive could crash the reader with an out-of-bounds read in
the directory-skip path (uc2_finish_cdir -> uc2_read_cdir -> uc2_get_tag).

decompress_cdir allocates cdir_buf inside its decode loop but, on its
error paths (decode failure or a checksum mismatch), returned before
setting cdir_range.end -- leaving cdir_buf non-NULL with a stale end. A
later uc2_read_cdir/uc2_finish_cdir then saw cdir_buf != NULL, skipped
re-reading, and walked a range whose end pointed below its start, so
range_len wrapped and range_get handed out wild pointers. Free cdir_buf
on every error path so the invariant "cdir_buf != NULL iff cdir_range is
valid" holds, and make range_len report an empty range (rather than a
huge one) if end ever precedes ptr, as defense in depth for the whole
parser.

Also add a compression-ratio ceiling to the cdir decode: a tiny crafted
stream can expand via long matches, so abort once the output far
outgrows the compressed bytes consumed.

Found with a new libFuzzer harness (tests/fuzz/, not built by default).
Memory-safety is clean over sustained fuzzing after this change; 22/22
ctest on Release and ASan. A residual slow-input timeout via a separate
decode path is tracked for follow-up.
2026-06-13 10:53:49 -04:00
..

Fuzzing the UC2 reader

fuzz_extract.c is a libFuzzer harness that drives the full read path (uc2_open -> uc2_read_cdir -> uc2_finish_cdir -> uc2_extract) over arbitrary bytes with an in-memory reader and a discard writer. It targets the code that parses untrusted .uc2 archives.

It is intentionally not part of the CMake build or CI: libFuzzer needs a Clang toolchain, and a fuzz run is open-ended rather than pass/fail. Build and run it by hand.

Build

Compile the harness together with the library sources and the embedded super-master, against a configured build tree (for uc2_version.h and super_data.S):

cmake -B build-asan -DCMAKE_BUILD_TYPE=Debug   # any tree works; provides the generated files
clang -fsanitize=fuzzer,address -O1 -g \
    -Ilib/include -Ilib/src -Ibuild-asan/lib \
    tests/fuzz/fuzz_extract.c $(ls lib/src/*.c) build-asan/lib/super_data.S \
    -lm -o fuzz_extract

Run

mkdir -p corpus && cp tests/archives/*.uc2 corpus/
./fuzz_extract -max_len=65536 -timeout=25 corpus/

ASan flags any out-of-bounds access; libFuzzer writes a crash-* (or timeout-*) artifact for each finding. Re-run a single artifact with ./fuzz_extract <artifact>.

Status

Memory-safety: clean over sustained runs after the 2026-06-13 cdir hardening (git-bug 69e8e52). A residual slow-input (decompression-bomb) timeout is tracked separately; it is a bounded-CPU issue, not a memory-safety one.