MultiSig Timelock

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

No selector or target policy allows arbitrary privileged calls

Author Revealed upon completion

Scope
src/MultiSigTimelock.sol: _executeTransaction

Root + Impact

Description

  • Normal behavior: Sensitive selectors (upgrade, ownership changes, approvals) should be allowlisted or require elevated delay.

  • Issue: Any 3-of-5 signers can execute arbitrary calldata against any address with full gas and contract balance, enabling instant infinite token approvals or proxy upgrades without on-chain policy controls.

(bool success,) = payable(txn.to).call{value: txn.value}(txn.data); // no selector guard

Risk

Likelihood:

  • Reason 1 // Calldata is often copy-pasted from scripts without deep review

  • Reason 2 // Malicious signer collusion is a primary threat model

Impact:

  • Impact 1 // Full administrative takeover of downstream systems in one execution

  • Impact 2 // Token allowances or upgrade slots irrevocably compromised

Proof of Concept

Explanation: Propose data calling upgradeTo(attackerImpl) on a proxy or setOwner(attacker) on downstream governance. With three confirmations the call executes; no policy rejects it.

// calldata crafted for upgradeTo() executes like any normal transfer

Recommended Mitigation

Explanation: Introduce allowlists/denylists per target and selector with overrideable minimum delays, and have _executeTransaction enforce them before the external call.

+ mapping(address => mapping(bytes4 => bool)) private s_allowed;
+ require(s_allowed[txn.to][bytes4(txn.data)], "selector blocked");

Status: Valid (Policy Bypass)


Support

FAQs

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

Give us feedback!