Skip to main content
Sigvex

Sign Extension Overflow Remediation

Validate the sign of a value before casting between signed and unsigned integer types so negative values cannot become huge positives.

Sign Extension Overflow Remediation

Overview

Casting a negative signed integer to an unsigned type produces a value near the unsigned maximum: uint256(int8(-1)) becomes 2^256 - 1. The EVM’s SIGNEXTEND fills the high bits with ones, so a small negative number turns into an enormous credit, price, or length. The fix is to check the sign before converting and handle the negative branch explicitly.

Related Detector: Sign Extension Overflow

Before (Vulnerable)

function applyDelta(int256 delta) external {
    uint256 amount = uint256(delta); // delta < 0 -> amount near 2^256
    balances[msg.sender] += amount;
}

After (Fixed)

function applyDelta(int256 delta) external {
    require(delta >= 0, "Negative delta");
    uint256 amount = uint256(delta);
    balances[msg.sender] += amount;
}

For logic that must accept negative inputs, branch on the sign and operate in the correct domain rather than reinterpreting the bit pattern:

function updatePrice(int256 priceChange) external {
    if (priceChange >= 0) {
        currentPrice = basePrice + uint256(priceChange);
    } else {
        uint256 decrease = uint256(-priceChange);
        require(basePrice >= decrease, "Price underflow");
        currentPrice = basePrice - decrease;
    }
}

Guarding the cast guarantees the unsigned value is the true magnitude, not a sign-extended wrap.

Alternative Mitigations

  • Use a signed-math library with explicit toUint256/toInt256 conversions that revert on out-of-range or negative inputs, so every conversion is checked in one audited place.
  • Keep arithmetic in the signed domain end-to-end and convert only at the boundary where a non-negative result is provable.
  • Where assembly performs SIGNEXTEND manually, add the same sign guard before the conversion.

Common Mistakes

  • Relying on Solidity 0.8 overflow checks to catch this. Those checks cover arithmetic, not the explicit uint256(signedValue) cast, which still wraps silently.
  • Comparing the converted unsigned value against a bound after the cast — by then the negative value has already become a large positive that may pass the check.
  • Negating int256 minimum (type(int256).min) before converting, which itself overflows.

References