Security·6 min का पठन·Solingo द्वारा

Integer Overflow और Underflow — SafeMath से पहले और बाद

Solidity 0.8 से पहले, integer overflow/underflow major vulnerabilities थे। जानें कैसे ये bugs काम करते थे, कैसे रोके जाते थे, और क्यों Solidity 0.8+ में built-in protection है।

# Integer Overflow और Underflow — SafeMath से पहले और बाद

Integer overflow और underflow vulnerabilities ने early smart contracts में havoc मचाया था। Solidity 0.8.0 (February 2021) ने built-in overflow checking introduce की, जो इन bugs को largely eliminate कर देती है — लेकिन legacy code और unchecked blocks अभी भी risks pose करते हैं।

इस guide में, हम explore करेंगे कि ये vulnerabilities कैसे काम करती थीं, SafeMath pattern, और modern Solidity में best practices।

Integer Overflow/Underflow क्या है?

Overflow तब होती है जब एक arithmetic operation का result उस type के maximum value से बड़ा होता है।

Underflow तब होती है जब result minimum value से छोटा होता है।

Example: uint8 (0 से 255)

uint8 x = 255;

x = x + 1; // Overflow: 255 + 1 = 0 (wraps around)

uint8 y = 0;

y = y - 1; // Underflow: 0 - 1 = 255 (wraps around)

Solidity 0.7 और पहले में, यह silently होता था — कोई error नहीं, बस incorrect values।

Pre-0.8 Vulnerability Example

// Solidity 0.7 (VULNERABLE)

pragma solidity ^0.7.0;

contract VulnerableToken {

mapping(address => uint256) public balances;

function transfer(address to, uint256 amount) external {

// No overflow check!

require(balances[msg.sender] >= amount, "Insufficient balance");

balances[msg.sender] -= amount; // Could underflow

balances[to] += amount; // Could overflow

}

function batchTransfer(address[] calldata recipients, uint256 amount) external {

// CRITICAL VULNERABILITY

uint256 totalAmount = recipients.length * amount; // Overflow possible!

require(balances[msg.sender] >= totalAmount, "Insufficient balance");

for (uint i = 0; i < recipients.length; i++) {

balances[recipients[i]] += amount;

}

balances[msg.sender] -= totalAmount;

}

}

Attack Scenario

// Attacker calls:

address[] memory recipients = new address[](2);

recipients[0] = attacker1;

recipients[1] = attacker2;

// amount = 2^255 (very large number)

// recipients.length = 2

// totalAmount = 2 * 2^255 = 2^256 = 0 (OVERFLOW!)

token.batchTransfer(recipients, 2**255);

Result:

  • totalAmount = 0 (overflow)
  • require(balances[msg.sender] >= 0) passes
  • दोनों recipients को 2^255 tokens मिलते हैं
  • Attacker का balance unchanged (0 deducted)
  • Unlimited tokens minted!

यह BeautyChain (BEC) token hack में exactly यही हुआ था (April 2018) — attacker ने unlimited tokens mint किए, token worthless हो गया।

SafeMath Library (Pre-0.8 Solution)

OpenZeppelin का SafeMath library हर operation पर overflow/underflow check करता था।

// Solidity 0.7

pragma solidity ^0.7.0;

library SafeMath {

function add(uint256 a, uint256 b) internal pure returns (uint256) {

uint256 c = a + b;

require(c >= a, "SafeMath: addition overflow");

return c;

}

function sub(uint256 a, uint256 b) internal pure returns (uint256) {

require(b <= a, "SafeMath: subtraction underflow");

return a - b;

}

function mul(uint256 a, uint256 b) internal pure returns (uint256) {

if (a == 0) return 0;

uint256 c = a * b;

require(c / a == b, "SafeMath: multiplication overflow");

return c;

}

function div(uint256 a, uint256 b) internal pure returns (uint256) {

require(b > 0, "SafeMath: division by zero");

return a / b;

}

}

Using SafeMath

pragma solidity ^0.7.0;

import "@openzeppelin/contracts/math/SafeMath.sol";

contract SafeToken {

using SafeMath for uint256;

mapping(address => uint256) public balances;

function transfer(address to, uint256 amount) external {

require(balances[msg.sender] >= amount, "Insufficient balance");

balances[msg.sender] = balances[msg.sender].sub(amount); // Safe

balances[to] = balances[to].add(amount); // Safe

}

function batchTransfer(address[] calldata recipients, uint256 amount) external {

uint256 totalAmount = recipients.length.mul(amount); // Reverts on overflow

require(balances[msg.sender] >= totalAmount);

for (uint i = 0; i < recipients.length; i++) {

balances[recipients[i]] = balances[recipients[i].add(amount);

}

balances[msg.sender] = balances[msg.sender].sub(totalAmount);

}

}

अब overflow/underflow होने पर transaction revert होगा।

Solidity 0.8+ Built-In Protection

Solidity 0.8.0 से, overflow/underflow checks automatically enforce होते हैं।

// Solidity 0.8+

pragma solidity ^0.8.0;

contract ModernToken {

mapping(address => uint256) public balances;

function transfer(address to, uint256 amount) external {

require(balances[msg.sender] >= amount);

balances[msg.sender] -= amount; // Reverts on underflow

balances[to] += amount; // Reverts on overflow

}

function batchTransfer(address[] calldata recipients, uint256 amount) external {

uint256 totalAmount = recipients.length * amount; // Reverts on overflow

require(balances[msg.sender] >= totalAmount);

for (uint i = 0; i < recipients.length; i++) {

balances[recipients[i]] += amount;

}

balances[msg.sender] -= totalAmount;

}

}

No SafeMath needed! Compiler automatically checks insert करता है।

unchecked Block — Gas Optimization

कभी-कभी आप deliberately overflow checking disable करना चाहते हैं (gas save करने के लिए)।

pragma solidity ^0.8.0;

contract UncheckedExample {

function safeIncrement(uint256 x) external pure returns (uint256) {

// Overflow check included (costs more gas)

return x + 1;

}

function unsafeIncrement(uint256 x) external pure returns (uint256) {

// No overflow check (saves gas)

unchecked {

return x + 1;

}

}

function loopWithUnchecked() external pure {

uint256 sum = 0;

// Loop counters never overflow in practice

for (uint256 i = 0; i < 100; unchecked { i++ }) {

sum += i;

}

}

}

When to Use unchecked

Safe use cases:

  • Loop counters (जब आप जानते हैं कि वे overflow नहीं होंगे)
  • Calculations जहां overflow mathematically impossible है
  • Gas-critical code जहां आपने manually verify किया है

Never use for:

  • User inputs
  • Balances/amounts
  • कोई भी calculation जो overflow हो सकता है

Common Patterns और Pitfalls

Pattern 1: Timestamp Overflow (Less Common Now)

// Old code (Solidity 0.7)

uint256 deadline = block.timestamp + 30 days; // Could overflow (theoretically)

// Modern (Solidity 0.8+)

uint256 deadline = block.timestamp + 30 days; // Reverts on overflow

block.timestamp एक uint256 है, तो overflow लगभग impossible है (universe का end से पहले नहीं होगा), लेकिन 0.8+ में यह automatically protected है।

Pattern 2: Safe Subtraction Before Division

// VULNERABLE (0.7)

function calculateFee(uint256 amount) external pure returns (uint256) {

uint256 feeRate = 100; // 1%

return (amount * feeRate) / 10000; // Multiplication could overflow

}

// SAFE (0.8+)

function calculateFee(uint256 amount) external pure returns (uint256) {

uint256 feeRate = 100;

return (amount * feeRate) / 10000; // Automatically reverts on overflow

}

Pattern 3: Casting Downcasts

pragma solidity ^0.8.0;

function downcast(uint256 x) external pure returns (uint128) {

// This does NOT automatically revert on overflow!

return uint128(x); // Truncates higher bits

}

function safeDowncast(uint256 x) external pure returns (uint128) {

require(x <= type(uint128).max, "Downcast overflow");

return uint128(x);

}

⚠️ Important: Type casting does not trigger overflow checks — manually verify करें!

Testing for Overflow/Underflow

Foundry Test Example

pragma solidity ^0.8.0;

import "forge-std/Test.sol";

contract OverflowTest is Test {

function testBuiltInOverflowProtection() public {

uint256 max = type(uint256).max;

// This should revert

vm.expectRevert();

uint256 overflow = max + 1;

}

function testBuiltInUnderflowProtection() public {

uint256 zero = 0;

// This should revert

vm.expectRevert();

uint256 underflow = zero - 1;

}

function testUncheckedOverflow() public {

uint256 max = type(uint256).max;

unchecked {

uint256 wrapped = max + 1;

assertEq(wrapped, 0); // Wraps to 0

}

}

}

Migration Guide: 0.7 → 0.8

// OLD (Solidity 0.7)

pragma solidity ^0.7.0;

import "@openzeppelin/contracts/math/SafeMath.sol";

contract OldToken {

using SafeMath for uint256;

mapping(address => uint256) public balances;

function transfer(address to, uint256 amount) external {

balances[msg.sender] = balances[msg.sender].sub(amount);

balances[to] = balances[to].add(amount);

}

}

// NEW (Solidity 0.8+)

pragma solidity ^0.8.0;

contract NewToken {

mapping(address => uint256) public balances;

function transfer(address to, uint256 amount) external {

balances[msg.sender] -= amount; // No SafeMath needed

balances[to] += amount;

}

}

Breaking Changes in 0.8

  • Arithmetic operations revert on overflow/underflow
  • ** (exponentiation) भी checked है
  • Type conversions require explicit handling

Best Practices Checklist

  • ✅ Solidity 0.8+ use करें built-in overflow protection के लिए
  • unchecked केवल तभी use करें जब mathematically safe हो
  • ✅ Type casting को manually validate करें
  • ✅ Legacy code को 0.8+ में migrate करें
  • ✅ हमेशा test करें edge cases (max values, zero, etc.)
  • ✅ Fuzzing tools use करें (Echidna, Foundry) overflow scenarios discover करने के लिए
  • ✅ Audit reports में overflow/underflow coverage verify करें

Real-World Examples

1. BeautyChain (BEC) Token — April 2018

  • batchTransfer में overflow vulnerability
  • Attacker ने unlimited tokens mint किए
  • Token value → 0
  • Loss: Company destroyed

2. SmartMesh (SMT) Token — April 2018

  • Similar batchTransfer overflow
  • Immediately after BEC incident
  • Token trading suspended

3. PoWHC (Ponzi Scheme) — 2018

  • Underflow bug में sell() function
  • Attacker ne 866 ETH drain किए
  • Contract abandoned

Common thread: सभी Solidity 0.7 या earlier में थे, SafeMath use नहीं किया गया था।

Conclusion

Integer overflow/underflow vulnerabilities ने early DeFi को plague किया था, लेकिन Solidity 0.8 ने game change कर दिया।

Key takeaways:

  • Solidity 0.8+ में automatic overflow/underflow protection है
  • SafeMath अब redundant है (0.8+ में)
  • unchecked blocks carefully use करें
  • Type casting manually validate करें
  • Legacy contracts को migrate करें या carefully audit करें
  • Always test edge cases

Solingo पर, आप real historical vulnerabilities के साथ integer overflow bugs को identify और fix करना सीखेंगे। Solidity 0.7 और 0.8+ दोनों में practice करें।

अभी शुरू करें: solingo.io/security/integer-bugs

Practice में लगाने के लिए तैयार हैं?

Solingo पर interactive exercises के साथ इन concepts को apply करें।

मुफ्त में शुरू करें