Platform: Code4rena
Start Date: 02/10/2023
Pot Size: $1,100,000 USDC
Total HM: 28
Participants: 64
Period: 21 days
Judge: GalloDaSballo
Total Solo HM: 13
Id: 292
League: ETH
Rank: 6/64
Findings: 2
Award: $21,458.48
๐ Selected for report: 1
๐ Solo Findings: 0
๐ Selected for report: OffsideLabs
14682.1206 USDC - $14,682.12
https://github.com/code-423n4/2023-10-zksync/blob/72f5f16ed4ba94c7689fe38fcb0b7d27d2a3f135/code/system-contracts/contracts/libraries/TransactionHelper.sol#L183-L187 https://github.com/code-423n4/2023-10-zksync/blob/72f5f16ed4ba94c7689fe38fcb0b7d27d2a3f135/code/system-contracts/contracts/DefaultAccount.sol#L78-L107
The L2 DefaultAccount.validateTransaction
function and transaction encoding process in the bootloader do not enforce EIP-155. If the reserved[0]
of the legacy tx is zero, the EIP-155 will be disabled. The chainId will not be included in the transaction signature.
bytes memory encodedChainId; if (_transaction.reserved[0] != 0) { encodedChainId = bytes.concat(RLPEncoder.encodeUint256(block.chainid), hex"80_80"); }
There are two potential exploitation scenarios:
A malicious attacker can replay transactions from the mainnet or other networks not protected by EIP-155. While there are very few EVM networks currently supporting transactions without EIP-155, attackers still have an opportunity to profit by replaying early transactions from other networks. For example, if an early ETH user A sent a legacy tx to user B before EIP-155 enabled, and now user A deposited some ETH into the Zksync era network, then user B could replay the early transaction from user A to steal his funds on the Zksync network.
Operators can replay early user transactions from eth/other EVM networks to collect gas fees or even profit directly.
EIP-155 will be disabled when the following two conditions are met:
transaction type is legacy tx, which tx_type=0
;
tx.reserved[0] == 0
The value of the reserved field cannot be set arbitrarily, but if the user inputs a transaction with tx_type=0
and the v
in the signature is 27 or 28, then reserved[0]
will be set to 0.
let should_check_chain_id = if matches!( common_data.transaction_type, TransactionType::LegacyTransaction ) && common_data.extract_chain_id().is_some() { U256([1, 0, 0, 0]) } else { U256::zero() };
pub fn extract_chain_id(&self) -> Option<u64> { let bytes = self.input_data()?; let chain_id = match bytes.first() { Some(x) if *x >= 0x80 => { let rlp = Rlp::new(bytes); let v = rlp.val_at(6).ok()?; PackedEthSignature::unpack_v(v).ok()?.1? }
Manual review
Enforce EIP-155 signature verification in the contract DefaultAccount.validateTransaction
Other
#0 - c4-pre-sort
2023-11-01T05:34:54Z
bytes032 marked the issue as primary issue
#1 - c4-pre-sort
2023-11-01T05:34:58Z
bytes032 marked the issue as sufficient quality report
#2 - miladpiri
2023-11-06T11:54:37Z
This is a design decision. So, QA.
#3 - c4-sponsor
2023-11-06T11:54:58Z
miladpiri (sponsor) confirmed
#4 - c4-sponsor
2023-11-06T11:55:03Z
miladpiri marked the issue as disagree with severity
#5 - GalloDaSballo
2023-11-23T19:22:38Z
Downgrading to QA because this is an option, not the default
#6 - c4-judge
2023-11-23T19:22:46Z
GalloDaSballo changed the severity to QA (Quality Assurance)
#7 - 5z1punch
2023-12-01T02:26:59Z
I have privately submitted a real attack example to sponsor and judge to demonstrate that this is a critical issue. However, due to its impact on the era mainnet, I am currently not publicly disclosing them on this page. Respectfully ping @GalloDaSballo @miladpiri
#8 - c4-judge
2023-12-05T12:51:24Z
This previously downgraded issue has been upgraded by GalloDaSballo
#9 - vladbochok
2023-12-11T12:04:24Z
This is design decision for supporting pre EIP155 transactions, that many other EVM compatible chains supports. The exact reason for supporting pre EIP155 transaction is having benefits of keyless transactions (as deterministic deployments). Due to this reason, I don't see this as a security issue at all.
#10 - c4-judge
2023-12-13T15:52:14Z
GalloDaSballo marked the issue as selected for report
#11 - c4-judge
2024-01-11T15:36:47Z
GalloDaSballo changed the severity to 2 (Med Risk)
#12 - GalloDaSballo
2024-01-11T15:40:26Z
I have spent tens of hours around this issue
Investigating it's impact And investigating why this issue is considered "acceptable"
I believe that a clear divide between Developers and Security Researchers can be seen in how Severity is interpreted for this issue.
Any "EVM Developer" will look at this finding as trivial, and obvious, "it's the user fault" for having a wallet that is pre EIP-155 And the usage of this signature scheme are keyless deployments
Any SR will look at this as a means to DOS users, and potentially steal their funds
I have spoken with a lot of different actors in the security space and have heard every possible severity for this type of findings
From known to high severity
After talking with 4 different judges, am going to set the finding as Medium Severity as zkSync is not uniquely subjected to this finding, but I believe the finding to be a danger to end users which should be publicly disclosed to them
๐ Selected for report: HE1M
Also found by: Audittens, OffsideLabs, zkrunner
6776.3633 USDC - $6,776.36
The _rawCompressedData
parameter for Compressor.publishCompressedBytecode
is currently provided directly by the operator and it represents the compressed bytecode, which should be published as โexpensiveโ pubdata. But the publishCompressedBytecode
function only check if the compressed data matchs with the original bytecode and bytecode hash. The only check for the length of _rawCompressedData
is
require(dictionary.length <= 2 ** 16 * 8, "Dictionary is too big");
However, the check doesn't guarantee the compression ratio of the compressed bytecode. So a malicious operator submit a very long but valid dictionary
to overcharge users gas, and even force users transactions to fail.
Malicious operators can overcharge users gas for the L2 transactions with FactoryDeps
.
And since the publishCompressedBytecode
function is called at the very beginning of bootloader.l2TxExecution
, if a malicious operator exploits this vulnerability to consume all the gas passed into ZKSYNC_NEAR_CALL_markFactoryDepsL2
, the normal transaction execution may be forced to fail due to out of gas.
The compressed bytecode is inputted directly from operator memory in the ZKSYNC_NEAR_CALL_markFactoryDepsL2
:
let dataInfoPtr := mload(COMPRESSED_BYTECODES_BEGIN_BYTE())
The operator can first compress the bytecode into the correct format and then pad the end of the dictionary with 0xff
bytes that won't be used or checked, until the dictionary length reaches its maximum value of 2**19
.
Manual review
Check the compression ratio, the length of the compressed bytecode should not be greater than the original length.
Invalid Validation
#0 - bytes032
2023-11-01T16:08:44Z
length of compressed bytecode should <= original length
#1 - c4-pre-sort
2023-11-02T13:56:48Z
141345 marked the issue as sufficient quality report
#2 - miladpiri
2023-11-06T11:37:05Z
This finding is valid but it is not explaing the attack clearly. This is duplicate of https://github.com/code-423n4/2023-10-zksync-findings/issues/71 that is explaining better and detailed.
#3 - c4-sponsor
2023-11-06T11:37:14Z
miladpiri marked the issue as disagree with severity
#4 - c4-sponsor
2023-11-06T11:37:26Z
miladpiri (sponsor) confirmed
#5 - c4-judge
2023-11-25T10:17:12Z
GalloDaSballo marked the issue as duplicate of #71
#6 - c4-judge
2023-11-28T15:51:29Z
GalloDaSballo changed the severity to 2 (Med Risk)
#7 - c4-judge
2023-11-28T15:51:39Z
GalloDaSballo marked the issue as satisfactory