Back to Blog
medium SEVERITY7 min read

Integer Overflow in Rust: How Unchecked Addition Can Bypass File Size Limits

A medium-severity integer overflow vulnerability was discovered and patched in a Rust file transfer receiver, where unchecked byte accumulation could allow attackers to bypass file size limits by exploiting arithmetic wraparound in release builds. The fix replaces a simple `+=` operation with Rust's `checked_add` method, which returns an error instead of silently wrapping around. This is a great reminder that even memory-safe languages like Rust can harbor subtle numeric vulnerabilities in relea

O
By Orbis AppSec
Published May 18, 2026Reviewed June 3, 2026

Answer Summary

This is an integer overflow vulnerability (CWE-190) in a Rust file transfer receiver where unchecked byte accumulation using `+=` allows arithmetic wraparound in release builds, bypassing file size limits. The fix replaces the `+=` operation with Rust's `checked_add()` method, which returns an error on overflow instead of silently wrapping around. This ensures that cumulative byte counts cannot exceed the maximum value of the integer type without triggering an explicit error path.

Vulnerability at a Glance

cweCWE-190
fixReplace `+=` with `checked_add()` and handle the `None` case as an error
riskBypass of file size limits allowing unlimited file uploads
languageRust
root causeUsing `+=` for byte accumulation which wraps around silently in Rust release builds
vulnerabilityInteger Overflow via Unchecked Addition

Integer Overflow in Rust: How Unchecked Addition Can Bypass File Size Limits

Introduction

Rust is celebrated for its memory safety guarantees — no null pointer dereferences, no buffer overflows, no use-after-free bugs. But "memory safe" doesn't mean "numerically safe." A subtle and dangerous class of bugs can still sneak through: integer overflow.

This post dives into a real-world vulnerability found in a Rust file transfer receiver where a single unchecked += operation could allow an attacker to feed unlimited data into a system that believed it was enforcing size limits. We'll break down exactly how it works, why Rust's release mode makes it worse, and how a one-line fix closes the door.

Whether you're new to Rust or a seasoned systems programmer, this vulnerability is a valuable reminder: safe memory management and safe arithmetic are two different things.


The Vulnerability Explained

What Went Wrong

In the file transfer receiver (src/transfer/receiver.rs), the code tracked how many bytes had been received from a sender using a running total:

// The vulnerable code
self.bytes_received += n;

Simple, right? The problem is what happens when bytes_received gets very, very large.

Rust's Debug vs. Release Mode Behavior

Here's the critical detail that makes this dangerous:

  • In debug builds, Rust panics on integer overflow. Your program crashes with a clear error message.
  • In release builds (cargo build --release), integer overflow silently wraps around — just like C and C++.

This is a deliberate performance trade-off documented by the Rust team, but it means production code is vulnerable in ways that testing (usually done in debug mode) will never catch.

When a u64 value exceeds its maximum (18,446,744,073,709,551,615), it wraps back to 0. When a usize on a 64-bit system does the same, the accumulated byte counter suddenly looks tiny — even though gigabytes or terabytes of data may have already been processed.

How an Attacker Exploits This

Here's the attack scenario, step by step:

  1. Attacker initiates a file transfer, advertising a file size just under the system's maximum allowed limit (e.g., MAX_SIZE - 1 bytes).
  2. The receiver accepts the transfer because the advertised size passes validation.
  3. The attacker sends data in chunks, far exceeding the advertised size.
  4. The bytes_received counter accumulates until it approaches u64::MAX.
  5. Overflow occurs — the counter wraps around to a small value (e.g., near 0).
  6. Size limit checks now pass again, because the wrapped value appears to be well within bounds.
  7. The attacker continues sending data indefinitely, exhausting memory, disk space, or CPU — a classic resource exhaustion / denial-of-service attack.
bytes_received progression (u64):
  0 → 1,000,000 → ... → 18,446,744,073,709,551,615 → 0 (OVERFLOW!)
                                                         ^
                                                   Size check passes again!

Real-World Impact

  • Denial of Service (DoS): Unlimited data floods the receiver, consuming memory and disk.
  • Security Bypass: Any downstream logic gated on bytes_received (logging, billing, rate limiting) can be fooled.
  • Data Integrity Issues: Systems that trust the byte counter for integrity checks may produce incorrect results.
  • Amplified Risk in File Import Flows: Combined with the lack of JSON depth limits mentioned in the broader vulnerability description, a crafted import file could simultaneously trigger overflow and deeply nested parsing — compounding the resource exhaustion.

The Fix

What Changed

The fix is elegant and idiomatic Rust — replace the unchecked addition with checked_add, which returns None on overflow instead of wrapping:

Before (vulnerable):

self.bytes_received += n;

After (secure):

self.bytes_received = self.bytes_received
    .checked_add(n)
    .ok_or_else(|| FenvoyError::InvalidMessage("bytes_received overflow".into()))?;

How It Works

Rust's standard library provides checked arithmetic methods on all integer types:

Method Behavior on Overflow
checked_add(n) Returns None
saturating_add(n) Returns MAX value
wrapping_add(n) Wraps around (explicit)
overflowing_add(n) Returns (result, did_overflow)

checked_add returns an Option<T>:
- Some(result) if the addition succeeded without overflow
- None if overflow would have occurred

The .ok_or_else(...) call converts None into a meaningful error, and the ? operator propagates that error up the call stack — terminating the transfer cleanly with an informative error message instead of silently continuing with a corrupted counter.

Why This Fix Is the Right Approach

There are several ways to handle overflow, but checked_add with an error is the best choice here because:

  1. It fails fast — the transfer is immediately terminated when something impossible happens.
  2. It's explicit — future maintainers can see that overflow is a considered case, not an accident.
  3. It's informative — the error message "bytes_received overflow" makes debugging and log analysis straightforward.
  4. It doesn't mask bugs — unlike saturating_add (which would silently cap the value), an error forces the issue to be handled.

Conclusion

This vulnerability is a perfect case study in the gap between "memory safe" and "fully safe." Rust's ownership model prevents an entire class of bugs that plague C and C++ — but integer arithmetic in release mode is still a sharp edge that can cut you if you're not careful.

The key takeaways:

  • 🦀 Rust release builds do NOT panic on integer overflow — silent wraparound is the default.
  • 🔢 Use checked_add (and friends) for any security-sensitive arithmetic — especially byte counters, size tracking, and index calculations.
  • 🚦 Validate continuously, not just at the start — an attacker controls the data stream, not just the initial handshake.
  • 🧪 Test in release mode — your debug tests won't catch overflow bugs in production.
  • 📏 Pair numeric safety with resource limits — overflow protection and size caps are complementary, not alternatives.

A single += replaced with checked_add closed this vulnerability. It's a small change with a big impact — and a great reminder that secure code is built from careful, deliberate choices at every level, even the arithmetic.

Write safe code. Check your math. Ship with confidence.


Found a vulnerability in your own codebase? Consider responsible disclosure and always patch promptly. Security is a team sport.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #4

Related Articles

critical

How buffer overflow happens in C++ and how to fix it

A critical buffer overflow in `create_hex_string()` within `hmlangw.cpp` let an unconditional 16-iteration loop write past the bounds of a 100-byte `hex` buffer using unchecked `sprintf` calls. The fix replaces `sprintf` with `snprintf` and caps the loop iterations based on the actual destination buffer size, closing off a memory corruption path reachable from serial or network input.

high

How remote memory exhaustion happens in Rust QUIC (Quinn) and how to fix it

A high-severity vulnerability (GHSA-4w2j-m93h-cj5j) in `quinn-proto`, the QUIC protocol implementation underlying the Quinn library, allowed remote attackers to exhaust server memory by sending unbounded out-of-order stream data. The `crosshash` project's `Cargo.lock` pinned the vulnerable `quinn-proto` 0.11.14; upgrading to 0.11.15 closes the gap by bounding how much out-of-order stream data the reassembly buffer will retain.

high

How Denial-of-Service via Unbounded Brace Expansion Happens in Node.js and How to Fix It

A critical denial-of-service vulnerability in the `brace-expansion` package allowed attackers to exhaust process memory through unbounded intermediate array expansion. The fix upgrades the package to patched versions (1.1.18, 2.1.4, 3.0.6, 5.0.9) that implement proper expansion length limits, preventing out-of-memory crashes in production applications.

high

How Inherited libvips Vulnerabilities in sharp Impact Image Processing and How to Fix Them

A critical vulnerability (GHSA-f88m-g3jw-g9cj) was discovered where the sharp image processing library inherited four dangerous libvips vulnerabilities that could be exploited through maliciously crafted images. The fix involved upgrading sharp from version 0.34.5 to 0.35.0, which includes hardened input handling and updated libvips bindings to prevent exploitation of these inherited weaknesses.

critical

How NULL pointer dereference from unchecked malloc() happens in C and how to fix it

A critical memory safety vulnerability was discovered in `bench/tokenizer/tokenizer.c` where `malloc()` was called without checking its return value before passing the pointer to `memcpy()`. If allocation fails and `malloc()` returns NULL, the subsequent `memcpy()` writes to address zero, causing heap corruption or potential arbitrary code execution. The fix adds a single NULL check immediately after allocation, exiting cleanly on failure rather than proceeding with a dangerously invalid pointer

critical

How Buffer Overflow via strcpy() Happens in C++ XML Parsers and How to Fix It

A critical buffer overflow vulnerability was discovered in `buildroot-external/package/libxmlparser/xmlParser.cpp`, where the `toXMLString` function used `_tcscpy()` to write XML escape sequences into a destination buffer without any bounds checking. An attacker supplying a crafted XML document could overflow the buffer and potentially execute arbitrary code. The fix replaces all five unsafe `_tcscpy()` calls with `memcpy()` calls that copy only the exact number of bytes required for each escape