-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Missing Constraint in default_dict_copy #91
Comments
Severity: High Comment: Ok |
dmvt marked the issue as selected for report |
hey all, after another round of review from Zellic, it appears that this is invalid (went to fast in the validation of the finding) as the default_value is asserted below in the loop kkrt-labs/kakarot@474951c#diff-18450849ea8d97868875c0b5eeb0934a496dbf959c60a5bcf9273ff5f2bbc298L117
|
Hey @ClementWalter, we picked a long time at this finding and were under the impression that it was valid. It would be great to get your take on the below POC which we believe would work:
|
I don't get the first part
what dict squashing does is that it enforces the fact that the sequence of So the Then we pick the first prev as default_value. And we copy the squash dict into a new dict, asserting in the mean time that all the prev_values are actually equal to the one picked. Eventually, it means that
Maybe you can try to craft a failing test case? |
@ClementWalter a full POC would take a bit of time, but maybe @3docSec has something lying around.
You're right, this may not be necessary. Squashing prevents modifying the Otherwise, as the finding claims:
This was never enforced in the code in scope. So we did have that:
But it was not verified that:
|
☝️ , just adding that if we look at the code: if (dict_len == 0) {
assert default_value = 0;
} else {
assert default_value = squashed_start.prev_value;
} in the |
There is a list of Now squashing enforces that this "random" list is actually a valid list of The "default" part of the dict is ignored in the initial From this squash dict, the code pick the first Then, it enforces that all the prev_keys are actually of this same prev_value, making it actually a At this point the Note that this function is Now, the issue may still be relevant if at some places, we don't call this So I guess that the issue is valid, not because the method itself, but because of missing |
Lines of code
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/kakarot/account.cairo#L83
https://github.com/kkrt-labs/kakarot/blob/7411a5520e8a00be6f5243a50c160e66ad285563/src/utils/dict.cairo#L83
Vulnerability details
Summary
In CairoZero, the correct usage of dict objects created via
default_dict_new
must be paired with a call todefault_dict_finalize
to ensure the integrity and prevent malicious prover's manipulation of its contents. However, this constraint is missing in the handling oftransient_storage
,storage
andvalid_jumpdests
, leading to severe vulnerabilities when executing smart contracts.Description of the Issue
According to CairoZero's documentation (link to default_dict), a proper workflow involving
default_dict_new
includes a finalization step usingdefault_dict_finalize
. This ensures the correct initialization of dictionary elements and prevents malicious provers from manipulating dictionary values through hints. Specifically,default_dict_finalize
enforces the constraint that the initial value of the first element'sprev_value
in the dictionary must equal to thedefault_value
.However, in the case of
transient_storage
,storage
andvalid_jumpdests
, this crucial constraint is missing.I will illustrate this issue using
transient_storage
. First,transient_storage
is initialized inAccount.init()
as follows:However, there is no subsequent call to
default_dict_finalize(transient_storage_start, transient_storage, 0)
to finalize the storage. Instead, the functiondefault_dict_copy()
is called ontransient_storage
multiple times during a transaction through theAccount.copy()
function:This copy operation starts by calling
dict_squash
on the originaltransient_storage
:dict_squash
itself does not assert theprev_value
of the first element in the dictionary.As a result, the subsequent copied transient_storage's initial value is also under the malicious prover's control.
If we look at the source code of default_dict_finalize, we will notice an extra constain in the
default_dict_finalize_inner
functionAs shown above, the additiona check besides
dict_squash
is:This constraint ensures that any uninitialized read from the dictionary returns the correct default value (
0
in this case). However, indefault_dict_copy
, this constraint is absent, meaning theprev_value
for the first dictionary entry oftransient_storage
is not guaranteed to match the expected default value.Impact
A malicious prover could manipulate the value read from
transient_storage
,storage
andvalid_jumpdests
. Specifically, they could fabricate a proof where uninitialized keys in the dictionary return values other than the intended default (0
). This could lead to unintended or unauthorized access to funds, manipulation of contract state, or other security breaches depending on the logic in the upper-level EVM contract.Assessed type
Invalid Validation
The text was updated successfully, but these errors were encountered: