#43014 [BC-Critical] finite Deadlock of Transactions (No Automatic Timeout + Sequential Execution) on multisig implementation
Submitted on Mar 31st 2025 at 16:23:22 UTC by @jovi for Attackathon | Movement Labs
Report ID: #43014
Report Type: Blockchain/DLT
Report severity: Critical
Target: https://github.com/immunefi-team/attackathon-movement-aptos-core/tree/main
Impacts:
Direct loss of funds
Permanent freezing of funds (fix requires hardfork)
Multisig owner-induced deadlock (escalation of privilege)
Description
Summary A single proposal in the multisig contract can become permanently stuck in “pending” status if it never meets the threshold for either approval or rejection. Because each proposal must be resolved in strict sequence order, all future proposals are indefinitely blocked, effectively locking any funds governed by the multisig. Contrary to the assumption that this requires “privileged access,” in this design the multisig owner role is held by normal users, not administrators—any unresponsive or uncooperative user can freeze the contract.
Vulnerability Details
Location
aptos-framework/sources/multisig_account.move
Description
Sequential Execution Constraint The contract enforces strict sequential processing of proposals. Specifically:
public fun can_be_executed(multisig_account: address, sequence_number: u64): bool acquires MultisigAccount { ... sequence_number == last_resolved_sequence_number(multisig_account) + 1 && num_approvals >= num_signatures_required // ... } public fun can_be_rejected(multisig_account: address, sequence_number: u64): bool acquires MultisigAccount { ... sequence_number == last_resolved_sequence_number(multisig_account) + 1 && num_rejections >= num_signatures_required // ... }
No proposal with
sequence_number = X+1
can be acted upon until proposalX
is fully approved or rejected.No Timeout or Override Because there is no time-based fallback mechanism (e.g., “auto-reject after 7 days”) or override function (e.g., a forced skip), a proposal
X
that never achieves enough approvals or rejections gets stuck in a permanent “limbo.”As a result,
last_resolved_sequence_number
never advances, preventing all future proposals from proceeding.
Owners Are Regular Users, Not Admins Unlike systems where a privileged admin could step in, any participant in the multisig—simply by refusing to vote—can block all future transactions. This is a fundamental design flaw, granting ordinary users disproportionate power to freeze funds indefinitely.
Why This Is a Problem
Permanent Freeze of Funds: If enough owners refuse to vote or go offline, the transaction never reaches a final decision.
No Built-In Remedy: If the code is non-upgradable, a full chain-level intervention may be the only fix.
Defeats Multisig Purpose: By design, a multisig is meant to handle multi-party consensus; deadlocks undermine that purpose entirely - as sometimes it is expected that not all members will be available.
Impact
Permanent Freeze of Funds: All subsequent proposals and, therefore, all future actions on the multisig are blocked.
Wider Ecosystem Risk: Since this multisig is a standard, permissionless tool, anyone deploying it is at risk of having their funds locked by some of the owners of multisig instances.
No Privilege Required: A single regular owner’s silence is enough to deadlock the system - depending on the voting threshold.
Recommended Fix
Implement a Timeout Mechanism
After a set period, automatically reject any proposal that has not been fully approved.
Example approach:
if current_time - proposal_start_time > TIMEOUT { force_reject_proposal(seq); }
Add an Override Feature
Provide a method (e.g., supermajority vote) to forcibly resolve or skip a stalled proposal. This ensures a single unresponsive or hostile user cannot lock all funds indefinitely.
Proof of Concept
Deploy a 2-of-3 multisig using the above contract.
Create proposal #1 (sequence_number = 1).
Owner A votes “yes,” Owner B votes “no,” Owner C remains silent.
Approvals = 1, Rejections = 1—neither reaches the required 2.
Proposal #1 remains in “pending” status forever;
last_resolved_sequence_number
never increments.All subsequent proposals (#2, #3, etc.) are blocked, freezing the multisig permanently.
Was this helpful?