Closed code423n4 closed 1 year ago
AFAIU, warden says that the innerSuccess may be true, but the returned size is zero. It looks off.
miladpiri marked the issue as sponsor disputed
miladpiri requested judge review
Logically speaking
ecrecover returns 0, and returndatasize
is non-zero (current code):
ecrecovers returns 0, and returndatasize
is zero:
For the logic above, I believe that the implementations are identical (semantically different but behaviors are the same), meaning that the finding is invalid
Please @iFrostizz feel free to send a coded POC of a different behavior if you can reproduce during Post Judging QA
GalloDaSballo marked the issue as unsatisfactory: Invalid
Hey @GalloDaSballo !
Thanks for your review,
The issue with the precompile return empty bytes with zero length but still yields a success is that the ecrecover
zk precompile will the load the bytes from the memory (those which should have been written by the precompile) at the key 32
.
Meaning that it will return sub(v, 27)
defined above, thus why the ecrecover is inconsistent and might lead to some funky behaviour.
Hey @GalloDaSballo !
Thanks for your review, The issue with the precompile return empty bytes with zero length but still yields a success is that the
ecrecover
zk precompile will the load the bytes from the memory (those which should have been written by the precompile) at the key32
. Meaning that it will returnsub(v, 27)
defined above, thus why the ecrecover is inconsistent and might lead to some funky behaviour.
Thank you for the extra info @iFrostizz can you please provide me with a coded POC of at least one instance that would cause this to happen?
Hey @GalloDaSballo ! Thanks for your review, The issue with the precompile return empty bytes with zero length but still yields a success is that the
ecrecover
zk precompile will the load the bytes from the memory (those which should have been written by the precompile) at the key32
. Meaning that it will returnsub(v, 27)
defined above, thus why the ecrecover is inconsistent and might lead to some funky behaviour.Thank you for the extra info @iFrostizz can you please provide me with a coded POC of at least one instance that would cause this to happen?
Right so I've done a small repro. Calling a contract that returns nothing but with a successful return:
assembly {
return(0, 0)
}
Will still allocate empty memory, so will overwrite the v in the memory as the retLen
of the call
was set to 0x20
.
Though, according to the yellow paper and shown in the report, the length of the return data of the Ecrecover may be 0 (denoted by ||o||
) if the return data of the ecrecover is none even if the inner call is true.
This is a POC: https://github.com/iFrostizz/ecrecover-mock
It tests for the unhappy path of an empty return data with a successful inner call, while the call shouldn't be yielding true.
@iFrostizz If we are returning a size of 0, then by definition aren't we returning a empty value which will be casted to 0?
Meaning we are returning the intended value?
I would maintain invalid unless we can have a case where we'd have ecrecover's EVM implementation returning 0 and the zkEVM implementation returning a non-zero value
@iFrostizz Please do provide with such an instance and we'll re-judge (or follow up with the Sponsor privately once you do)
Yep @GalloDaSballo , agreeing. I'll dig a bit more into the ecrecover inners and let you know
Lines of code
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L85 https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L86 https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L88 https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L71
Vulnerability details
Impact
According to the Ethereum yellow paper and in the specifications of the
ecrecover
precompile, it is stated that if theecrecover
doesn't return anything (denoted by∅
), then the return should be 0 as well.If we take a look at the current
ecrecover
implementation of the precompile, it does aprecompileCall
with the signature whose parameters (r, s, v) were checked before. Then loads the memory at slot 0 and we can see just after that in order to return some data, those two must betrue
.The issue is that compared to the yellow paper, there is no handling of the case when the precompile doesn't return anything, but yields a success boolean.
As the client is not in scope, it is not clear how it would result in if there was a bug there as well.
Then, it may return the data that is at the location
32
or0x20
in hex for a length of32
, most likely thev
parameter which was substracted by 1. This is not what we would expect from the result of aecrecover
call, that should normally return the address of the signer of the message.This is where it is handled in go-ethereum, the Ethereum client: https://github.com/ethereum/go-ethereum/blob/master/crypto/secp256k1/secp256.go#L118
Tools Used
Manual inspection
Recommended Mitigation Steps
Check that the
returndatasize
isn't zero as another parameter to return some data.