Closed code423n4 closed 1 year ago
GalloDaSballo marked the issue as primary issue
Primary because of coded POC
Intended by design. The value can be used for any purposes by the owner of the account. The other users who interacts with this value should know how the system works.
miladpiri marked the issue as sponsor disputed
I have considered Med and QA for the report, but I must close as OOS because the sponsor has documented these gotchas in the readme, please see the following
It provides a function `validateNonceUsage` which the bootloader uses to check whether the nonce has been used for a certain account or not. Bootloader enforces that the nonce is marked as non-used before the validation step of the transaction and marked as used afterward. The contract ensures that once marked as used, the nonce can not be set back to the "unused" state.
Note that nonces do not necessarily have to be monotonic (this is needed to support more interesting applications of account abstractions, e.g. protocols that can start transactions on their own, tornado-cash like protocols, etc). That's why there are two ways to set a certain nonce as "used":
- By incrementing the `minNonce` for the account (thus making all nonces that are lower than `minNonce` as used).
- By setting some non-zero value under the nonce via `setValueUnderNonce`. This way, this key will be marked as used and will no longer be allowed to be used as the nonce for accounts. This way it is also rather efficient since these 32 bytes could be used to store some valuable information.
As the sponsor said:
None of the statement was invalidated by the report and for this reason, in-spite of the quality of the submission I must close as OOS as already disclosed
GalloDaSballo marked the issue as unsatisfactory: Out of scope
Lines of code
https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/NonceHolder.sol#L77-L96 https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/NonceHolder.sol#L101-L104 https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/NonceHolder.sol#L151-L166 https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/bootloader/bootloader.yul#L2498-L2523
Vulnerability details
Impact
Specific inconsistent state caused by setters by sidestepping the checks in the codebase.
Once a nonce has been used, the value mapped under it shouldn't be changeable.
Proof of Concept
1) Some input validations exist in NonceHolder.setValueUnderNonce(). One prevents the nonce's value from being 0, the other hints at an attempt to have a sequentially used nonce.
2) However, it can be seen that there are no checks preventing a previously filled
key
to be used again and overwrite the_value
that was used for it. Therefore, callingsetValueUnderNonce()
twice in a row, with the same valid_key
but two different_value
, is valid, effectively overwriting thevalue
undernonce
.3) Given the fact that the method getValueUnderNonce() exists, it should then be expected that it returns the right value under the nonce, and not one that has been mistakenly overwritten at some point.
4) The impact here would be limited to a dirtied state as there's a validateNonceUsage() function called in the bootloader.
Coded POC
1) Use the following to setup a Foundry POC:
Setting up a hybrid project
Set the following:
And, unfortunately, for the project / current scope to compile with Foundry so that we can test what we want: we need to delete the
.yul
files undercontracts
(precompiles/
andEventWriter.yul
), otherwise we'll get an error telling us that the compiler only supports 1yul
file in the project.2) Replace the following so that the code can compile with Foundry. We'll be forcing a Sequential nonce ordering here but it doesn't really matter:
3) Inject the following coded POC under
2023-03-zksync/test/nonceHolder.t.sol
:4) Run the test with
forge test -m test_setValueUnderNonce -vv
and see the following output:Tools Used
Manual review, Foundry
Recommended Mitigation Steps
Given that isNonceUsed() will return true if there's a non-zero value under the nonce and there's a
Reusing the same nonce twice
error invalidateNonceUsage
, we can see here that an existing path to taint values under a nonce shouldn't exist.In
setValueUnderNonce()
, consider enforcing the unique operation of "writing a value under nonce" by adding a check similar to the following: