Integer overflow/underflows are surprisingly common in smart contracts, because blockchain applications often compute math over financial data.
Rust is a popular language used in blockchains such as Solana and Polkadot. For many developers, it may be a misconception that Rust is memory-safe so it is free of arithmetic overflow/underflows. This article explains why Rust programs still suffer from arithmetic errors, how these issues affect blockchain security, and how to deal with them in smart contracts.
Just like in a house where every furniture occupies a space, in computer programs, every piece of data needs a space to store its value. The space is limited. If the value of a certain data (e.g. an account’s balance) after computation exceeds the size of its space, then an overflow/underflow occurs.
In Rust, an unsigned integer can have one of the following types: u8, u16, u32, u64, u128 and usize. The type denotes the number of bits used to store the integer: u8 can hold values between 0 and 255, u16 can hold values between 0 and 65535, and so forth. For example, x = x + 1 — if an u8 integer x is changed to a value outside of its range, say 256 or -1, then an overflow/underflow will occur.
A bit surprising — Rust behaves differently in debug mode and release mode on Integer overflow/underflows. In debug mode, Rust adds built-in checks for overflow/underflow and panics when an overflow/underflow occurs at runtime.
However, in release (or optimization) mode, Rust silently ignores this behavior by default and computes two’s complement wrapping (e.g., 255+1 becomes 0 for an u8 integer). You can play and see the difference using this link.
Note: The overflow-checks in Rust can be enabled in release mode https://doc.rust-lang.org/cargo/reference/profiles.html#overflow-checks
In other words, an overflow in Rust that appears as panic when debugging may disappear when deployed in production.
While there are good reasons (e.g. performance) for Rust to ignore overflows in release mode, this inconsistent behavior may lead to an illusion of Rust safety on arithmetic operations. This is particularly dangerous for blockchains and smart contracts, which live in adversarial environments.
Both Solana smart contracts and Solana’s core runtime (the validator code) written in Rust have seen quite a number of arithmetic errors.
A partial list from Github (click links to pull requests):
The above shows an integer underflow on total_loan_notes -= note_amount(line 245) and overflow on total_deposit += token_amount (line 246) in the jet-v1 protocol. Both variables are of type u64 . The fixes are straightforward: replace the - with checked_sub and the + with checked_add . These will return None instead of wrapping around on underflow or overflow.
The above shows an integer overflow in the Solana bpf loader. Both the multiplication num_accounts * size_of::<AccountMeta>() and the addition + data_len can cause overflows. The fixes are to replace the * with saturating_mul and the + with saturating_add. These will saturate values at the numeric bounds instead of overflowing.
The above shows a slightly different type of arithmetic errors in Solana. The division / between two integers total_current_stake * 100 and total_stake can loss precision due to truncate of fractions. The fix is to change the type of these variables to floating point type f64 . In this way, the result current_stake_percent has also precision of type f64.
When writing a Solana smart contract in Rust, there are three common ways to deal with arithmetic errors:
Besides, it’s a no-brainer to have a tool that comprehensively checks for arithmetic errors in all code paths, just like checking spelling mistakes.
X-Ray is an advanced tool with built-in support to detect common security pitfalls in Solana smart contracts, including arithmetic overflow/underflows.
For all blogs by sec3, Please visit https://www.sec3.dev/blog