xanoutas/solidity-security-findings huggingface dataset:
Address aliasing is wrongfully applied even to EOAs
Audit: 2024-09-kakarot-findings
Severity: MEDIUM

# Lines of code

https://github.com/kkrt-labs/kakarot/blob/b8f5f2a20bd733cc8885c010291885e1df7dc50e/solidity_contracts/src/L1L2Messaging/L1KakarotMessaging.sol#L8


# Vulnerability details


## Proof of Concept

Kakarot inherits the address aliasing logic from Optimism as hinted in [`AddressAliasHelper.sol`](https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/solidity_contracts/src/L1L2Messaging/AddressAliasHelper.sol):

```solidity
// from https://github.com/ethereum-optimism/optimism/blob/a080bd23666513269ff241f1b7bc3bce74b6ad15/packages/contracts-bedrock/src/vendor/AddressAliasHelper.sol

pragma solidity ^0.8.0;

library AddressAliasHelper {
    ..snip
}
```

Now this is a key property of the Optimism bridge, which is that **all contract addresses** are aliased. And this is done inorder to avoid a contract on L1 to be able to send messages as the same address on L2, because often these contracts will have different owners.

To go into more details about why & how this is used, we can see this: https://docs.optimism.io/chain/differences#address-aliasing:

> Address aliasing is an important security feature that impacts the behavior of transactions sent from L1 to L2 by smart contracts. Make sure to read this section carefully if you are working with cross-chain transactions. Note that the CrossChainMessenger contracts will handle address aliasing internally on your behalf.
> When transactions are sent from L1 to L2 by an Externally Owned Account, the address of the sender of the transaction on L2 will be set to the address of the sender of the transaction on L1. However, the address of the sender of a transaction on L2 will be different if the transaction was triggered by a smart contract on L1.
> Because of the behavior of the CREATE opcode, it is possible to create a contract on both L1 and on L2 that share the same address but have different bytecode. Even though these contracts share the same address, they are fundamentally two different smart contracts and cannot be treated as the same contract. As a result, the sender of a transaction sent from L1 to L2 by a smart contract cannot be the address of the smart contract on L1 or the smart contract on L1 could act as if it were the smart contract on L2 (because the two contracts share the same address).
> To prevent this sort of impersonation, the sender of a transaction is slightly modified when a transaction is sent from L1 to L2 by a smart contract. Instead of appearing to be sent from the actual L1 contract address, the L2 transaction appears to be sent from an "aliased" version of the L1 contract address. This aliased address is a constant offset from the actual L1 contract address such that the aliased address will never conflict with any other address on L2 and the original L1 address can easily be recovered from the aliased address.
> This change in sender address is only applied to L2 transactions sent by L1 smart contracts. In all other cases, the trans
`pow()` doesn't correctly handle `0^0`
Audit: 2024-09-kakarot-findings
Severity: MEDIUM

# Lines of code

https://github.com/kkrt-labs/kakarot/blob/b8f5f2a20bd733cc8885c010291885e1df7dc50e/cairo/kakarot-ssj/crates/utils/src/math.cairo#L26-L62


# Vulnerability details


## Proof of Concept

Take a look at the `pow` function implementation in the `ExponentiationImpl` trait https://github.com/kkrt-labs/kakarot/blob/b8f5f2a20bd733cc8885c010291885e1df7dc50e/cairo/kakarot-ssj/crates/utils/src/math.cairo#L26-L62:

```rust
impl ExponentiationImpl<
    T,
    +Zero<T>,
    +One<T>,
    +Add<T>,
    +Sub<T>,
    +Mul<T>,
    +Div<T>,
    +BitAnd<T>,
    +PartialEq<T>,
    +Copy<T>,
    +Drop<T>
> of Exponentiation<T> {
    fn pow(self: T, mut exponent: T) -> T {
        let zero = Zero::zero();
        if self.is_zero() {
            return zero;
        }
        let one = One::one();
        let mut result = one;
        let mut base = self;
        let two = one + one;

        loop {
            if exponent & one == one {
                result = result * base;
            }

            exponent = exponent / two;
            if exponent == zero {
                break result;
            }

            base = base * base;
        }
    }
}
```

The function immediately returns zero if the base is zero, without considering the exponent. This leads to an incorrect result for the case of 0^0.

To demonstrate:

If `result = 0_u32.pow(0);`, result would be `1`.

### Coded POC

```rust
    #[test]
    fn test_pow() {
        assert(5_u256.pow(10) == 9765625, '5^10 should be 9765625');
        assert(5_u256.pow(45) == 28421709430404007434844970703125, '5^45 failed');
        assert(123456_u256.pow(0) == 1, 'n^0 should be 1');
        assert(0_u256.pow(123456) == 0, '0^n should be 0');
        assert(0_u256.pow(0) == 0, '.');//@audit we should have 1 here though
    }
```

## Impact

This implementation leads to unexpected behavior and breaks mathematical calculations that rely on this exponentiation function. The case of `0^0` is mathematically undefined, and different systems handle it differently. In many programming languages and mathematical libraries, 0^0 is defined to be 1, which aligns [with certain mathematical conventions](https://cs.uwaterloo.ca/~alopez-o/math-faq/mathtext/node14.html).

The current implementation returning 0 for 0^0 easily breaks algorithms that expect 0^0 to be 1, potentially leading to incorrect results or unexpected program behavior. This is especially problematic in financial or scientific calculations where precision is crucial.

## Recommended Mitigation Steps

Explicitly handle the case of 0^0:

```rust
fn pow(self: T, exponent: T) -> T {
    let zero = Zero::zero();
    let one = One::one();

    if self.is_zero() {
        if exponent.is_zero() {
            return one; // Define 0^0 as 1
        }
        return zero;
    }

    // ... snip
}
```

Alternatively, if you want to emphasise the undefined nature of 0^0, you could return a Result type:

```rust
fn pow(self: T, exponent: T) -> Result<T, PowerError
Potential Reentrancy Vulnerability in execute_starknet_call() and eth_send_transaction()
Audit: 2024-09-kakarot-findings
Severity: MEDIUM

# Lines of code

https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/interfaces/interfaces.cairo#L75-L78
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/interfaces/interfaces.cairo#L169-L179


# Vulnerability details

Vulnerability Details:
The execute_starknet_call() and eth_send_transaction() functions involve external calls, which could introduce reentrancy risks. If the to parameter in these functions references a contract that re-enters the calling function, it could exploit reentrancy vulnerabilities by manipulating the contract’s state or draining funds.
Code References:
execute_starknet_call()
eth_send_transaction()
The potential reentrancy risk arises from the fact that both functions interact with external contracts without reentrancy protection mechanisms in place, leaving them vulnerable to recursive calls that could occur before state changes are finalized.

Proof of Concept:
Reentrancy in eth_send_transaction():
The eth_send_transaction() function sends a transaction to an external contract via the to parameter.
If the target contract calls back into eth_send_transaction() (or any vulnerable state-modifying function), it could cause state to be manipulated before it’s properly updated.

Reentrancy in execute_starknet_call():
Similarly, the execute_starknet_call() function could allow for reentrancy if the external call to to references a contract that recursively calls back into execute_starknet_call() or other state-dependent functions.

Attack Scenario:
An attacker creates a malicious contract that re-enters the vulnerable functions before critical state changes occur (such as transferring funds or updating balances).
By leveraging this reentrancy, the attacker can repeatedly withdraw or manipulate the contract state, leading to loss of funds or other unintended behavior.

Impact:
Fund Draining: Reentrancy could allow an attacker to drain funds from the contract by repeatedly re-entering the function before state changes are finalized.
State Manipulation: Critical contract state variables could be modified in unintended ways by recursive reentrant calls, causing logic flaws or incorrect contract behavior.

Recommended Mitigation Steps:
Implement Reentrancy Guards:
Use a reentrancy guard mechanism (e.g., a mutex or flag) to prevent recursive function calls during sensitive operations. For example:
if reentrancy_guard != 0:
    revert('Reentrancy detected');
reentrancy_guard = 1;

// Execute sensitive logic

reentrancy_guard = 0;

Avoid External Calls After State Changes:
Ensure that any external calls to untrusted contracts are made after critical state changes are finalized. This limits the impact of reentrant calls by ensuring the contract state is already in a consistent state.

Thorough Testing:
Test the functions in various scenarios, including recursive calls and reentrancy attacks, to ensure the proper functioning of the reentrancy guar
execute_starknet_call can be used for reentrant attacks
Audit: 2024-09-kakarot-findings
Severity: MEDIUM

# Lines of code

https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/accounts/account_contract.cairo#L344


# Vulnerability details

## Impact
Reentrant attack

## Proof of Concept

`execute_starknet_call` avoids reentrant attacks by determining whether `to_address` is `kakarot_address` (core contract) :

```rust
@external
func execute_starknet_call{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
    to: felt, function_selector: felt, calldata_len: felt, calldata: felt*
) -> (retdata_len: felt, retdata: felt*, success: felt) {
    Ownable.assert_only_owner();
    let (kakarot_address) = Ownable.owner();
    let is_get_starknet_address = Helpers.is_zero(
        GET_STARKNET_ADDRESS_SELECTOR - function_selector
    );
    // Helpers.is_zero ->  if(input==0) return 1 else return 0 
    let is_kakarot = Helpers.is_zero(kakarot_address - to); //to == owner -> is_kakarot = 1

    // function_selector == GET_STARKNET_ADDRESS_SELECTOR -> is_forbidden = 0
    // to = owner (is_kakarot =1) && function_selector != GET_STARKNET_ADDRESS_SELECTOR -> is_forbidden = 1
    tempvar is_forbidden = is_kakarot * (1 - is_get_starknet_address); 
    if (is_forbidden != FALSE) {
@>      let (error_len, error) = Errors.kakarotReentrancy();
        return (error_len, error, FALSE);
    }
    let (retdata_len, retdata) = call_contract(to, function_selector, calldata_len, calldata);
    return (retdata_len, retdata, TRUE);
}
```

However, this still does not prevent reentrant attacks if the attacker specifies `to_address` as the Account contract and calls the `execute_from_outside` function in the account contract to execute a transaction again (reentrant).

1. Since `to_address` is not equal to `kakarot_address`, it can pass kakarotReentrancy's check.
2. The nonce and storage space of the account are changed after the evm order ends(Starknet.commit). If a new transaction is initiated during the execution of the evm order, the old nonce and data store are used in the new transaction.

This leads to the problem of reentrant attacks that are common in evm, and the creation of accounts can also be a problem of duplicate creation.

Attack Scenario:

1. User A initiates transaction that invokes a function in attacker B's contract. The attacker obtains user A's transaction signature in advance and uses this signature to repeat the transaction through `execute_from_outside`.

2. When an attacker calls a third-party contract, he signs it with his private key in the fallback function (or some other type of callback), and then initiates another transaction with `execute_from_outside`, causing a common reentrant attack problem.


## Tools Used
vscode

## Recommended Mitigation Steps
```diff
func execute_starknet_call{
+    if function_selector == execute_from_outside{
+        let (error_len, error) = Errors.kakarotReentrancy();
+            return (error_len, error, FALSE);
+    }
}
```



## Assessed type

Reentrancy
No way to cancel l1 -< l2 messages 
Audit: 2024-09-kakarot-findings
Severity: MEDIUM

# Lines of code

https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/solidity_contracts/src/L1L2Messaging/L1KakarotMessaging.sol#L26-L61


# Vulnerability details

## Description
There is no api to allow cancellation of l1->l2 messages. In the event of an issue in the kakarot contracts, this will result in the fee being permanently lost since the user has no ability to reclaim the funds.
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/solidity_contracts/src/L1L2Messaging/L1KakarotMessaging.sol#L26-L61
As we can see there are only functions to either send the message from l1 -> l2 or consume an l2 message. There is no `cancelL1toL2Message` present.
## Recommended Mitigation Steps
https://docs.starknet.io/architecture-and-concepts/network-architecture/messaging-mechanism/#l2-l1_message_cancellation
Introduce the following API to let users cancel their messages after waiting the time limit so that they can reclaim funds. 


## Assessed type

Context
Cross-chain `sendMessageToL2` using bridge might fail due to not checking the msg.value passed by the users.
Audit: 2024-09-kakarot-findings
Severity: MEDIUM

# Lines of code

https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/solidity_contracts/src/L1L2Messaging/L1KakarotMessaging.sol#L32


# Vulnerability details

## Title
Cross-chain `sendMessageToL2` using bridge might fail due to not checking the `msg.value` passed by the users.

## Vulnerability Details
EVM contracts send messages from L1 to L2 using a bridge. However, it is not guaranteed that the message will be processed by the sequencer. For instance, a message can fail to be processed if there is a sudden spike in the gas price and the value provided is too low.

If we look at [L1L2Messaging::sendMessageToL2](https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/solidity_contracts/src/L1L2Messaging/L1KakarotMessaging.sol#L32C14-L32C29) the function doesn't verify whether user has provided enough gas to cover bridge fees to complete the transaction.

```solidity
    function sendMessageToL2(address to, uint248 value, bytes calldata data) external payable {
        uint256 totalLength = data.length + 4;
        uint256[] memory convertedData = new uint256[](totalLength);
        convertedData[0] = uint256(uint160(AddressAliasHelper.applyL1ToL2Alias(msg.sender)));
        convertedData[1] = uint256(uint160(to));
        convertedData[2] = uint256(value);
        convertedData[3] = data.length;
        for (uint256 i = 4; i < totalLength; ++i) {
            convertedData[i] = uint256(uint8(data[i - 4]));
        }

        // Send the converted data to L2
@>      starknetMessaging.sendMessageToL2{value: msg.value}(kakarotAddress, HANDLE_L1_MESSAGE_SELECTOR, convertedData);
    }
```
If an L1 message is never processed by the sequencer, users will never receive their deposits in either L1 or L2, and they need a way to cancel the message.

## Impact
If the `msg.value` is not properly checked and estimated, users may lose their funds due to unprocessed `sendMessageToL2()`. This can lead to significant financial losses and a lack of trust in the system. Additionally, the inability to retrieve or cancel the message can result in funds being locked indefinitely, causing further inconvenience and potential financial damage to users.

Reference: https://github.com/crytic/building-secure-contracts/tree/master/not-so-smart-contracts/cairo/l1_to_l2_message_failure
## Tools Used
Manual Review

## Recommendation
When sending a message from L1 to L2, it is essential to consider the possibility that a message may never be processed by the sequencer. This can block either the contract from reaching a certain state or users from retrieving their funds. If needed, allow the use of `startL1ToL2MessageCancellation()` and `cancelL1ToL2Message()` to cancel ongoing messages.


## Assessed type

Invalid Validation
Execution gas price is fixed.
Audit: 2024-09-kakarot-findings
Severity: MEDIUM

# Lines of code

https://github.com/kkrt-labs/kakarot/blob/b8f5f2a20bd733cc8885c010291885e1df7dc50e/cairo_zero/kakarot/library.cairo#L436


# Vulnerability details

## Title
Execution gas price is fixed.
## Links to affected code
https://github.com/kkrt-labs/kakarot/blob/b8f5f2a20bd733cc8885c010291885e1df7dc50e/cairo_zero/kakarot/library.cairo#L436
## Impact 
The geth implementation has `TxData .gas()` field to put different gas values for a transaction here:
https://github.com/ethereum/go-ethereum/blob/24c5493becc5f39df501d2b02989801471abdafa/core/types/transaction.go#L81 

But the kakarot implementation is providing the fixed amount of gas which is 2100000000 in the function `library::handle_l1_message()`. By providing a fix amount of gas the execution will fail if the sudden spike in the gas price and the value provided is too low or if the calldata is larger then user can't set the gas price for execution.

There is no way to set the gas value for transaction execution.
## Proof of Concept
If we look at the function [library::handle_l1_message()](https://github.com/kkrt-labs/kakarot/blob/b8f5f2a20bd733cc8885c010291885e1df7dc50e/cairo_zero/kakarot/library.cairo#L436) we can see it passes 2100000000 wei for the `eth_call()` function as execution gas cost.
```rust
    func handle_l1_message{
        syscall_ptr: felt*,
        pedersen_ptr: HashBuiltin*,
        range_check_ptr,
        bitwise_ptr: BitwiseBuiltin*,
    }(l1_sender: felt, to_address: felt, value: felt, data_len: felt, data: felt*) -> (
        model.EVM*, model.State*, felt, felt
    ) {
        // TODO: ensure fair gas limits and prices
        let (val_high, val_low) = split_felt(value);

        tempvar value_u256 = new Uint256(low=val_low, high=val_high);
        
        let to = model.Option(is_some=1, value=to_address);
        let (access_list) = alloc();

        return eth_call(
@>           0, l1_sender, to, 2100000000, 1, value_u256, data_len, data, 0, access_list
        )
    }
```

## Tools Used 
Manual Review
## Recommended Mitigation Steps
We recommend the gas should be set by users who execute the function.



## Assessed type

Other
Infinite mint by invoking `handle_l1_message` with an account with insufficient balance
Audit: 2024-09-kakarot-findings
Severity: MEDIUM

# Lines of code

https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/library.cairo#L421-L438
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/kakarot.cairo#L370-L385
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/library.cairo#L78-L120
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/interpreter.cairo#L941-L952
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/solidity_contracts/src/L1L2Messaging/L1KakarotMessaging.sol#L26-L45


# Vulnerability details

## Description
When invoking Kakarot from L1, we first call the following function: https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/solidity_contracts/src/L1L2Messaging/L1KakarotMessaging.sol#L26-L45
notice that the value is not checked as the comment indicates it will be taken from the L2 address. Then the following `l1_handler` function is called: 
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/kakarot.cairo#L370-L385
This will then call `handle_l1_message`: https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/library.cairo#L421-L438 which has no checks on the gas limit sent, as it is hardcoded to 2100000000.
Then eth_call is invoked: https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/library.cairo#L78-L120
Which eventually hits this line in the interpreter: https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/interpreter.cairo#L941-L952
However since in this flow we never called `eth_send_raw_unsigned_tx`, the following fee deduction from the origin account will underflow if the origin account has less than 2100000000 wei. This creates a balance overflow and essentially mints ether to the sender. 
## Recommended Mitigation Steps
Perform the check that the user's balance is greater than the gas limit inside the `handle_l1_message` function. 


## Assessed type

Under/Overflow
L1 -> L2 messages do not have a balance check leading to balance overflow
Audit: 2024-09-kakarot-findings
Severity: MEDIUM

# Lines of code

https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/kakarot.cairo#L380
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/library.cairo#L435


# Vulnerability details

## Description
There are two possible ways of sending a transaction in Kakarot EVM. One way is calling the `eth_send_raw_unsigned_tx`:
```
@external
func eth_send_raw_unsigned_tx{
    syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}(tx_data_len: felt, tx_data: felt*) -> (
    return_data_len: felt, return_data: felt*, success: felt, gas_used: felt
) {
...
...
}
```

This function takes the raw transaction data and runs various checks on the transaction and the caller. One of the checks is making sure the sender has enough [balance to run the transaction](https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/eth_rpc.cairo#L300):
```
    let (is_balance_enough) = uint256_le(tx_cost, balance);
    with_attr error_message("Not enough ETH to pay msg.value + max gas fees") {
        assert is_balance_enough = TRUE;
    }
```

Another way to send a transaction in Kakarot is via L1 -> L2 messages. These messages are consumed by [the handle_l1_message function](https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/kakarot.cairo#L370):
```
@l1_handler
func handle_l1_message{
    syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}(from_address: felt, l1_sender: felt, to_address: felt, value: felt, data_len: felt, data: felt*) {
```

The problem is the above function doesn't actually run any checks on the sent messages. It just calls the `handle_l1_message` function [in the library.cairo](https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/library.cairo#L435) which then calls the `eth_call` [function](https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/library.cairo#L78).

After `eth_call` is called the transaction is basically executed via `execute` function in [interpreter.cairo](https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/interpreter.cairo#L820).

In L1 -> L2 messages there are no validations done on the transaction or the sender's balance. This leads to underflow in the `execute` function of the `interpreter.cairo` [when the fee is substracted from the sender's balance](https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/interpreter.cairo#L950):
```
        let max_fee = gas_limit * env.gas_price;
        let (fee_high, fee_low) = split_felt(max_fee);
        let max_fee_u256 = Uint256(low=fee_low, high=fee_high);

        with state {
            let sender = State.get_account(env.origin);
            let (local new_balance) = uint256_sub([