Skip to content
zit zit 0.3.29
Login

zit 0.3.29

zit 0.3.29 fixes a crash in the zlib decoder that every sync request and reply passes through. Any peer could trigger it with 32 bytes.

No artifact, tag namespace, sync message, or on-disk repository format changed.

A truncated dynamic-block header no longer crashes the decoder

The nightly fuzz soak failed four nights running on the same target, sync_container_round_trip, each time with a different random seed and each time as a bare segmentation fault. The cause was in the inflate zit carries from Zig 0.16's standard library, and it's the same kind of slip as the one the soak found there a week ago: end-of-input bit accounting.

Before tossing a code-length symbol, tossBitsShort checks that enough bits remain. It computed them as bufferedLen() * 8 + consumed_bits, adding the bits already consumed from the current byte where it has to subtract them. With one byte left and six of its bits consumed, a seven-bit toss passed the check and advanced the reader past its end. The next peek sliced buffer[seek..end] with seek past end, got a length of 2⁶⁴−1, and read up the stack until it hit an unmapped page.

Random garbage reaches it: the soak's input was 32 bytes whose dynamic-block header ends one byte short. A peer could send the same bytes as a sync request or reply. The check is now written without subtraction, like the earlier fix, so a short header is just the end of the stream and decode returns an error.

The regression test is the soak's own input. It panics a Debug build without the fix and passes with it. All four nights' seeds pass at the soak's round count. The vendored file's header now records both fixes.

A note on why it was hard to see. A unit test on the captured bytes passed at first, because it used a 1 MiB output cap and these bytes declare 287 MB, so decode refused them before the inflate ran. The fuzzer uses the protocol's own reply cap. The test now admits the declared size.

The soak stops at its first failure

The four failures also hid something. The soak stops at the first failing target, and sync_container_round_trip is sixteenth of twenty-eight. The twelve after it, including transport_frame_garbage, which drives this same decoder, and serve_request_garbage, got no deep run for four nights. A full soak was dispatched with this release to cover them.

Compatibility

Qualification

zig fmt --check, zig build test, e2e, smoke, git-import-e2e, git-export-e2e, dst, and every fuzz target at 256 rounds, plus the three-runner CI battery and a full-depth soak.