panic! called
| Vulnerability potential | Low |
| DDoS potential | Medium |
panic! aborts the thread; consider returning Result instead
Impact
panic! triggers an unrecoverable error: it runs the panic hook (printing the
message and optionally a backtrace) and then unwinds the current thread, running
destructors as it goes. If the unwind reaches the thread’s boundary the thread
dies; if that thread is main, the process exits with code 101. Under
panic = "abort" the runtime calls abort() immediately and the whole process
dies with SIGABRT, no unwinding.
Using panic! for conditions that are actually recoverable — bad input, a
missing file, a failed network call — converts ordinary error handling into a
crash. In a server each such panic kills at least the handling task and, with
abort or on a critical thread, the entire process.
Vulnerability potential
- Denial of service. If an attacker can reach a
panic!— by supplying input that hits apanic!-guarded branch or violates an asserted precondition — they can crash the request, the worker, or (underpanic = "abort") the whole service. Repeated triggering is a reliable remote DoS. - State and lock damage. A panic mid-operation can leave shared data
partially updated and poisons any
Mutex/RwLockheld across it, so even non-malicious clients see degraded or failing behavior afterward. - Information exposure (minor). Panic messages may include internal details (paths, values, addresses); if surfaced to clients or shared logs they leak implementation information.
It is not memory-unsafe, so vulnerability is Low; the dominant risk is availability, hence the Medium DoS rating.
Technical details
panic! expands to a call into the standard panic machinery
(core::panicking::panic / panic_fmt), which invokes the current panic hook
and then either unwinds or aborts depending on the crate’s panic strategy. The
unwind can be intercepted at a boundary with std::panic::catch_unwind, but only
under panic = "unwind"; library code cannot rely on callers catching it.
Panic vs. Result
Rust separates recoverable errors (Result<T, E>, propagated with ?) from
unrecoverable ones (panic!). panic! is appropriate only for bugs and
violated invariants that indicate the program is in an unexpected state, not for
conditions that arise from valid-but-unfortunate inputs or environment. Reaching
for panic! where a Result belongs is the defect.
Catching the issue
Lint and review
clippy::panic (a restriction lint) flags explicit panic! calls so they can be
justified or replaced; teams often #![deny(clippy::panic)] in library crates,
allowing it only in tests. Review rule: panic! is for “this should be
impossible” invariants, not for handling expected failures.
Refactor and contain
Return Result/Option and propagate with ?; use thiserror/anyhow for
ergonomic error types. Where panics must be tolerated, wrap task/request
boundaries in std::panic::catch_unwind and install a panic hook to log
occurrences for monitoring. Building with panic = "abort" removes the safety
net entirely, so audit panics accordingly.
How to reproduce
Run the following; observe the panic message and exit code 101.
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("division by zero"); // crashes instead of returning an error
}
a / b
}
fn main() {
println!("{}", divide(10, 0));
}