MultiSig Timelock

First Flight #55
Beginner FriendlyWallet
100 EXP
Submission Details
Impact: high
Likelihood: high

Signer Revocation Does Not Invalidate Prior Confirmations, Allowing Execution with Fewer Valid Signers

Author Revealed upon completion

Root + Impact

The MultiSigTimelock contract tracks transaction approvals using a stored confirmation counter (Transaction.confirmations) that is incremented when a signer confirms a transaction. However, when a signer is later removed via revokeSigningRole, the contract does not invalidate that signer’s prior confirmation or revalidate confirmations against the current signer set.

As a result, a transaction may be executed even though fewer than the required number of currently valid signers have approved it. This violates core multisig security assumptions and allows execution under weakened authorization guarantees.Signer Revocation Does Not Invalidate Prior Confirmations, Allowing Execution with Fewer Valid Signers

Description

  • Confirmations are tracked using a monotonically increasing counter:

    s_transactions[txnId].confirmations++;

  • When a signer is revoked:

    • The signer is removed from s_isSigner and SIGNING_ROLE

    • No updates are made to:

      • Transaction.confirmations

      • s_signatures[txnId][revokedSigner]

  • Execution only checks:

    if (txn.confirmations < REQUIRED_CONFIRMATIONS) revert;

  • The contract does not verify that confirmations belong to currently authorized signers at execution time.

This allows a revoked signer’s approval to continue contributing toward the quorum requirement indefinitely.

Risk

Likelihood:

  • Signer revocation is a supported and expected administrative action. The vulnerability triggers naturally whenever a signer who has already confirmed a transaction is later removed. No malicious behavior or complex setup is required.

  • This issue breaks a fundamental multisig invariant: execution should only be possible when the required number of current signers approve a transaction. It allows execution under weakened security assumptions, potentially leading to unauthorized fund transfers or governance actions.

Impact:

  • A transaction can be executed with fewer valid signer approvals than intended, undermining the multisig’s authorization model. This may enable fund transfers or contract calls that were not approved by the required number of current signers.

    In governance-critical or custody scenarios, this represents a serious security failure and can lead to unauthorized fund movement.

Proof of Concept

The following test demonstrates that a transaction can be executed even after one of the confirming signers is revoked:

function testExecutionWithRevokedSignerApproval() public grantSigningRoles {
vm.deal(address(multiSigTimelock), 10 ether);
uint256 txnId =
multiSigTimelock.proposeTransaction(SPENDER_ONE, 1 ether, "");
// Three signers confirm
multiSigTimelock.confirmTransaction(txnId);
vm.prank(SIGNER_TWO);
multiSigTimelock.confirmTransaction(txnId);
vm.prank(SIGNER_THREE);
multiSigTimelock.confirmTransaction(txnId);
// One confirming signer is revoked
multiSigTimelock.revokeSigningRole(SIGNER_TWO);
vm.warp(block.timestamp + 2 days);
// Execution succeeds despite only 2 valid signers remaining
multiSigTimelock.executeTransaction(txnId);
assertEq(SPENDER_ONE.balance, 1 ether);
}

Recommended Mitigation

Remove the stored confirmation counter and dynamically count confirmations from currently active signers at execution time.

+ function _getValidConfirmationCount(uint256 txnId)
+ internal
+ view
+ returns (uint256 count)
+ {
+ for (uint256 i = 0; i < s_signerCount; i++) {
+ address signer = s_signers[i];
+ if (s_signatures[txnId][signer]) {
+ count++;
+ }
+ }
+}

Use this value when validating execution.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!