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: 24/64
Findings: 1
Award: $3,293.31
🌟 Selected for report: 0
🚀 Solo Findings: 0
3293.3126 USDC - $3,293.31
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
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.
securityCouncil
allowed to be removed (set to address(0)) or initialize to address(0) at deployement time.minDelay
allowed to be 0.updateSecurityCouncil
and updateDelay
can be updated by the owner only.updateSecurityCouncil
and updateDelay
operation might be missed by security council.The owner can bypass the security council in few ways:
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:scheduleShadow
but include 2 transactions (the security council would not see it as shadowed):
minDelay = 0
actual upgrade to fix
a specific bugChangeMinDelay
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.
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.
updateSecurityCouncil
or updateDelay
as usual.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.
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