Expand description
Containment for panics raised inside third-party parsers.
§Why this exists
ADR-0006 requires strypt’s own parsing code never to panic: malformed input is expected
input, and failures are typed Result values. That rule cannot be extended to a dependency
by wishing. lopdf is a third-party PDF parser handling attacker-controlled bytes
(ADR-0018), and docs/THREAT_MODEL.md §5.1 names a panic inside it as one of the realistic
residual risks that forbid(unsafe_code) does not address.
That risk stopped being theoretical: a sustained fuzz run reached an integer overflow in
lopdf 0.44.0’s cross-reference parser (parser/mod.rs:516, start + index where start
comes from the file). Because Cargo.toml deliberately enables overflow-checks in release
so that an overflow aborts rather than wrapping into a nonsensical offset, the shipped
binary panicked — a user handed a hostile PDF got a stack trace and exit code 101 instead of
“this file could not be processed”.
§What this does, and what it does not
guard runs a closure and converts an unwinding panic into a typed error, so a dependency’s
panic reaches the user as an ordinary refusal. This is containment, not a fix. The
defect stays in the dependency and is reported upstream; this only stops it reaching the
user as a crash.
Three limits, stated because a guard that is trusted beyond its reach is worse than none:
- It requires unwinding panics. Built with
panic = "abort"the process dies before any of this runs. strypt does not setpanic = "abort", and this is a reason not to. - It cannot catch what does not unwind — a stack overflow from deep recursion, an
abort, or a SIGSEGV. Bounded recursion (
ParseLimits) is the control for the first. - It says nothing about correctness. A dependency that panicked may equally return a wrong answer without panicking, which no guard detects. Fail-closed refusal on panic is a floor, not a guarantee.
§Why the panic message is suppressed
The default panic hook prints to stderr. A panic message from a parser can quote the bytes it was parsing, and those bytes are the user’s document — the metadata they are trying to destroy. CLAUDE.md §3.8 forbids printing metadata values, so a guarded panic must not print the default message. The hook is installed once and delegates to the previous hook whenever the guard is not active, so unguarded panics elsewhere still report normally.
Functions§
- guard
- Run
f, converting an unwinding panic intoon_panic().