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: 16/64
Findings: 2
Award: $5,230.38
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: zero-idea
Also found by: 0x1337, 0xTheC0der, 0xstochasticparrot, Audittens, HE1M, Jeiwan, erebus, gumgumzum, leviticus1129, lsaudit, quarkslab, rvierdiiev
656.3255 USDC - $656.33
https://github.com/code-423n4/2023-10-zksync/blob/main/code/system-contracts/contracts/Constants.sol#L35 https://github.com/code-423n4/2023-10-zksync/blob/main/code/system-contracts/contracts/Constants.sol#L27-L28 https://github.com/code-423n4/2023-10-zksync/blob/main/code/system-contracts/contracts/AccountCodeStorage.sol#L93-L94
getCodeHash(address)
function from AccountCodeStorage
is called when the EXTCODEHASH
EVM opcode is used. By reading this function, it appears that precompile contracts bytecode hash is EMPTY_STRING_KECCAK = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
. The function returns this value for all addresses less than CURRENT_MAX_PRECOMPILE_ADDRESS
, currently 0x02
.
The problem is that there are 2 precompile contracts deployed at address 0x06
and 0x07
. The bytecode hash value returned by getCodeHash(address)
will be 0x00
for those two precompiles.
The main issue comes from CURRENT_MAX_PRECOMPILE_ADDRESS
in Constants.sol
, which is set to 0x02
. As such, it doesn't include EcAdd (0x06
) and EcMul (0x07
) precompiles.
Affected code:
Scenario:
EXTCODEHASH
to determine if a precompile exist before calling itEMPTY_STRING_KECCAK
To verify the issue, the following contract can be used:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ZigturLogPrecompiles { function returnHashFromPrecompiles(address _precompile) external view returns (bytes32 _value) { assembly { _value := extcodehash(_precompile) } } }
Then, the following unit test can be used:
it("Log precompiles hashes", async function () { const provider = new Provider(zkSyncTestnet.url); const wallet = new Wallet(RICH_WALLET_PK, provider); const deployer = new Deployer(hre, wallet); const LogPrecompilesArtifact = await deployer.loadArtifact('ZigturLogPrecompiles'); const logPrecompilesContract = await deployer.deploy(LogPrecompilesArtifact); const EcRecoverPrecompileHash = await logPrecompilesContract.returnHashFromPrecompiles('0x0000000000000000000000000000000000000001'); const Sha256PrecompileHash = await logPrecompilesContract.returnHashFromPrecompiles('0x0000000000000000000000000000000000000002'); const EcAddPrecompileHash = await logPrecompilesContract.returnHashFromPrecompiles('0x0000000000000000000000000000000000000006'); expect(EcRecoverPrecompileHash).to.equal('0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'); expect(Sha256PrecompileHash).to.equal('0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'); expect(EcAddPrecompileHash).not.equal('0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'); expect(EcAddPrecompileHash).to.equal('0x0000000000000000000000000000000000000000000000000000000000000000'); });
Hardhat + zkSync docker local setup
There are 2 solutions:
This solution consist of storing the EMPTY_STRING_KECCAK
hash value in the AccountCodeStorage
contract for each precompile.
This way, the precompiles check in getCodeHash
could be deleted and the returned value would be valid.
The second solution is to update CURRENT_MAX_PRECOMPILE_ADDRESS
in Constants
to set the new max precompile (0x07
). Applying this solution isn't the best thing as not yet available precompiles (i.e. 0x03
, 0x04
and 0x05
) will have a non-zero hash value.
Context
#0 - c4-pre-sort
2023-10-31T06:51:14Z
bytes032 marked the issue as duplicate of #142
#1 - c4-judge
2023-11-23T19:31:05Z
GalloDaSballo marked the issue as satisfactory
🌟 Selected for report: HE1M
Also found by: 0xstochasticparrot, erebus, quarkslab, rvierdiiev
4574.0453 USDC - $4,574.05
Judge has assessed an item in Issue #379 as 2 risk. The relevant finding follows:
Empty contracts hash doesn’t respect zkSync’s bytecode hash format The code hash returned by the getCodeHash function does not comply with zkSync’s bytecode hash format when querying an empty account, a constructing contract or a precompiled contract.
The getCodeHash function is supposed to simulate the behavior of the EXTCODEHASH EVM opcode, as per the EIP-1052 specification. However, zkSync redefined how bytecode hashes are defined:
“”" On zkSync the bytecode hashes are stored in the following format:
The 0th byte denotes the version of the format. Currently the only version that is used is “1”. The 1st byte is 0 for deployed contracts’ code and 1 for the contract code that is being constructed. The 2nd and 3rd bytes denote the length of the contract in 32-byte words as big-endian 2-byte number. The next 28 bytes are the last 28 bytes of the sha256 hash of the contract’s bytecode. The bytes are ordered in little-endian order (i.e. the same way as for bytes32 ). “”" getCodeHash returns the bytecode hash as stored in the AccountCodeStorage system contract (i.e. complying with zkSync’s definition of a bytecode hash), except for empty accounts, constructing contracts and precompiled contracts, for which it returns keccak256("") == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470.
The valid value would be either 0x0100000086f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 or 0x0101000086f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 depending on whether the contract is constructing or not.
The impact is that smart contracts can’t rely on bytecode hash to know the bytecode size of an account. As the size in the hash is messed up for impacted accounts, smart contract will not consider those accounts as zero bytecode size ones. The decoded size from the hash would 0x4601 = 17921 instead of 0x0000.
Note: impacted accounts are empty, constructing and precompiled contracts.
Classification CWE-1068: Inconsistency Between Implementation and Documented Design
#0 - c4-judge
2023-12-13T15:47:21Z
GalloDaSballo marked the issue as duplicate of #133
#1 - c4-judge
2023-12-13T15:47:27Z
GalloDaSballo marked the issue as satisfactory