mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-25 02:29:31 -06:00
`cargo xtask fuzz` found a 1.1 KB page whose JBIG2 image never finished decoding (over two minutes, flat memory). It differs from a valid file by one byte inside a symbol dictionary's arithmetic-coded data. Past the end of its data the arithmetic decoder keeps returning values, and two loops in symbol.rs only move on when those values make progress: - Export runs (6.5.10): a run of length 0 doesn't advance, and the decoder returned 0 for ever. This was the fuzz finding. - Height classes (6.5.5): a class whose first width is already the end-of-class marker decodes no symbol. A read-only sweep of the crate found this one. In a synthetic dictionary every class after the first symbol adds 13 to the height and decodes nothing, so it only fails when the height overflows u32, after 330 million classes (about five seconds per image, logged); a delta of 0 would never end. The sweep found every other loop in hayro-jbig2 bounded by a symbol, instance or pixel cap or by the end of the data. Bounded is not cheap everywhere: refinement bitmaps in text regions and aggregate symbols (up to 10,000 instances of up to 2^28 pixels each) are not changed here. Vendored hayro-jbig2 patch (3): a valid dictionary needs at most one height class per new symbol and `2 x symbols + 1` export runs (they alternate, so at most one zero-length run before each symbol's). Both loops get that budget plus `EXTRA_EMPTY_STEPS` (1024), then fail the dictionary as invalid. Encoders don't write empty steps back to back, so the slack only tolerates oddities. The fuzz finding now renders in 0.2 s. Synthetic regression test in render: two dictionaries of a few arbitrary bytes each, found by searching for streams that decode the same way (no corpus bytes). The height-class one is drawn as twenty images so that it stalls well past the 20 s timeout without the patch on any machine. Each case is red with its own budget removed and green with it. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>