Reproduced Exploit

FairSide — anyone can inflate FSDVesting and claim the balance

updateVestedTokens(address _user, uint256 _amount) checks _user == beneficiary. _user is chosen by the caller, and the caller is not required to be the FSD token. A stranger passes the beneficiary and 1900.

Nov 2021•Ethereum•access-control•3 min read

Chain

Ethereum

Category

access-control

Date

Nov 2021

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 42324-h-01-anyone-can-arbitrarily-call-fsdvestingupdatevestedtoken. Standalone Foundry PoC and full write-up: 42324-h-01-anyone-can-arbitrarily-call-fsdvestingupdatevestedtoken_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/access-control/missing-auth · vuln/logic/reward-calculation

Key info#

ProtocolFairSide
FindingCode4rena, November 2021, AuditVault 42324
Vulnerable codeFSDVesting.updateVestedTokens
HarmThe cliff pays 100 instead of 5. The vesting contract is emptied.
Chainlocal synthetic (audited Ethereum contracts)

TL;DR#

updateVestedTokens(address _user, uint256 _amount) checks _user == beneficiary. _user is chosen by the caller, and the caller is not required to be the FSD token. A stranger passes the beneficiary and 1900.

The schedule unlocks 5 percent at the cliff and the rest linearly over 18 months. On an amount of 100 the cliff pays 5. After the amount is 2000, 5 percent is 100, which is every token the contract holds. The claim transfers 100. The fair claim left 95 locked.

The vulnerable code#

SOLIDITY
require(_user == beneficiary, "FSDVesting::updateVestedTokens: Only beneficiary can update the vested amount");
require(_amount != 0, "FSDVesting::updateVestedTokens: Amount cannot be zero");
// @> VULN: no onlyFsd, and _user is an argument, so any caller can inflate amount.
amount = amount + _amount;

The fix is the onlyFsd gate. A call from anyone else reverts and does not change amount.

Root cause#

The beneficiary check does not bind msg.sender. Each user has their own vesting contract, and the intended writer is FSD. Without that restriction the stored amount can be raised above the tokens actually deposited, and the cliff percentage then covers the whole balance.

Preconditions#

The contract was created with amount 100 and holds 100 tokens. The cliff has passed and the 18 month linear period has not meaningfully started, so the linear term is 0. The caller is not FSD. One transaction.

Attack walkthrough#

  1. calculateVestingClaim is 5.
  2. The transaction sender is not the beneficiary.
  3. A stranger calls updateVestedTokens(beneficiary, 1900). amount becomes 2000.
  4. claimVestedTokens pays 100, the token balance.
  5. The beneficiary holds 100. The vesting contract holds 0.
  6. updateGuarded from that same non-FSD caller reverts.

Control, with no update, claims 5 and leaves 95 in the contract.

Diagrams#

flowchart TD A["Vesting holds 100 and the cliff pays 5"] --> B["Stranger calls updateVestedTokens"] B --> C["VULN: amount becomes 2000"] C --> D["Five percent of 2000 is 100"] D --> E["Claim transfers the whole balance"] E --> F["Fair claim would have left 95 locked"]

Impact#

95 tokens that should still be vesting are withdrawn at the cliff. The contract that was supposed to hold them is empty.

Remediation#

Restrict updateVestedTokens to the FSD token, and stop taking the beneficiary as a caller-supplied argument.

How to reproduce#

BASH
cd 42324-h-01-anyone-can-arbitrarily-call-fsdvestingupdatevestedtoken_exp
forge test -vv

Sources#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.