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
•Technically reviewed by Anupam Mediratta•Published May 18, 2026•Reviewed 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

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

Related Articles

high

xcb_get_image_reply() NULL Deref on malloc() Failure

The XCB image-reply handler in the screen-streaming bridge allocated a buffer with `malloc()` and immediately wrote to it with `memset()` and `memcpy()` without checking for allocation failure. Under memory pressure, this produces a null-pointer dereference that crashes the process, turning a resource-exhaustion condition into an immediate denial of service. The fix adds a NULL check that frees the intermediate reply and returns early instead of writing to invalid memory.

high

CVE-2026-73089: browserslist 4.28.1 DoS via Query Result Caching

CVE-2026-73089 is a high-severity denial-of-service vulnerability in browserslist versions before 4.28.7. The issue stems from unbounded memory growth when caching results from distinct browser queries, eventually causing out-of-memory crashes in build processes and development servers.

high

XShmGetImage Heap Corruption: Unvalidated Image Height in streamproxy

The `XShmGetImage` function in the X11 shared memory image path copies pixel data row-by-row using `memcpy` without validating that the source image height matches the destination buffer height. An attacker or compromised server could provide an oversized source image, causing writes beyond the allocated heap buffer and triggering heap corruption or code execution.

critical

SGX Enclave ecall_store_data memcpy Buffer Overflow in 256-Byte

The Intel SGX enclave's trusted bridge functions `ecall_store_data` and `ecall_retrieve_data` used `memcpy()` to move data into and out of a fixed 256-byte `secure_storage` buffer without validating that `data_len` fit within destination boundaries. An attacker providing oversized `data_len` values could corrupt enclave memory, breaking SGX's confidentiality guarantees.

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