Closed c4-bot-7 closed 7 months ago
saxenism (sponsor) disputed
Considered Invalid because all fields are set on L1 contract and then the hash is double checked in the bootloader
The Warden specifies that an upgrade transaction can be replayed by adjusting one of the reserved
fields of the transaction given that it accepts multiple valid values and is not part of the TransactionValidator
signature validation process.
While an interesting observation, an upgrade transaction will not be replayable due to how protocol version upgrades cannot be performed to the same version per the relevant functions in the BaseZkSyncUpgrade
contract (1, 2).
alex-ppg marked the issue as unsatisfactory: Invalid
Lines of code
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/libraries/TransactionValidator.sol#L55 https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/libraries/TransactionHelper.sol#L118-L136
Vulnerability details
Impact
Field
reserved
is not being taken into consideration when encoding hash of the zkSync native transaction type (TransactionHelper._encodeHashEIP712Transaction()
).Moreover, according to
TransactionValidator.validateUpgradeTransaction()
, upgrade transaction is valid whenreserved[1] <= type(uint160).max
. This means that it's possible to replay upgrade transaction by providing different values toreserved[1]
field.Proof of Concept
File: TransactionValidator.sol
While
reserved[0]
,reserved[2]
,reserved[3]
must equal to0
- thereserved[1]
just needs to be lower or equaltype(uint160).max
.File: TransactionHelper.sol
Since
reserved
field is not being taken into consideration in_encodeHashEIP712Transaction()
, this basically means, that it would be possible to replay some transactions with different_transaction.reserved[1]
.reserved
is set to"0100"
reserved[1]
: nowreserve
is"0200"
.require(_transaction.reserved[1] <= type(uint160).max, "uf");
is valid (line 55 inTransactionValidator.sol
) - the whole upgrade transaction is still valid and it's possible to replay it.Tools Used
Manual code review
Recommended Mitigation Steps
reserved
fields should have constant values, until they are not taken into consideration inTransactionHelper._encodeHashEIP712Transaction()
. This means, that line 55 inTransactionValidator.sol
should be changed from:require(_transaction.reserved[1] <= type(uint160).max, "uf");
torequire(_transaction.reserved[1] == 0, "uf");
.Assessed type
Other