What is Cairo ?
In 2021, StarkWare introduced Cairo, a programming language designed for creating provable programs using zero-knowledge proofs. It serves as the smart contract language for Starknet, a layer-2 blockchain built on top of Ethereum.
This article explores some of the most common vulnerabilities in Cairo smart contracts. As Cairo has evolved a lot since it’s first version Cairo 0, this discussion focuses on vulnerabilities affecting smart contracts written in versions posterior to 1.0, with the current Cairo compiler version being 2.8.5 at the time of writing. The article examines some of the main classes of vulnerabilities, detailing how to detect them using tools developed by FuzzingLabs. Additionally, it provides insights into possible remediations.
By understanding these vulnerabilities and leveraging detection tools, developers can build more secure and robust smart contracts on StarkNet. Some of the vulnerabilities presented here are specific to Cairo, and some others are more generic smart contracts vulnerabilities.
An integer overflow occurs when an arithmetic operation produces a value outside the representable range for a given number of bits. This is common in languages using fixed-size integers, such as C or Java, where adding two large integers can exceed the maximum value storable in an int
.
In Cairo, the primary type is called felt
(corresponding to field element), represented by the keyword felt252
. It corresponds to an integer in the range 0 ≤ x < P
, where P is a 252-bit prime number defined as 2^251 + 17 * 2^192
. Overflows or underflows occur if values exceed P or fall below 0, but results are computed modulo P to bring them back within the range.
fn vulnerable_overflow_function(input: felt252) -> felt252 {
// Define the maximum value for a felt252
// The maximum value is calculated as 2^251 + 17 * 2^192
let felt_252_max_value =
// 2^251
0x800000000000000000000000000000000000000000000000000000000000000
+ 17
// 2^192
* 0x1000000000000000000000000000000000000000000000000;
// Adding the input to the maximum value, which can trigger an overflow if the input is > 0
let result = felt_252_max_value + input;
result
}
pub fn main() {
// Adding 1 to the maximum possible value of felt252 to demonstrate overflow
let overflowed_felt252 = vulnerable_overflow_function(1);
// Print the result
println!("{}", overflowed_felt252);
}
We can run the program using cairo-run
./cairo-run ./examples/felt252_overflow.cairo --single-file
0
It successfully overflowed the maximum felt value and printed 0, because the result of (MAX_FELT_VALUE + 1) mod P is equal to 0.
We start by compiling the Cairo program into it’s Sierra intermediate representation:
./cairo-compile ./felt252_overflow.cairo --single-file --replace-ids ./felt_overflow.sierra
Then we run the felt_overflow detector on the Sierra file:
./sierra-decompiler -f felt_overflow.sierra -d --detector-names felt_overflow
[Security] Felt 252 Overflow
- felt252_overflow::felt252_overflow::vulnerable_function: parameters v0 could be used to trigger a felt252 overflow/underflow
To avoid overflow and underflow bugs with the felt type, prefer using integer types (i128, i16, i32, i64, i8, u128, u16, u256, u32, u64, u8, an usize), which include built-in overflow and underflow checks, instead of felts.
Interactions between Ethereum L1 contracts and Cairo contracts on StarkNet L2 can introduce various types of bugs. Here is a non-exhaustive list of examples:
The vulnerability arises from the mismatch in data types between Ethereum L1 and Starknet L2, particularly when transferring addresses or other uint256
values from L1 to L2. In Ethereum, addresses are uint160
, but during cross-layer messaging, they are often represented as uint256
. In Starknet, addresses are of the felt type, which has a smaller range (0 < x < P
, where P is 2^251 + 17 * 2^192
). This mismatch can lead to issues such as valid L1 addresses mapping to null or unexpected addresses on L2, potentially resulting in tokens being sent to unintended addresses.
To mitigate this, it is crucial to validate and verify parameters, especially user-supplied ones, when sending messages from L1 to L2. This ensures that the values fall within the acceptable range of the felt type in StarkNet, or to use the u256
type (or any other integer type similar to the one used in the L1 contract) in Cairo smart contracts when necessary, preventing unintended consequences.
To prevent fund loss, ensure all security checks are identical on both L1 and L2 sides. Asymmetrical validations can cause failed corresponding actions, leading to potential fund loss or service denial.
The following contract stores a secret in the secret storage field, if sensitive information is stored in the smart contract store, this is a vulnerability because the information will be accessible to anyone.
When writing a Cairo smart contract, remember that no data is private on the StarkNet blockchain. If your smart contract needs to store private data on-chain, consider encrypting the data off-chain before deploying it. Alternatively, you can replace plain-text values with hashes to preserve data privacy.
#[starknet::contract]
mod ExposedSecret {
#[storage]
struct Storage {
// This field stores the secret
secret: felt252,
}
#[constructor]
fn constructor(
ref self: ContractState,
secret: felt252
) {
// The secret is written to the storage in plaintext, making it accessible to anyone who can read the contract's storage
self.secret.write(secret);
}
}
Just like in Solidity, reentrancy vulnerabilities are also possible in Cairo smart contracts deployed on StarkNet, with the same mechanisms.
The mitigations for this class of vulnerabilities are the same in Cairo. Just like in Solidity, OpenZeppelin has created a Reentrancy guard that can be used in Cairo smart contracts.
Here is an example of its usage in a function from the Argent wallet smart contract. The Reentrancy guard exposes two methods: start
and end
, which must be called at the beginning and end of the protected function:
fn __execute__(ref self: ContractState, calls: Array) -> Array> {
self.reentrancy_guard.start();
let exec_info = get_execution_info().unbox();
let tx_info = exec_info.tx_info.unbox();
assert_only_protocol(exec_info.caller_address);
assert_correct_invoke_version(tx_info.version);
// execute calls
let retdata = execute_multicall(calls.span());
// emit event
let hash = tx_info.transaction_hash;
let response = retdata.span();
self.emit(TransactionExecuted { hash, response });
self.reentrancy_guard.end();
retdata
}
It is also important to follow the Check-Effect-Interaction pattern, which is is a common design pattern used to prevent reentrancy attacks on Ethereum that can be copied in Cairo contracts.
The pattern involves following a specific order of operations in your functions:
Here is a snippet of code from the Carbonable Minter that follows this pattern :
fn claim(ref self: ContractState, user_address: ContractAddress, id: u32) {
assert(self.sold_out(), 'Contract not sold out');
// [Check] Booking ok;
let mut booking = self._booked_values.read((user_address.into(), id));
assert(
booking.status == mint_status_to_u8(BookingStatus::Booked(())), 'Booking not found'
);
let projects_contract = self._projects_contract.read();
let slot = self._slot.read();
// [Effect] Update Booking status
booking.status = mint_status_to_u8(BookingStatus::Minted(()));
self._booked_values.write((user_address.into(), id), booking);
// [Interaction] Mint
let token_id = self
._projects_contract
.read()
.mintNew(user_address.into(), slot, booking.value);
self
.emit(
Event::BookingClaimed(
BookingClaimed { address: user_address, value: booking.value, }
)
);
}
In conclusion, this article has provided an in-depth look at some of the most common vulnerabilities that can occur when writing Cairo smart contracts. While it is imperative that smart contracts handling critical operations undergo thorough auditing, it is also important to note that many vulnerabilities can be automatically detected using various security tools. Tools such as the cairo-fuzzer can be employed for fuzzing, while static analysis tools like thoth and sierra-analyzer offer additional layers of security. By leveraging these tools, developers can significantly enhance the security and reliability of their Cairo smart contracts, ensuring a more robust and secure blockchain ecosystem.
Antonin Fagat / @rgrsmth
Patrick Ventuzelo / @Pat_Ventuzelo
Founded in 2021 and headquartered in Paris, FuzzingLabs is a cybersecurity startup specializing in vulnerability research, fuzzing, and blockchain security. We combine cutting-edge research with hands-on expertise to secure some of the most critical components in the blockchain ecosystem.
Contact us for an audit or long term partnership!
Cookie | Duration | Description |
---|---|---|
cookielawinfo-checkbox-analytics | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". |
cookielawinfo-checkbox-functional | 11 months | The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". |
cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
cookielawinfo-checkbox-others | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. |
cookielawinfo-checkbox-performance | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". |
viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |