Skip to content
rusthacks.dev
Security

Integer Overflow Bugs in Rust Release Builds and Fixes

Integer overflow panics in debug builds and wraps in release builds by default. Learn how that becomes a vulnerability, which arithmetic to use for untrusted numbers and how to enable checks in release.

3 min read
Summarize in AI
Flat illustration for the post “Integer Overflow Bugs in Rust Release Builds and Fixes”, composed around security.

Rust prevents out-of-bounds memory access, which leads many readers to think arithmetic bugs cannot hurt them. An overflowed length does not corrupt memory in safe Rust. It produces a wrong number that later logic trusts: a price that wraps to a small value, a quota check that passes, a buffer of 4 bytes where 4 GB was requested. This post shows how it happens and the arithmetic to use. It builds on release versus debug, in the security guide.

Rust 1.90, edition 2024.

Two builds, two behaviors

rust
fn total_cents(unit: u32, qty: u32) -> u32 {
    unit * qty
}

fn main() {
    println!("{}", total_cents(100_000, 100_000));
}

In a debug build the program panics:

Text
thread 'main' panicked at src/main.rs:2:5:
attempt to multiply with overflow

In a release build it prints 1410065408, the product modulo 2³². The real answer is 10,000,000,000. A checkout that computes the amount to charge this way charges 14 million cents instead of 10 billion. The code is memory safe and wrong.

Make the intent explicit

rust
fn total_cents(unit: u32, qty: u32) -> Option<u32> {
    unit.checked_mul(qty)
}
MethodOn overflowUse for
checked_add, checked_mulreturns Noneuntrusted numbers, with error handling
saturating_addclamps at MAXcounters, scores
wrapping_addwraps, by choicehashes, checksums, ring indices
overflowing_addreturns value and flagmulti-word arithmetic

Choosing a variant documents what you meant and behaves the same in debug and release. For a chain of operations, use try_fold or the ? operator on Option.

Casts are the quiet one

rust
let big: u64 = 5_000_000_000;
let small = big as u32;   // 705032704, no warning
let safe = u32::try_from(big); // Err(TryFromIntError)

as truncates and wraps without complaint. Clippy has lints for this (cast_possible_truncation, cast_sign_loss in the pedantic group). Use try_from for anything that came from input, and From when the conversion is lossless (From and Into).

Sizes and allocations

The classic bug: let bytes = count * item_size; with attacker-controlled count, then Vec::with_capacity(bytes). A wrapped product allocates less than the loop writes, or in unsafe code corrupts memory. In safe Rust the loop still bounds-checks, so you get a panic, not a breach; in unsafe or FFI you get the breach. Use checked_mul for every size computation and cap the result (limits for parsers).

Turning checks on

Cargo.tomlTOML
[profile.release]
overflow-checks = true

Now release builds panic like debug. Expect a small slowdown in arithmetic-heavy code; measure it (benchmarking pitfalls). Many security-minded projects accept the cost. Individual hot loops can opt out with wrapping_* methods.

Finding the sites

Clippy's arithmetic_side_effects lint (restriction group) flags every unchecked operation. Enable it on the modules that handle input. Fuzzing with overflow checks on finds the inputs that trigger it.

Limits

This post covers integer types. Floating-point has its own surprises with NaN and infinity, and usize is 32 bits on some targets. The language reference describes the behavior in integer overflow.

Taggedoverflow
Share

Keep reading

Flat illustration for the post “Request Limits and Slow Client Attacks in Rust Services”, composed around web.
Security

Request Limits and Slow Client Attacks in Rust Services

Slow clients and oversized requests exhaust connections and memory. Set header and body timeouts, body size limits, concurrency limits and keep-alive bounds at the proxy and in the Axum service with Tower layers.

5 min read

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.