Token-0x

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

Integer Overflow in `_mint` Function Due to Unchecked Assembly Arithmetic

Author Revealed upon completion

Root + Impact

The _mint function uses Yul assembly for arithmetic operations which do NOT have Solidity 0.8+'s built-in overflow protection. This can cause silent integer overflow in totalSupply and account balances.

Description

  • Solidity 0.8+ automatically reverts on integer overflow/underflow. However, inline assembly (Yul) does NOT have this protection - arithmetic operations wrap around silently.

  • The _mint function uses add() for both totalSupply and accountBalance without overflow checks.

function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
// ... validation ...
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
@> sstore(supplySlot, add(supply, value)) // No overflow check!
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
@> sstore(accountBalanceSlot, add(accountBalance, value)) // No overflow check!
}
}

Risk

Likelihood:

  • Requires minting large amounts close to type(uint256).max

  • Dependent on how implementing contracts expose mint functionality

  • Could happen accidentally with incorrect mint amounts

Impact:

  • totalSupply can wrap around to small values while actual circulating supply is huge

  • Individual balances can overflow and reset to near-zero

  • Complete breakdown of token accounting

Proof of Concept

Here is the PoC to mint tokens

function test_mintOverflow() public {
address account1 = makeAddr("account1");
address account2 = makeAddr("account2");
// Mint max uint256 - 1 to account1
token.mint(account1, type(uint256).max - 1);
assertEq(token.totalSupply(), type(uint256).max - 1);
// Mint 2 more tokens - causes overflow
token.mint(account2, 2);
// totalSupply has overflowed to 0
assertEq(token.totalSupply(), 0);
// But there are still tokens in circulation!
assertEq(token.balanceOf(account1), type(uint256).max - 1);
assertEq(token.balanceOf(account2), 2);
}

Recommended Mitigation

Add overflow checks in assembly before performing the addition.

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
let supply := sload(supplySlot)
+ let newSupply := add(supply, value)
+ // Check for overflow: if newSupply < supply, overflow occurred
+ if lt(newSupply, supply) {
+ revert(0, 0)
+ }
- sstore(supplySlot, add(supply, value))
+ sstore(supplySlot, newSupply)
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
+ let newBalance := add(accountBalance, value)
+ // Check for overflow
+ if lt(newBalance, accountBalance) {
+ revert(0, 0)
+ }
- sstore(accountBalanceSlot, add(accountBalance, value))
+ sstore(accountBalanceSlot, newBalance)
}
}

Support

FAQs

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

Give us feedback!