Skip to content

CWE-191: Integer Underflow (Wrap or Wraparound)

Overview

Integer underflow happens when a subtraction (or other arithmetic) produces a result below the minimum value the integer type can represent. An unsigned type wraps to a very large positive value instead of going negative. A signed type has room to hold a negative result, so the far more common outcome there is a plainly negative number that never wrapped at all - the type's minimum is only crossed when both operands are already near it. Where it is crossed, Java and C# define the wrap as landing on a large positive value, while in C and C++ signed overflow is undefined behaviour, so the compiler is entitled to assume it cannot happen and may delete a check written to catch it. The bug typically starts with an unvalidated "remaining space," "bytes left," or "count minus offset" calculation where the subtrahend can legitimately exceed the minuend.

Relationship to Other CWEs

  • CWE-191 (this page) - a subtraction whose result falls below the type's minimum and wraps
  • CWE-682 (Incorrect Calculation) - the pillar this Base-level page sits under. No page here
  • CWE-189 (Numeric Errors) - the MITRE category this page belongs to. No page here
  • CWE-190 (Integer Overflow) - the mirror image: overflow when addition or multiplication exceeds the maximum, underflow when subtraction goes below the minimum. Findings on wraparound arithmetic can land on either page depending on the operator involved
  • CWE-192 (Integer Coercion Error) - distinct: this page is an arithmetic result going out of range, where CWE-192 is a type conversion silently misrepresenting a value that was never out of range to begin with

Underflow is not confined to unmanaged code: Java and C# subtract in the same fixed-width types and neither signals a shortfall by default, so the wrong number is produced there too and only the consequence differs (see Risk).

Risk

High: In C/C++, an unsigned underflow used as a buffer size or copy length drives out-of-bounds writes, oversized allocations, or heap corruption. Which shape you get follows the type, not the language. An unsigned type - C's size_t, C#'s uint/ulong - turns a small negative-intent value into an enormous positive one, which no bounds check phrased as "is it big enough" will reject. A signed type produces a negative one, and in Java and C# that is caught at the point of use rather than corrupting memory. The two report it differently, which matters when you are searching logs for it. Measured on JDK 26, new byte[n] throws NegativeArraySizeException; on .NET 10 the same allocation throws System.OverflowException, and Array.CreateInstance with a negative length throws ArgumentOutOfRangeException instead. A negative index throws ArrayIndexOutOfBoundsException and IndexOutOfRangeException respectively rather than reading past the end. That is a denial of service rather than memory corruption, but the wrong number can still pass a security check it should have failed or corrupt business logic before it ever reaches an allocation.

Remediation Steps

Core Principle: Compare the operands before subtracting - minuend >= subtrahend - rather than judging the result. On an unsigned type the result carries no evidence that it wrapped, so a check made afterwards has nothing left to test; on a signed type a negative result is at least visible, but only where every path that consumes it actually looks.

Trace the Data Path

  • Source: Any two values fed into a subtraction (or reverse iteration) where at least one is influenced by user input, a file, or a network message - buffer lengths, offsets, counts, remaining-space calculations
  • Sink: The subtraction itself, and whatever consumes its result - a malloc/allocation size, a memcpy length, a loop bound, an array index
  • Missing Controls: No check that the minuend is at least as large as the subtrahend before subtracting; loop counters that assume decrementing an unsigned value never reaches below zero

Validate Before Subtracting (Primary Defense)

  • Check minuend >= subtrahend before subtracting; reject or clamp instead of computing a value that has already wrapped
  • Prefer a language or library's checked-arithmetic primitives (Math.subtractExact in Java, checked { } blocks in C#, compiler builtins such as __builtin_sub_overflow in C/C++) so an underflow throws or is caught rather than silently wrapping
  • Reserve unsigned types for values that are logically never negative and where you also enforce that invariant at every arithmetic site; when in doubt, use a signed type and check for negative results explicitly

Handle Reverse Iteration (Defense in Depth)

  • Never write for (i = count - 1; i >= 0; i--) with an unsigned loop counter - when count is 0, count - 1 wraps to the type's maximum value and the >= 0 condition is always true, producing an out-of-bounds access or an effectively infinite loop
  • Prefer forward iteration, or a reverse pattern that decrements inside the loop condition so the comparison happens before the value can wrap

Test with Boundary Values

  • subtrahend == minuend and subtrahend == minuend + 1 (the exact wraparound boundary)
  • A count of 0 feeding a reverse loop
  • Maximum representable values for the type in use
  • Re-scan with the security scanner to confirm the finding is resolved

Common Vulnerable Patterns

// VULNERABLE - pseudo-code
function copy_into_buffer(dest_size, src_size):
    space_left = dest_size - src_size   // unsigned subtraction, no check
    // if src_size > dest_size, space_left wraps to a huge value
    if space_left > 0:
        copy(src_size, into=dest)        // writes past the end of dest

Why this is vulnerable: space_left is meant to be a remaining capacity, and an unsigned type cannot hold a negative one. When the subtraction goes below zero the type does not report a problem; it reports the largest number it can hold. The guard space_left > 0 then reads as "there is room", which is the exact opposite of the truth, and the more the source overruns the destination the more room the check believes there is.

The inversion is what makes this different from ordinary overflow. A wrapped addition usually produces a small or negative result that a later sanity check might catch by accident; a wrapped subtraction produces an enormous one, which passes every check written as "is it big enough". The comparison has to be made between the operands, before the arithmetic runs - if (src_size > dest_size) reject - because the subtraction is where the information is lost.

Secure Patterns

// SECURE - pseudo-code
function copy_into_buffer(dest_size, src_size):
    if src_size > dest_size:
        reject("source larger than destination")
    copy(src_size, into=dest)

Why this works: The check happens before the subtraction is ever computed, so there is no intermediate wrapped value for the rest of the function to act on. Comparing the two operands directly, rather than inspecting their difference afterward, is safe regardless of whether the type is signed or unsigned.

Common Pitfalls

  • Checking the subtraction result instead of the operands: Testing if (result > 0) after computing a - b is too late for an unsigned type - the wrapped value is itself a large positive number, so the check passes when it should have failed.
  • Assuming a signed cast fixes an unsigned underflow: Casting a wrapped size_t to a signed type after the fact doesn't recover the original intent; the wraparound already happened, and the cast just reinterprets the same wrong bits.
  • Trusting a length or count that travels with the data it describes: A "bytes remaining" or count field taken from a file or network message is still attacker-controlled input, not a validated bound - it needs the same minuend-before-subtrahend check as any other untrusted value.
  • Relying on managed-language bounds checking as the whole fix: A Java or C# array access that throws on a negative or huge size prevents memory corruption, but an unhandled exception from that throw is itself a denial-of-service if the caller doesn't catch and reject the bad input gracefully.

Additional Resources