Incorrect state for accounts without code and nonce #10
Labels
bug
Something isn't working
downgraded by judge
Judge downgraded the risk level of this issue
grade-b
primary issue
Highest quality submission among a set of duplicates
QA (Quality Assurance)
Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax
🤖_primary
AI based primary recommendation
sponsor confirmed
Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")
sufficient quality report
This report is of sufficient quality
Lines of code
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/backend/starknet.cairo#L203
Vulnerability details
Impact
When committing the state of an account, if the account has no code and no nonce, nothing is committed. This creates a critical issue when a non-existent account receives some native token. Since nothing is committed for this account (because it has no code and no nonce), its
extcodehash
will be zero when it should actually bekeccak256("")
.Proof of Concept
Imagine there’s an address X, which is a non-existent account (no code, no nonce, and no balance). If you get the
extcodehash
of this address, it returnsbytes32(0)
as expected. Here’s how theextcodehash
operation works: it shows that if the account doesn’t exist, zero is returned, but if the account exists, the hash of the code at address X is returned.https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/instructions/environmental_information.cairo#L486
If some native token is sent to address X, calling
extcodehash(address(X))
in the same transaction would returnkeccak256("")
because the account now exists (since its balance is no longer zero). This works fine in Kakarot.Here’s the problem:
Imagine address X, a non-existent account, receives some native token in the first transaction. Then, in a second transaction (not the same transaction), the
extcodehash
opcode is used to get the code hash at address X. It should returnkeccak256("")
since the account now exists (with a non-zero balance), but instead, it returnsbytes32(0)
on Kakarot, incorrectly.The root cause of this issue is as follows:
When the first transaction finishes, the new state of the accounts involved (i.e. sender and Address X) is committed.
The transaction flow looks like this:
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/backend/starknet.cairo#L203
In this function, when the new state of address X is being committed, it checks if the nonce or code for X is non-zero. Since both are zero (because only native tokens were received, so neither the nonce nor the code changed), nothing is committed. This means the code hash for X (which should now be
keccak256("")
) is not set.When the second transaction runs and
extcodehash(address(X))
is called, since the balance is non-zero, it skips the if block:https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/instructions/environmental_information.cairo#L486
But since the code hash wasn’t saved after the first transaction, it pushes zero onto the stack instead of
keccak256("")
.This clearly demonstrates that
extcodehash
is not functioning the same way as it does on Ethereum. This could have a significant impact depending on the context of different projects. For instance, a malicious user could send 1 wei to any non-existent EOA, ensuring that theextcodehash
for that EOA remains incorrectly set to zero. This would mislead any project relying on that information.PoC
In the following PoC, the test
test_reset_extcodehash
considers a non-existent account (no code, no nonce, no balance) at the address0x5b300000009Cf68545DCFcB03FCB875056BEDdC4
.First, the function
test1()
is executed. Initially, it checks the account’s balance and codehash, showingbalance = 0
andcodehash = 0
. Then, within the sametest1()
function, 2 wei is transferred to the address, and both the balance and codehash are recalculated. This correctly showsbalance = 2
andcodehash = keccak256("")
.Next, the function
test2()
is called, which repeats the same process for the same address. It first checks the balance and codehash, correctly showingbalance = 2
, but incorrectly displayscodehash = 0
. Intest2()
, another 2 wei is transferred to the address, and the balance and codehash are recalculated. The balance is correctly shown as4
, but the codehash remains incorrectly at0
.In the second test,
TestResetExtcodehashAddress0
, only the balance andextcodehash(address(0))
are checked. It shows that even though the balance is nonzero, the codehash is incorrectly displayed as zero.The following function was added to the address
2024-09-kakarot\kakarot\kakarot_scripts\utils\kakarot.py
, so that when deploying theTestResetExtcodehash
contract, 10 wei is just transferred to it initially. This helper simplifies running the main test directly.To run the end-to-end tests correctly, the
make test-end-to-end7
command should be used, as defined in theMakefile
.Output log is:
Tools Used
Recommended Mitigation Steps
The following modification can solve the issue:
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/backend/starknet.cairo#L164
Assessed type
Context
The text was updated successfully, but these errors were encountered: