Token-0x

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

Critical Integer Underflow in _burn Function

Author Revealed upon completion

Root + Impact

Description

  • The _burn function performs unchecked subtraction in assembly without verifying that the account has sufficient balance or that the total supply is sufficient. The function uses 'sub' opcode which will underflow if the value being burned exceeds the account balance or total supply. This could allow an attacker to burn more tokens than they own, causing their balance to underflow to a massive number (close to MAX_UINT256), effectively creating tokens out of thin air.

function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
// ...
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, sub(accountBalance, value)) // UNDERFLOW: No balance check
}
}

Risk

Likelihood:

  • High

Impact:

  • An attacker could burn more tokens than they possess, causing their balance to underflow to MAX_UINT256 - (burnAmount - actualBalance). This effectively allows unlimited token creation without proper minting, completely breaking the token economics and allowing theft of value.

Proof of Concept

function test_overflow() public{
uint256 amount = type(uint256).max;
address account = makeAddr("account");
token.mint(account, amount);
uint256 balance = token.balanceOf(account);
assertEq(balance, amount);
assertEq(token.totalSupply(), amount);
token.mint(account, 1);
balance = token.balanceOf(account);
assertEq(balance, 0);
assertEq(token.totalSupply(), 0);
}

Recommended Mitigation

Use validation before subtracting

- remove this code
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))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
//underflow
sstore(accountBalanceSlot, sub(accountBalance, value))
}
}
+ add this code
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
// Check account balance first
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
// Revert if insufficient balance
if lt(accountBalance, value) {
mstore(0x00, shl(224, 0xe450d38c))
mstore(add(0x00, 4), account)
mstore(add(0x00, 0x24), accountBalance)
mstore(add(0x00, 0x44), value)
revert(0x00, 0x64)
}
let supply := sload(supplySlot)
sstore(supplySlot, sub(supply, value))
sstore(accountBalanceSlot, sub(accountBalance, value))
}
}

Support

FAQs

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

Give us feedback!