Token-0x

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

Missing Overflow Protection in Internal Mint Allows Arbitrary Token Inflation

Author Revealed upon completion

[H-04] Missing Overflow Protection in Internal Mint Allows Arbitrary Token Inflation

Description

The ERC20 core implementation contains a critical arithmetic safety flaw in the token minting logic.
The _mint() function inside ERC20Internals.sol performs raw addition in inline assembly without any overflow protection.

If the mint amount is large enough, adding to the current total supply or user balance can overflow, wrapping around to a small number and effectively resetting balances or supply. This permanently corrupts both the global supply accounting and individual balances.

Although _mint() is marked internal, any child contract can expose this functionality through a public or external wrapper, intentionally or accidentally, enabling arbitrary token creation.

// src/helpers/ERC20Internals.sol
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0xec442f05))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
// @audit No overflow check here.
let supply := sload(supplySlot)
sstore(supplySlot, add(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
// @audit No overflow check here.
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, add(accountBalance, value))
}
}

Risk

Likelihood: High

  • The contract is designed for inheritance.

  • Arithmetic is performed unchecked in assembly.

  • No validation exists for total supply or account balance overflow.

Impact: Critical

  • Arbitrary inflation of user balances.

  • Total supply overflow and corruption.

  • Broken token economics and permanent protocol integrity loss.

Proof of Concept

  • The following PoC demonstrates that minting via a malicious child contract can overflow balances or supply.

contract MaliciousToken is ERC20 {
constructor() ERC20("Evil", "EVL") {}
function mintAnyone(address to, uint256 amount) public {
_mint(to, amount);
}
function balanceOf(address user) public view override returns (uint256) {
return _balanceOf(user);
}
function totalSupply() public view override returns (uint256) {
return totalSupply_();
}
}
function test_PoC_MintOverflow() public {
address attacker = makeAddr("attacker");
MaliciousToken evil = new MaliciousToken();
// Step 1: Push totalSupply near uint256 max
uint256 nearMax = type(uint256).max - 10 ether;
evil.mintAnyone(attacker, nearMax);
assertEq(evil.totalSupply(), nearMax);
// Step 2: Mint more to trigger overflow
evil.mintAnyone(attacker, 100 ether);
uint256 supplyAfter = evil.totalSupply();
uint256 balanceAfter = evil.balanceOf(attacker);
// Supply should have wrapped around to a small number
assertLt(supplyAfter, 1000 ether);
assertLt(balanceAfter, 1000 ether);
}
forge test --match-test test_PoC_MintOverflow -vvv
  • The test passes, confirming that unchecked addition causes silent overflow and state corruption.

Recommended Mitigation

  • Add explicit overflow protection before performing arithmetic inside _mint():

function _mint(address account, uint256 value) internal {
+ require(value > 0, "Invalid amount");
+ require(totalSupply + value >= totalSupply, "Overflow");
+ require(balanceOf(account) + value >= balanceOf(account), "Overflow");
let supply := sload(supplySlot)
sstore(supplySlot, add(supply, value))
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, add(accountBalance, value))
}

Support

FAQs

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

Give us feedback!