Jan 28, 2026·6 min read·26 visits
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.
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.
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.
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.
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 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:
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.
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:
cargo update -p soroban-sdk. The patched versions (22.0.9, 23.5.1, 25.0.2) contain the checked_add/sub logic that forces a panic.[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.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N| 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 |
Integer Overflow or Wraparound
netfoil, an allowlist-based DNS proxy, failed to sanitize ALPN fields parsed from untrusted DNS-over-HTTPS (DoH) HTTPS Resource Records. This allowed attackers to inject ANSI escape sequences into log files or trigger Denial of Service (DoS) via uncontrolled memory allocations.
An issue was discovered in the tokio-postgres library for Rust prior to version 0.7.18. A trust assumption mismatch between the PostgreSQL protocol messages sent by a server and how they are parsed and indexed by the client-side library allows a rogue or compromised database server to trigger a Denial of Service (DoS) crash via an unhandled out-of-bounds slice indexing panic.
CVE-2026-14669 is a critical heap-based buffer overflow vulnerability in PostgreSQL's date/time formatting function to_char(timestamptz). The flaw arises from unsafe copying of user-controlled timezone abbreviations into a fixed-size internal buffer. An authenticated database user can trigger this issue by setting a long POSIX timezone abbreviation containing custom formatting, allowing them to overwrite adjacent heap structures and hijack execution control to achieve remote code execution (RCE) with the privileges of the 'postgres' operating system user.
An unauthenticated remote denial of service vulnerability exists in the Unleash feature management platform. By submitting a crafted JSON payload containing deeply nested structures to an OpenAPI-validated endpoint, an attacker can trigger uncontrolled recursion within the error formatting module. This leads to a call-stack exhaustion (RangeError: Maximum call stack size exceeded) inside the Node.js runtime, causing the service to crash immediately without recovery.
CVE-2026-63004 is a server-side request forgery (SSRF) vulnerability in the Unleash feature management platform. Authenticated administrators with CREATE_ADDON or UPDATE_ADDON privileges can exploit this vulnerability to initiate requests to loopback addresses, private networks, and cloud metadata endpoints, potentially leading to information disclosure and credential extraction.
Prior to version 8.0.3, Unleash's Markdown event formatter directly mutated the global template-escaping function of the shared mustache Node.js module, resulting in a process-wide security degradation where HTML/Markdown escaping was permanently disabled for the application lifetime.