When Math Lies: Integer Wraparounds in Stellar's Soroban SDK
Jan 28, 2026·6 min read·5 visits
Executive Summary (TL;DR)
The Soroban SDK used bare arithmetic to calculate slice indices. In Rust release builds (default for many), `u32::MAX + 1` wraps to `0`. This turns a request for 'all data' into a request for 'no data' silently. It affects data slicing and random number generation.
A critical integer overflow vulnerability in the Rust `soroban-sdk` allows smart contracts to silently process incorrect data ranges or generate wild random numbers. By exploiting standard arithmetic wrapping behaviors in Rust's release mode, attackers can force contracts to slice empty buffers instead of full datasets, or bypass PRNG bounds, leading to severe logic corruption without triggering a transaction revert.
The Hook: Rust, The Safe Language (Usually)
We love Rust. We love it for the borrow checker, the type safety, and the smug sense of superiority we get when telling C++ developers that their memory leaks are "so 1990s." But Rust has a dirty little secret that bites hard when you leave the cozy embrace of debug builds: integer overflow.
In the world of blockchain, specifically Stellar's Soroban smart contract platform, the rs-soroban-sdk is the bridge between your contract logic and the host environment. It handles the nitty-gritty of memory, types, and host calls. But in CVE-2026-24889, that bridge turned out to be made of balsa wood painted to look like steel.
The vulnerability isn't some complex heap-spraying technique or a ROP chain. It's 3rd-grade math. The SDK was performing manual arithmetic on range bounds—calculating start and end indices for slices—using standard operators (+ and -). In the high-stakes, optimized world of Wasm smart contracts, this arithmetic didn't panic when it hit the integer limit. It rolled over like an odometer on a '95 Honda Civic, turning MAX into 0. And in a smart contract, 0 is rarely the number you want to see when you asked for EVERYTHING.
The Flaw: Release Mode Logic
To understand this bug, you have to understand how Rust compiles. In dev (debug) profiles, if you try to add 1 to u32::MAX, the program panics. It screams, stops execution, and saves you from yourself. This is great. In Soroban terms, a panic aborts the transaction, reverting all state changes. No harm, no foul.
However, smart contracts need to be small and fast. They are compiled in release mode. By default, unless you explicitly set overflow-checks = true in your Cargo.toml, Rust disables overflow checks for performance. u32::MAX + 1 becomes 0. No panic. No abort. The code just keeps executing with the wrong number.
The SDK implements Bytes::slice and Vec::slice. These functions take a RangeBounds argument (like 0..=len). To communicate with the Soroban host environment (which expects exclusive end indices), the SDK has to normalize these bounds. If you pass an inclusive range like 0..=u32::MAX, the SDK calculates the end index as end + 1.
Do you see the train wreck coming? 4,294,967,295 + 1 wraps to 0. The SDK then tells the host: "Give me a slice starting at 0 and ending at 0." The host dutifully returns an empty byte array. The contract thinks it processed the data, but it processed nothing.
The Code: The Smoking Gun
Let's look at the actual code responsible for this silent failure. The vulnerability existed in the slice method's bound calculation logic. It looked something like this:
// Vulnerable logic in SDK < 22.0.9
let end_bound = match range.end_bound() {
Bound::Included(s) => *s + 1, // <--- HERE IS THE BUG
Bound::Excluded(s) => *s,
Bound::Unbounded => len,
};It looks innocent. It's logically correct in a world with infinite integers. But we live in a 32-bit (or 64-bit) world. When s is u32::MAX, *s + 1 wraps.
The fix provided by the Stellar team is the textbook definition of "defensive programming." They replaced the bare arithmetic with checked operations and—crucially—an explicit trap.
// The Fix
Bound::Included(s) => s.checked_add(1)
.expect_optimized("attempt to add with overflow"),Note the use of .expect_optimized(). In the Soroban SDK, this isn't just a standard unwrap. It maps to wasm32::unreachable(), which is a specialized instruction that traps the Wasm execution immediately. This ensures that even if the developer still has overflow checks disabled in their project config, the SDK will force a crash (transaction abort) rather than allowing the wraparound.
The Exploit: Vanishing Data and Rogue RNG
How do we weaponize this? There are two primary vectors: Data Slicing and Random Number Generation (PRNG).
Vector 1: The Empty Slice Trick
Imagine a governance contract that iterates through a list of votes stored in a Bytes object to count "Yes" votes. The function takes a range to allow batch processing.
fn count_votes(votes: Bytes, range: RangeInclusive<u32>) -> u32 {
let batch = votes.slice(range); // Vulnerable slice call
// ... iterates over batch ...
}An attacker calls this with 0..=u32::MAX. The SDK wraps the length calculation to 0. The batch variable becomes an empty slice. The loop runs zero times. The function returns 0 "Yes" votes. If this logic was used to validate a quorum, you might have just bricked the proposal system or bypassed a check that requires "at least X votes checked."
Vector 2: The Chaos PRNG
The Prng::gen_range function had the opposite problem. To handle Bound::Excluded(end), it calculated end - 1.
If you call prng.gen_range(0..0), the code sees an excluded bound of 0. It calculates 0 - 1. In unsigned arithmetic, this wraps backwards to u64::MAX. Suddenly, a call that was supposed to generate a number in a tight range (or fail) is generating numbers across the entire 64-bit integer space. This is catastrophic for gaming contracts or lotteries relying on specific probability distributions.
The Impact: Silent Corruption
The terrifying part of this vulnerability is the silence. In the blockchain world, we rely on the principle that "if it compiles and runs without error, the state transition is valid."
CVE-2026-24889 breaks that contract. It allows a transaction to complete successfully (SUCCESS status) while performing operations that are logically impossible (like slicing a 4GB array into 0 bytes when you asked for the whole thing). This leads to:
- Logic Bypasses: Security checks that iterate over data ranges can be skipped entirely.
- State Corruption: Contracts might write "0" to storage when they meant to write the length of the buffer.
- Predictable RNG: Or rather, unpredictable RNG where strict bounds are required for fairness.
This affects any contract compiled with default release profiles using the affected SDK versions. It is a fundamental flaw in the foundational library of the ecosystem.
The Fix: Update or Trap
If you are a Soroban developer, stop what you are doing. Check your Cargo.toml. If you are using rs-soroban-sdk versions < 22.0.9, 23.0.x < 23.5.1, or 25.0.x < 25.0.2, you are sitting on a time bomb.
Remediation Steps:
- Update the SDK:
cargo update -p soroban-sdk. The patched versions (22.0.9,23.5.1,25.0.2) contain thechecked_add/sublogic that forces a panic. - Enable Overflow Checks: Don't rely solely on the SDK to save you. Enable global overflow checks in your release profile. This adds a tiny bit of gas cost overhead but saves you from an entire class of bugs.
[profile.release]
overflow-checks = trueThis is like wearing a seatbelt and having airbags. The SDK patch is the airbag; the config change is the seatbelt. Use both.
Official Patches
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
rs-soroban-sdk Stellar | < 22.0.9 | 22.0.9 |
rs-soroban-sdk Stellar | >= 23.0.0, < 23.5.1 | 23.5.1 |
rs-soroban-sdk Stellar | >= 25.0.0, < 25.0.2 | 25.0.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-190 |
| Attack Vector | Network |
| CVSS | 5.3 (Medium) |
| Impact | Integrity Loss / Logic Bypass |
| Exploit Status | PoC Available (Implicit) |
| Language | Rust / Wasm |
MITRE ATT&CK Mapping
Integer Overflow or Wraparound
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.