zkSync Era - dontonka's results

Future-proof zkEVM on the mission to scale freedom for all.

General Information

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

zkSync

Findings Distribution

Researcher Performance

Rank: 24/64

Findings: 1

Award: $3,293.31

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: erebus

Also found by: AkshaySrivastav, Audittens, HE1M, dontonka, twcctop

Labels

bug
2 (Med Risk)
low quality report
satisfactory
edited-by-warden
duplicate-260

Awards

3293.3126 USDC - $3,293.31

External Links

Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/main/code/contracts/ethereum/contracts/governance/Governance.sol#L46 https://github.com/code-423n4/2023-10-zksync/blob/main/code/contracts/ethereum/contracts/governance/Governance.sol#L249-L259

Vulnerability details

Description

The Governance contract is a bit special as not role based, but instead controlled by two multi-sig wallet which both have certain control over it. There is the owner (Matter Labs) and a security council formed by some external members. The reason for this design is to increase security over important operation and allow for instant operation in case of emergencies. So both parties should not trust each other blindly, and this smart contract should fill the gap to enforce this trust. This contract is very powerfull as it has the power to upgrade any smart contract on zKSync Era, from system contract to any user contract, it's like a root access on Linux, so it's critical to manage security accordingly.

I think the current implementation is too flexible and not secure enough, which defeat the purpose of having the security council. While normally this would be a Low severity submission, I feels it deserve a Medium severity as it's more a fundamental change.

  1. securityCouncil allowed to be removed (set to address(0)) or initialize to address(0) at deployement time.
  2. minDelay allowed to be 0.
  3. updateSecurityCouncil and updateDelay can be updated by the owner only.

Impact

  • owner can bypass security council.
  • updateSecurityCouncil and updateDelay operation might be missed by security council.

Proof of Concept

The owner can bypass the security council in few ways:

  1. securityCouncil == address(0) AND minDelay = 0: Such state would allow the owner todo instant transaction.
  2. securityCouncil == address(0) AND minDelay > 0: Such state would allow the owner todo scheduled transaction without any security counsil being able to cancel.
  3. securityCouncil != address(0) AND minDelay = 0: Such state would allow the owner todo instant transaction bypassing the security counsil.
  4. securityCouncil != address(0) AND minDelay > 0: This should be the state of the contract after deployment. The security council should not trust action from the owner (that's their purpose, so any scheduleShadow should be cancel by them if not consulted before hand about what is this transaction about). When a scheduleShadow occurs, the communication about this transaction is offchain and adhoc, there is always a risk of miss information being communicated between the owner and the security council, as the owner cannot be blindly trusted, it's the reason why there is a security council. Nevertheless, the current implementation could allow the following scenario:
  • ) owner decide to go rogue or keys leak. There is an important fix to be addressed which cannot go public as it could be exploited, so the owner proceed with a scheduleShadow but include 2 transactions (the security council would not see it as shadowed):
    1. minDelay = 0
    2. do the actual upgrade to fix a specific bug
  • ) that could be executed instantly with the agreement of the security council (or could wait the minDelay).
  • ) what happen after the bug is fixed, everyone is happy, but now the owner can execute transaction instantly, which defeat the purpose of the security council (as they would not have the time to cancel). The security council could detect this soon after as ChangeMinDelay event could be detected or directly checking the value of minDelay, but this is not atomic, and there will be a window where the owner will be able todo whatever he wants instantly.

There is a reason why there is a security council, and this means the contract should enforce rules that prevent the owner to bypass them for important operation against the behavior of governance contract itself.

Tools Used

Manual inspection

Firstly, I would recommend to prevent minDelay to be set to zero but to a minimum value instead (hardcoded to some value that make sense), as instant transaction are already possible throught the security council. It make sense for the original OZ TimelockController contract to allow a zero minimum delay, since otherwise instant operation are impossible, but with the current addition here, that feel a bit too permissive.

Furthermore, updateSecurityCouncil and updateDelay are very critical function which should always require the agreement of both the owner and the security council (which should be mandatory at construction time to be none address(0)), not a single party, and only be allowed as scheduleTransparent, so both parties understand the change involved and don't need to trust an offline communication for those changes, it's not to hide a specific exploit anyway. The fact that security council can cancel the operation is not secure enough for those operations, which is why those two operations should require the direct acceptance of both parties, and not assume if they didn't cancel it means for sure they saw the operation and agree with it.

To acheive this I would propose something similar to a 2-step ownership transfer.

  1. owner call scheduleTransparent todo updateSecurityCouncil or updateDelay as usual.
  2. you would have a new logic as follow for the owner and security council to accept those governance operation change and execute it.
   modifier onlySingleGovernanceTx(Operation calldata _operation) {
        bytes32 id = hashOperation(_operation);
        require(isOperationPending(id), "Operation must be pending");
	require(_operation.calls.length == 1, "Only one governance transaction per operation");
	//TODO decode _operation.calls[0].data and require selector to be IGovernance.updateSecurityCouncil or IGovernance.updateDelay
	_;
    }
	
    modifier onlyOwnerAndSecurityCouncil() {
        require(ownerAccepted != byte32(0) && ownerAccepted == scAccepted, "Owner and security council must have accepted the transaction");
        _;
    }	
	
    function ownerAcceptGovernanceTx(Operation calldata _operation) external onlyOwner onlySingleGovernanceTx {
	require(ownerAccepted == byte32(0));
	ownerAccepted = id;
        emit OwnerAcceptedGovernanceTx(id);
    }

    function securityCouncilAcceptGovernanceTx(Operation calldata _operation) external onlySecurityCouncil onlySingleGovernanceTx {
	require(scAccepted == byte32(0));
	scAccepted = id;
        emit SCAcceptedGovernanceTx(id);
    }

    function cancelGovernance(bytes32 _id) external onlyOwnerOrSecurityCouncil {
        require(isOperationPending(_id), "Operation must be pending");
        delete timestamps[_id];
	ownerAccepted = byte32(0);
	scAccepted = byte32(0);		
        emit OperationGovernanceCancelled(_id);
    }

    function executeGovernance(Operation calldata _operation) external onlyOwnerAndSecurityCouncil {
	// same as execute...
	ownerAccepted = byte32(0);
	scAccepted = byte32(0);
    }

Personally, I would probably even add such the described 2-steps acceptance for upgrade that affects user space, as this should pretty much never happen and only in case of catastrophic hacks (like this one on Ethereum), and should require direct acceptance of both parties. That would be a little bit more involved and might not be possible, but still throwing the idea.

Finally, we also need to consider the low probability but still possible that the security council is going rogue or keys leaked. Fortunatelly, the current implementation is already mitigating this by only allowing owner to schedule transactions, so even if security council can execute instant transaction, since they cannot schedule transaction themself, that is safe and secure.

Assessed type

Governance

#0 - c4-pre-sort

2023-10-31T06:58:56Z

bytes032 marked the issue as low quality report

#1 - miladpiri

2023-11-09T10:41:11Z

Some part is duplicate. Some part is invalid.

#2 - c4-judge

2023-11-26T18:01:58Z

GalloDaSballo marked the issue as duplicate of #260

#3 - c4-judge

2023-11-28T15:53:17Z

GalloDaSballo marked the issue as satisfactory

#4 - nethoxa

2023-11-29T20:42:20Z

Regarding points 1, 2, 3, those are privileges abuse which are covered under the bot race/analysis (and can be checked pretty easily via etherscan/cast and the community would know). Regarding 4, the security council can request the operation to be executed and match it with the submitted one, as they are hashed and the check in line 172 covers that (which means the governor can NOT trick the security council by appending a malicious payload via a shadow upgrade). Moreover, it does not talk about the governance contract being broken if one of the actors gets compromised and the reasons behind that.

#5 - GalloDaSballo

2023-12-10T08:46:52Z

While the disputes are somewhat legitimate, I believe the finding is a valid duplicate of 260 and will keep as is

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter