Oracle quorum can fall below a majority of committee members
contracts/oracle/AccountingOracle.sol:L94–L131 (setQuorum, removeMember, _submitReport)
- Likelihood
- Medium
- Impact
- High
- Weakness
- CWE-285
- Description
Each report is finalised when
quorummembers have submitted the same report hash for a frame.setQuorum(q)only requiresq > 0, andremoveMember()decrements the member count without re-checking the quorum. With seven members and a quorum of four, removing three members leaves four members and a quorum of four, which is fine; but a latersetQuorum(2)or an initial misconfiguration lets two members finalise reports alone.Reports drive
totalPooledEther, so a finalised false report changes every hETH balance. The pool applies sanity bounds to the implied annual rate, but the bounds were generous (an annualised 27% positive rebase) and a negative report is bounded only by the slashing limit.The defect is that the safety of the oracle depends on an invariant (quorum is a strict majority) that the contract does not enforce.
- Preconditions
- A quorum set below a strict majority, through configuration error or a compromised manager role.
- Collusion or compromise of that many committee members.
- Exploit scenario
- 1.The committee manager role (a 3-of-5 multisig) lowers the quorum to two during a member rotation and forgets to raise it.
- 2.Two colluding members submit a report overstating validator balances within the 27% annualised bound.
- 3.The pool applies a positive rebase. The members, who hold hETH, request withdrawals at the inflated rate before the next honest report corrects it.
- Impact
Value transferred from stakers to the colluding members, bounded by the rebase limit per report; repeated reports compound the loss.
- Recommendation
Enforce
quorum ≥ members / 2 + 1insetQuorum,addMemberandremoveMember, and tighten the positive rebase bound to a realistic annualised rate.- Fix
- contracts/oracle/AccountingOracle.sol (remediation commit)diff
function setQuorum(uint256 q) external onlyRole(MANAGE_MEMBERS_ROLE) {- require(q > 0, "quorum");+ _requireMajority(q, members.length); quorum = q; } function removeMember(address m) external onlyRole(MANAGE_MEMBERS_ROLE) { _remove(m);+ if (quorum > members.length) quorum = members.length;+ _requireMajority(quorum, members.length); }++ function _requireMajority(uint256 q, uint256 n) internal pure {+ if (q < n / 2 + 1 || q > n) revert QuorumNotMajority(q, n);+ } - Resolution
Fixed. The quorum is validated on every membership change, and the positive rebase bound was lowered to an annualised 10%.
