C Thirty-two\sbytes\sand\sa\sserver\sgoes\sdown D 2026-09-26T17:56:41.426 E 2026-09-23T20:31:21 42e5a2289aee837717e2eb9272f6f6b82925ec73 N text/x-markdown U dmestas W 2355 # Thirty-two bytes and a server goes down 0.3.29 fixes a crash in the zlib decoder that every sync request and reply passes through. Before it, a single anonymous 32-byte POST to `/xfer` took down a `zit serve` process: no login, no valid project code, nothing but the bytes. Upgrade any server running 0.3.28 or earlier. It surfaced as a nightly soak that failed four nights running, each time on the same fuzz target, `sync_container_round_trip`, each time with a different seed, and each time as a bare segmentation fault with no stack trace. The changing seed was the first clue that this was not one unlucky input: the soak draws fresh seeds every night, and this bug was common enough that any seed reached it within a quarter of a million rounds. The crash was not where a stack overflow would put it. At the fault, the decoder's input reader had its read position at 29 and its end at 28. A slice from 29 to 28 has a length of 2⁶⁴−1, and the next read walked up the stack until it met the unmapped page above it. What moved the position past the end was a check before skipping a few bits of input. The inflate zit carries is the one from Zig 0.16's standard library, and its `tossBitsShort` asked whether enough bits remained as `buffered bytes × 8 + bits already consumed`. The consumed bits have to be subtracted, not added. With one byte left and six of its bits used, a seven-bit skip passed the check and stepped off the end. It was the same kind of mistake the soak had found in the same file a week earlier: arithmetic at the end of the input. Both checks are now written with no subtraction, and those two are the only guards on the end of input in the decoder. The regression test is the soak's own input, and it took a detour to make it honest. The first version passed without the fix, because it gave `decode` a 1 MiB output cap and these bytes declare 287 MB, so the input was refused before the inflate ever ran. The fuzzer uses the protocol's real cap. With that, the test panics a Debug build without the fix and passes with it. One thing the four red nights also hid: the soak stopped at its first failure, and this target is sixteenth of the sweep, so the twelve after it went four nights without a deep run. A full soak after the fix covered them, all clean, and the soak now records a failure and keeps sweeping. Z cb69d68bec7bf67d4b33b35e9b155776