Every integer type in Solidity has a fixed size. A uint8, for example, can hold values from 0 to 255. If you add 1 to 255, it wraps back to 0 — this is overflow. Subtract 1 from 0 and you get 255 — this is underflow. Before Solidity 0.8.0, these operations happened silently with no error.
Attackers exploit overflow and underflow to bypass balance checks, manipulate token supplies, or unlock hidden paths in contract logic.
// VULNERABLE — uint8 overflows at 255
function transfer(address to, uint8 amount) public {
balances[msg.sender] -= amount; // underflow if amount > balance
balances[to] += amount;
}
// Attacker passes amount = 256, and balances[msg.sender] wraps to a huge number
// SAFE — Solidity 0.8+ reverts on overflow/underflow by default
// Or use OpenZeppelin SafeMath for older compiler versions:
using SafeMath for uint256;
balances[msg.sender] = balances[msg.sender].sub(amount);
balances[to] = balances[to].add(amount);
SafeMath.unchecked { } blocks only when you are certain no overflow can occur (e.g., loop counters in known-bounded loops).