fix: cap how much a ZIP entry can inflate when reading ebooks #41
Loading…
Reference in a new issue
No description provided.
Delete branch "jtk/zip-size-caps"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #14.
Problem:
ZipReaderread whole entries into memory: the zstream path collected every chunk, and the:zipfallback used:zip.extract(..., :memory). A crafted EPUB/CBZ with a multi-GBcontainer.xml, OPF or cover (a zip bomb, ~1000:1 with deflate) could exhaust memory and take down the BEAM during a scan.Fix:
ZipReader.read_entry/4takesmax_bytes:(default 50 MB;ZipReader.xml_max_bytes/0= 5 MB forcontainer.xml, the OPF andComicInfo.xml).:zipfallback (archives with data descriptors): checks the central directory's uncompressed size first, then runs:zip.extractin a separate process withmax_heap_size(3× the cap, withinclude_shared_binaries: true, floor 8 MB). If the size was a lie the process is killed, not the VM. It also checksbyte_sizeof what comes back.Tests:
zip_reader_test.exs: under/over the cap for both plain and data-descriptor archives (built with:zip.createandZstream.zip), a streamed archive whose central directory lies about a 30 MB entry, and an EPUB whosecontainer.xmlis over 5 MB. I also checked by hand that the heap-capped process is the one that dies (:killed) on the lying archive.mix precommit: 493 passed.Review from Nye: APPROVE at
bb8ce0a(posted by Upper Wolfjaw; Nye's token is read-only)CI green, merges cleanly with main and with #37–#40. Checked: Zstream 0.6.7 Entry.size is the local-header uncompressed_size, so the header check is valid and the inflated-byte counter catches lies; data-descriptor archives fall to :zip, where list_dir's file_info size is the central-dir size, and spawn_monitor + max_heap_size (include_shared_binaries, kill) catches lies there; :killed maps to :too_large; no other :zip/Zstream callers outside ZipReader.
Non-blocking lows (filed separately): (1) Zstream inflates every entry it passes while searching, so a huge entry before the target costs unbounded CPU (memory stays bounded); (2) extract_capped's receive has no
aftertimeout, so a stuck :zip extraction could block the Oban worker forever.