57615 sc medium permanent freezing of user assets in staking sol

Submitted on Oct 27th 2025 at 16:03:34 UTC by @DoD4uFN for Audit Comp | Belongarrow-up-right

  • Report ID: #57615

  • Report Type: Smart Contract

  • Report severity: Medium

  • Target: https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/periphery/Staking.sol

  • Impacts:

    • Permanent freezing of funds

Description

Brief / Intro

A malicious actor can permanently freeze other users’ staked assets in the Staking contract by exploiting unbounded iteration in withdrawal logic. The vulnerability occurs because stakes are stored in an ever-growing array and iterated over on every withdraw and emergencyWithdraw. By strategically inserting thousands of zero-value stakes, an attacker can cause all withdrawal-related transactions for a victim to run out of gas, resulting in permanently frozen funds for affected users.

Vulnerability Details

The Staking contract maintains a list of Stake structs per user in a dynamic array:

mapping(address staker => Stake[]) public stakes;

Each time a user deposits, a new Stake entry is appended:

function _deposit(address by, address to, uint256 assets, uint256 shares) internal override {
    super._deposit(by, to, assets, shares);
    stakes[to].push(Stake({shares: shares, timestamp: block.timestamp}));
}

During withdrawals, the _consumeUnlockedSharesOrRevert and _removeAnySharesFor functions iterate over the entire stakes[staker] array, performing swap-and-pop operations to consume unlocked stakes:

Since there are no bounds or gas-efficiency constraints on the array length, the time complexity grows linearly with the number of stakes. A malicious user can exploit this by depositing a large number of zero-value stakes into another user’s stakes array (possible in the current implementation).

When the victim later attempts to withdraw, the function will attempt to iterate through thousands of stakes, ultimately exceeding the block gas limit and reverting. This creates a permanent denial of service: the victim’s withdrawal (and emergencyWithdraw) will consistently revert, effectively freezing their assets indefinitely.

Exploitability is worsened because the attacker spends no tokens to bloat another user’s stakes array with many entries of zero value.

Exploitation Steps

1

Step

Attacker deposits 0 tokens at least once before the victim's legitimate deposit:

2

Step

Victim deposits tokens at some point in time:

3

Step

Attacker deposits 0 tokens multiple times using staking.deposit(0, victimAddress). Around 3,500 deposits were sufficient to exceed typical Ethereum block gas limits in testing.

4

Step

Victim calls withdraw() or emergencyWithdraw(); the loop in _consumeUnlockedSharesOrRevert runs out of gas before completing and the transaction reverts, leaving the victim unable to access their staked funds.


Impact Details

Impact: Permanent freezing of assets

An attacker can target any staker by bloating their stakes array with 0-amount deposits. Once the gas cost exceeds the block gas limit, the victim’s withdrawal and emergency withdrawal functions will always revert, regardless of the gas limit provided. In testing, approximately 3,500 zero-amount deposits were sufficient to cause a revert on Ethereum mainnet gas limits.

As a result, users’ tokens become permanently locked in the contract, and no administrative mechanism exists to clean or reset stake entries.

This impact qualifies as "Permanent freezing of user funds", which is a Critical severity according to Immunefi’s impact classification.

References

  • Staking.sol: pushing new entries to stakes https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/periphery/Staking.sol#L245

  • Staking.sol: iterating over all entries of stakes at withdraw https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/periphery/Staking.sol#L258-L290

  • Staking.sol: iterating over all entries of stakes at emergencyWithdraw https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/periphery/Staking.sol#L296-L315


Proof of Concept

Proof of Concept (coded)

Add the following PoC at test/v2/platform/staking.test.ts in Staking features.

Proof of Concept (in steps)

1

Step

Attacker creates initial zero stake before victim:

2

Step

Victim stakes normally:

3

Step

Attacker floods victim’s stakes array:

4

Step

Victim attempts to withdraw:

Was this helpful?