# 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

- No on-disk repository or wire format changed; Fossil RFC revision 00
  remains the target.
- A container whose zlib stream ends partway through a dynamic block header
  is now refused with an error instead of crashing the process.

## 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.
