Underconstrained output in felt_to_bytes_little()
allows contract deployments to incorrect addresses
#39
Labels
3 (High Risk)
Assets can be stolen/lost/compromised directly
bug
Something isn't working
duplicate-118
edited-by-warden
🤖_primary
AI based primary recommendation
🤖_03_group
AI based duplicate group recommendation
satisfactory
satisfies C4 submission criteria; eligible for awards
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/utils/bytes.cairo#L80
Vulnerability details
To explain this finding, we compare two utility functions that are very similar in their implementation:
load_bytecode()
inkakarot/src/kakarot/accounts/library.cairo
andfelt_to_bytes_little()
inkakarot/src/utils/bytes.cairo
. Both contain logic for un-packing up to 31 bytes from afelt
into an array of single bytes.If we isolate only the few interesting lines of the inner loop in
load_bytecode()
, we have the following:The loop starts with
count = BYTES_PER_FELT
(which is31
), then at every cyclecount
is decreased by1
, and when it reaches the value0
, the loop ends, finally asserting thatvalue = 0
.This correctly ensures the
[output]
values provided by the prover are honest, because if[output]
wasn't effectively containing the last byte ofvalue
, the field division at L576 would have increasedvalue
(instead of decreasing it) to a value that couldn't be brought down to0
with at most31
iterations of chopping off the least significant byte.To summarize,
load_bytecode
is safe because:[output]
is checked to be between 0-255 values (code omitted for brevity)value
is checked to be0
at the end of the loop.Now, we compare this to the corresponding loop in
felt_to_bytes_little()
:This time, the terminating condition is
value = 0
, wherevalue
is the inputfelt
after the iterative field divisions. Also in this case,value
can not only shrink, but also grow larger at L74 if the[output]
provided by the prover is not honest.If we compare with the three conditions that make
load_bytecode
safe, we see that the second condition2. no more than 31 iterations are allowed
does not hold in this case, so the prover is effectively allowed to provide an arbitrary number of bytes, as long as these are followed by more bytes that bringvalue
back to zero.In other words, the lack of check that
bytes_len
is at most31
leaves space for the prover to provide arbitrary bytes in output, making theload_bytecode
function under-constrained and un-sound.For an exploit to happen, however,
bytes_len
cannot be purely arbitrary, because this value serves as a lookup index into thepow256_address
table to enforce this other constraint:This is, however, not a strong requirement, because the
pow256_address
can be looked up out-of-bounds (as proven in the below PoC), effectively looking up values from portions of the bytecode that have a different purpose.Within Kakarot, the
felt_to_bytes_little
function is used to calculate addresses of contract creation with all methods (CREATE, CREATE2, and EOA transactions withoutto
). While the CREATE2 path is not exploitable for external constraints (most notably amemset
call exploding in gas consumption in case of an exploit), the other methods are vulnerable. A malicious prover can therefore artificially alter the address created from EOAs and from contracts using theCREATE
opcode.This can cause users loss of funds because they may create and fund a contract, but the contract may end up being deployed at a different address, causing the provided funds to be permanently locked. While users may be on the guard for this address change to happen with contracts created via the CREATE opcode (that is notably vulnerable in case of block reorgs), this would certainly be a behavior that can't be predicted in case of creation from EOA where the caller has direct control over the nonces.
Proof of Concept
The below test patches the
felt_to_bytes_little
prover hint to show how malicious data can be passed via arbitrary execution and still meet all theassert
statements coded:In particular, with
value = 3
, while an honest prover would provideoutput = [ 3 ]
, the test demonstrates how a malicious prover can provide the following output consisting of 34 bytes:Recommended Mitigation Steps
Consider adding an additional constraint to
felt_to_bytes_little
for the final value ofbytes_len
not to exceed31
.Assessed type
Other
The text was updated successfully, but these errors were encountered: