Incident Report: Unlock-prevention-while-voting-invariant Bug

Start, End [2020-12-24 1328 UTC - ongoing, estimated end Jan 2020]
Impact Specific timing of concurrent proposals could allow for malicious users to vote more than once on the same proposal
Root cause summary Incorrect time comparison

Timeline and Impact

[2020-12-24 1328 UTC] Community member Zviad submits email to outlining the bug
[2020-12-24 1548 UTC] Tim shared more broadly with relevant stakeholders and creates slack channel for mitigation
[2020-12-24 1652 UTC] * Nam creates incident report
* Asa and Nam assess severity of the bug
* First read is that the bug is not in need of immediate fix as current voting behaviors and trustworthiness of governance approvers significantly limits the downsides to exploiting this bug
* OpenZeppelin notified of a bug that we want to release
2020-12-24 1711 UTC * It was determined that a simple mitigation is to suggest to governance approvers not to allow concurrent proposals to have partially overlapping referendum phases with the overlap being > unlock period for Locked CELO
* “Proper” mitigation will look as follows:
   1. Fix will be created in the upcoming days, submitted on a private branch to OpenZeppelin
   2. OpenZeppelin to audit as part of release 2
   3. Fix gets merged on the public release branch
   4. Release timing will remain as planned, with proposal to upgrade on Mainnet around January 25
2020-12-26 1709 UTC * Martin shared unit test case and fix
2020-12-29 1041 UTC * Incident Report shared with OpenZeppelin
2020-12-31 1445 UTC * OpenZeppelin confirms fix
2020-01-04 2015 UTC * Martin submitted public PR to be released in Celo Core Contract Release 2

Root Causes

  • To avoid a user voting, unlocking, transferring, locking and voting again on the same proposal, the Governance contract exposes an isVoting function that the LockedGold contract uses to only allow an unlock when the account is no longer voting on a governance proposal
  • Governance keeps track of that in the Voter struct with the field mostRecentReferendumProposal
  • However, in the vote function, Governance compares the timestamp of the proposal against this value, which will always be true, and thus the most recently voted proposal will be stored (instead of the proposal with the latest referendum end time)
  • Thus, a malicious voter with a specific timing of concurrent governance proposals could (copied from original report)
    • assume ReferendumStage duration is 1 week.
      • assume there are two proposals in referendum stage: Prop1, and Prop2
      • Prop1 has 1 day left, Prop2 has 7 days left.
      • User can:
    • ** Vote for Prop2 (mostRecentReferendumProposal will become Prop2)
    • ** Vote for Prop1 (mostRecentReferendumProposal will incorrectly become Prop1)
    • ** Wait a day so Prop1 enters ExecutionStage
    • ** Unlock all CELO (since isVoting check will incorrect return false now)
    • ** Wait 3 days
    • ** Transfer CELO to a fresh new address, lock it, and vote for Prop2 since it is still in the referendum stage.
  • Mitigation is to ensure that approval is made only to proposals whose referendum phase is either non-overlapping or end time more than 3 days apart. Mitigation is subject to actions of a quorum of governance approvers.


Previous similar incidents


Things that went well

  • Community disclosure process
  • Support of governance approvers meant that no exploit was possible between disclosure and fix
  • Determined that the risk was low quickly
  • Prepared a unit test and patch quickly
  • Open channel to OpenZeppelin allowed rapid audit
  • Regular, automated release process meant it was low overhead to include fix in subsequent Core Contracts Release 2

Things that didn’t go well

  • Did not have a test case covering this specific case
  • Initial PR reviews and audit did not catch this issue

Actions [Proposed]

Issues Created/Suggested

  • Examine unit test coverage of governance contract more closely
  • Allow verification tooling to apply to private branches in case a hotfix is needed

Run book Changes


Tactical Tensions


Governance Tensions