Open c4-submissions opened 11 months ago
bytes032 marked the issue as primary issue
bytes032 marked the issue as sufficient quality report
Invalid. Due to the nature of native AA, we ignore EIP3607.
miladpiri (sponsor) disputed
@miladpiri wdyt about preventing a EOA being able to take ownership of a SC? Wouldn't it be better to enforce the check?
Regarding the report:
from
is an EOA (default account), then tx.origin
will be equal to from
.from
is a custom account, then it's raw-code-hash will be nonzero, so it will not be considered as an EOA. So, tx.origin
will be set to bootloader
address, and since extcodehash(bootloader)
returns nonzero
and none-EMPTY_STRING_KECCAK
, tx.origin
will not be considered as an EOA anymore. This is expected due to nature of native AA.So, this finding is not an issue or bug.
Regarding your question @GalloDaSballo , I could not understand it correclty.
Regarding the report:
- If
from
is an EOA (default account), thentx.origin
will be equal tofrom
.- If
from
is a custom account, then it's raw-code-hash will be nonzero, so it will not be considered as an EOA. So,tx.origin
will be set tobootloader
address, and sinceextcodehash(bootloader)
returnsnonzero
andnone-EMPTY_STRING_KECCAK
,tx.origin
will not be considered as an EOA anymore. This is expected due to nature of native AA.So, this finding is not an issue or bug.
Regarding your question @GalloDaSballo , I could not understand it correclty.
EIP-3607 was added to prevent the scenario of being able to find the PK of a contract that contains value, without it any PK that maps out to an address could start a tx as an EOA and steal the funds
Basically the check prevents being able to find a PK that maps out to a deployed Smart Contract
From the EIP
We estimate that a collision between a contract and an EOA could be found in about one year with an investment of ca. US$10 billion in hardware and electricity.
Meaning that this could be weaponized in the future
Is EIP-3607 already enforced on zkSync? Do you think it should / should not?
We already prevent the attack mentioned by:
from
is called, i.e. just EOA address collision is not enough. The account must literally accept the signature, which at this case, it is not a bug anymore. In other words, when an EOA triggers a transaction, the default account contract is called by the bootloader to validate the signature. In case, if a PK of a contract is found, it is still not possible to drain the fund. Because that contract must implement the validateTransaction
function and validate the signature.GalloDaSballo changed the severity to QA (Quality Assurance)
Downgrading to QA and invite the Warden to send a follow up in PJQA, I believe that zkSync has addressed the underlying issue as shown above by the Sponsor
Hi @GalloDaSballo,
Following up on the present issue:
I agree with the sponsor @miladpiri:
Due to the nature of native AA, we ignore EIP3607.
In case someone got the private key for an address where a contract is deployed and sends a transaction from that address, the bootloader's ensureAccount(...) function would revert at the beginning of the validation step even before any signature checks are performed.
If this check passes because the contract was registered as an account at the ContractDeployer
, the contract would still need to implement the validateTransaction(...)
(as mentioned by the sponsor), executeTransaction(...)
and payForTransaction(...)
methods in order for the transaction to be executed.
Due to this native implementation of account abstraction, the attack outlined by EIP-3607 is not possible on zkSync Era, therefore they chose to ignore EIP-3607 compliance as a whole.
tx.origin
(bootloader address) and its codehash not being an EOA, therefore EIP-712 transactions are not compliant with EIP-3607 and not consistent with the L1 EVM.
tx.origin
(very unlikely, but possible), such transactions would fail. All in all, zkSync Era strives for equivalence with the EVM where possible to avoid compatibility issues for protocols running on multiple chains including zkSync Era.
Following the Recommended Mitigation Steps
, i.e. complying with EIP-2938: Account Abstraction concerning tx.origin
, native account abstraction and EIP-3607 could go hand-in-hand.
Thanks @MarioPoneder
I do not see any issue. Moreover, while compiling a contract that is using tx.origin
in zkSync Era, a warning is displayed:
https://github.com/matter-labs/era-compiler-solidity/blob/main/src/solc/standard_json/output/error/mod.rs#L123
I believe that this finding was a good addition to the contest and a worthwhile discussion
This ultimately highlights a gotcha due to native account abstraction
Overall the finding points at a risk that integrators can have
For this reason, I believe that QA is most appropriate for this contest
The responsibility of defending their code will fall to the integrators, for those contracts this finding may cause further impacts, however that would require said contracts to be in-scope which in this case they are not
For this reason, after confirming with the Sponsor that EOAs can start txs, but also agreeing with the Warden that tx.origin checks can create gotchas, am going to maintain QA severity
Lines of code
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/bootloader/bootloader.yul#L1321-L1328
Vulnerability details
Impact
The EIP-3607: Reject transactions from senders with deployed code requires transactions where the
from
address (tx.origin
in Solidity) is not an EOA to be rejected.This EIP was alread merged into the Ethereum yellow paper and the Geth execution client.
However, in case of EIP-712 transactions on L2 using custom account abstraction, the
from
address is not an EOA due to having a codehash, therefore the bootloader uses its own address astx.origin
, see ZKSYNC_NEAR_CALL_executeL2Tx(...):The bug is, that the bootloader address also points to a non-zero & non-empty codehash, see also AccountCodeStorage.getRawCodeHash(...) & AccountCodeStorage.getCodeHash(...), therefore the bootloader is not recognized as an EOA which defies the purpose of the above
switch-case
block.As a consequence, EIP-712 transactions using custom account abstraction do not comply with EIP-3607 and therefore are in danger to fail:
Side note: Strictly, account validation and (paymaster) gas refund of any transaction type are also not compliant with EIP-3607, due to
tx.origin
being the bootloader address, but those are special cases and not responsible for the execution of the actual transaction.The main issue remains that Legacy & EIP-1559 L2 transactions comply with EIP-3607, but EIP-712 transactions using custom account abstraction do not.
Proof of Concept
The following PoC tests the EIP-3607 conformity of multiple transaction types and shows that the
tx.origin
, in case of EIP-712 transactions using custom account abstraction, indeed points to non-zero & non-empty codehash leading to nonconformity.First, add the custom account contract
./code/system-contracts/contracts/test-contracts/CustomAccount.sol
from the secret gist (too long to include in report). It is an 1:1 copy of theDefaultAccount
, just with custom signature validation and it registers itself as account on construction.Second, add the checker test contract
./code/system-contracts/contracts/test-contracts/EIP3607Checker.sol
:Next, apply the diff to the existing
DefaultAccount
test suite and runbash quick-setup.sh
from./code/system-contracts/scripts
to execute the PoC test case:Tools Used
Manual review
Recommended Mitigation Steps
As suggested by EIP-2938: Account Abstraction, use
0xffffffffffffffffffffffffffffffffffffffff
instead of the bootloader's address astx.origin
on EIP-712 transactions to be compliant with EIP-3607 and consistent with the L1 EVM.Assessed type
Context