Skip to content

Unauthorized Contracts Can Bypass Precompile Authorization via delegatecall in Kakarot zkEVM #124

Open
@howlbot-integration

Description

@howlbot-integration

Lines of code

https://github.com/kkrt-labs/kakarot/blob/6496ee1d18380dae5b069c27b3129d82ae282419/cairo_zero/kakarot/precompiles/precompiles.cairo#L29-L161

Vulnerability details

Summary

In the Kakarot zkEVM implementation exec_precompile, unauthorized contracts can bypass the precompile authorization mechanism using the delegatecall. This allows unauthorized contracts to execute privileged precompile functions intended only for authorized (whitelisted) contracts, compromising the security and integrity of the system.

Vulnerability Details:

Let's take a look at the exec_precompile function with the unessential part omitted for brevity:

func exec_precompile{
    syscall_ptr: felt*,
    pedersen_ptr: HashBuiltin*,
    range_check_ptr,
    bitwise_ptr: BitwiseBuiltin*,
}(
    precompile_address: felt,
    input_len: felt,
    input: felt*,
    caller_code_address: felt,
    caller_address: felt,
) -> (output_len: felt, output: felt*, gas_used: felt, reverted: felt) {

    //SNIP...
    // Authorization for Kakarot precompiles
    let is_kakarot_precompile = PrecompilesHelpers.is_kakarot_precompile(precompile_address);
    if is_kakarot_precompile != 0 {
        // Check if caller is whitelisted
        let is_whitelisted = KakarotPrecompiles.is_caller_whitelisted(caller_code_address);
        if is_whitelisted == FALSE {
            jmp unauthorized_call;
        }
        // Proceed with precompile execution
        // ...
    } else {
        jmp unauthorized_call;
    }
}

The issue arises because the authorization mechanism relies solely on caller_code_address, which refers to the code being executed, and does not consider caller_address, the actual address of the contract making the call. This approach is insufficient and can be exploited using delegatecall.

Analogy:
If an EVM contract A delegatecalls EVM contract B, and B is allowlisted, then the call to the Cairo precompile succeeds because the allowlist is based on the code address (of B) and not on the execution address (of A).
This means that an unauthorized contract (Contract A) can exploit this behavior to execute privileged operations by delegatecalling a whitelisted contract (Contract B) and passing the authorization check incorrectly.

As such, it is an issue for other whitelisted smart contracts allowing arbitrary calls to external smart contracts. For example, this is the case of some smart contracts allowing callbacks. In those situations, a malicious user could make a DEX execute a call to the malicious smart contract that would be able to access the precompiles pretending to be the DEX.

Impact

This issue could allow unauthorized contracts to perform operations that should be restricted to whitelisted contracts like in the case of DEX that supports callbacks and has been added to the allowistlist. Moreover, all contracts within the allowistlist must be written with the assumption that they can also be delegatecalled; i.e they must not make any assumptions about their storage or their own EVM address (address(this)).

Prior to my discussion with the sponsor in a private thread about the implications of this attack surface, the sponsor intended to eventually remove the whitelist mechanism, not fully considering the dangers of delegatecalls to precompiles, which would have been a bad idea.

And with the team’s plan to eventually remove the allowlist, prior to knowing about this attack surface, it gives any contract the opportunity to exploit this on KakarotEVM, which, as we’ve seen, can be catastrophic—especially in the case of MoonBeam’s msg.sender impersonation disclosure by pwning.eth

Recommendation

In the interim, the obvious mitigation is to ensure that all whitelisted contracts are written with the assumption that they can also be delegatecalled. And more importantly, the whitelist mechanism should never be removed as this would open a flood gate of vulnerabilities to the protocol.

A permanent solution to this issue would be to disable Delegatecalls for custom precompiles.

Assessed type

call/delegatecall

Metadata

Metadata

Assignees

No one assigned

    Labels

    3 (High Risk)Assets can be stolen/lost/compromised directly🤖_34_groupAI based duplicate group recommendation🤖_primaryAI based primary recommendationH-01bugSomething isn't workingedited-by-wardenprimary issueHighest quality submission among a set of duplicatesselected for reportThis submission will be included/highlighted in the audit reportsponsor confirmedSponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")sufficient quality reportThis report is of sufficient quality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions