Skip to content

CWE-190: Integer Overflow or Wraparound - Java

Overview

Java's int and long arithmetic operators (+, -, *) silently wrap on overflow with no exception and no compiler warning - the same failure mode as C, without the undefined-behavior risk. The usual consequence in Java code is a wrapped value reaching an array allocation size, an index calculation, or a security check (a session timeout, a rate limit, a permission count) rather than memory corruption, since the JVM's bounds-checked arrays turn a wrapped negative size into a caught NegativeArraySizeException. The logic error itself is no less real.

Common Vulnerable Patterns

A wrapped product used as an allocation size

// VULNERABLE - unchecked multiplication, silently wraps
public static byte[] allocateBuffer(int count, int itemSize) {
    // Attack: count = 0x40000000, itemSize = 4 -> total wraps to exactly 0
    int total = count * itemSize;
    return new byte[total];
}

Why this is vulnerable: int multiplication in Java wraps modulo 2^32 with no exception and no compiler warning, so total is not the number of bytes the caller asked for. With count = 0x40000000 and itemSize = 4 the true product is 2^32, which wraps to 0 - new byte[0] succeeds, nothing throws, and the method returns a buffer the caller believes is 4 GB. Every write into it then lands outside the array, which the JVM turns into an ArrayIndexOutOfBoundsException at some later point far from the cause, or into a silently truncated result if the writing loop is bounded by the array's own length.

The obvious guard does not close it. Adding if (total < 0) throw ... catches only the wraps that happen to land in the negative half: 0x40000000 * 4 is 0 and 0x40000000 * 5 is 1073741824, both of which pass a sign check while being wrong. A check on the result cannot distinguish a wrapped value from a legitimate one, because the information needed to tell them apart was discarded by the multiply. The check has to happen before or during the arithmetic - Math.multiplyExact, or bounds on the inputs.

A wrapped sum used in a security check

// VULNERABLE - unchecked addition decides whether a limit has been reached
public static boolean withinRateLimit(int currentCount, int increment, int limit) {
    // Attack: increment = Integer.MAX_VALUE -> the sum wraps negative and passes
    int newCount = currentCount + increment;
    return newCount <= limit;
}

Why this is vulnerable: the wrap turns the guard into a permit. 5 + Integer.MAX_VALUE is -2147483644, which is <= limit for any plausible limit, so the larger the requested increment the more certainly the check passes - the opposite of what the code reads as. Nothing signals it: no exception, no negative array size for the JVM to catch, just a true returned to a caller that treats it as authorization.

This is the shape worth recognising past this example, because it has no containing failure. An overflow that reaches an allocation eventually surfaces as a crash somewhere; an overflow that reaches a comparison produces a correct-looking boolean, and the only evidence is the behaviour the limit was supposed to prevent. The same applies to permission counts, quota checks, session-expiry arithmetic and offset/length pairs validated with offset + length <= size.

Secure Patterns

Math.addExact / Math.multiplyExact

public class SafeArithmetic {
    public static byte[] allocateBuffer(int count, int itemSize) {
        if (count < 0 || count > 1_000_000) {
            throw new IllegalArgumentException("Invalid count");
        }
        if (itemSize < 0 || itemSize > 1_000) {
            throw new IllegalArgumentException("Invalid item size");
        }

        int total = Math.multiplyExact(count, itemSize);  // Throws ArithmeticException on overflow
        return new byte[total];
    }

    public static int addWithLimit(int currentCount, int increment, int limit) {
        int newCount = Math.addExact(currentCount, increment);  // Throws on overflow instead of wrapping
        if (newCount > limit) {
            throw new IllegalStateException("Limit exceeded");
        }
        return newCount;
    }
}

Why this works: Math.addExact, Math.subtractExact, and Math.multiplyExact (java.lang.Math, available since Java 8) perform the same operation as +/-/* but throw ArithmeticException instead of silently wrapping, so the overflow becomes a failure the caller can catch rather than a wrong number that flows on. Validating the input range first bounds the operands before they reach arithmetic at all.

BigInteger for Values That May Exceed long Range

import java.math.BigInteger;

public class LargeArithmetic {
    public static BigInteger safeMultiply(long a, long b) {
        return BigInteger.valueOf(a).multiply(BigInteger.valueOf(b));
    }
}

Why this works: BigInteger has no fixed width and cannot overflow - appropriate for calculations (financial totals, cryptographic values) where the theoretical range exceeds what long can hold, at the cost of being slower than primitive arithmetic. Still validate against an application-level practical limit before using a BigInteger result to size a collection or array, since Java arrays are still indexed by int.

Framework-Specific Guidance

  • Bean Validation (Jakarta Validation): @Max/@Min on request DTO fields reject out-of-range values before they reach arithmetic, but validate the combination of fields separately (a class-level @AssertTrue or a service-layer check) when overflow depends on a product or sum of two fields rather than either field alone.
  • Spring: the same request-DTO validation applies; don't rely on @Positive/@Max on individual fields alone when the vulnerable operation is a calculation across fields.

Testing

  • Boundary values: 0, Integer.MAX_VALUE, Integer.MAX_VALUE - 1, Integer.MIN_VALUE, Long.MAX_VALUE.
  • Values engineered to overflow the specific operation under test (two large operands whose product exceeds Integer.MAX_VALUE).
  • Confirm Math.multiplyExact/Math.addExact actually throw for the overflow case, and that the calling code handles the exception (rejects the request) rather than letting it propagate uncaught.
  • For BigInteger paths, confirm the application-level practical limit still rejects unreasonably large inputs even though BigInteger itself won't overflow.

Common Pitfalls

  • Assuming Java's exceptions catch every overflow automatically: only the Math.*Exact methods throw on overflow - the plain +, -, * operators wrap silently, exactly like C. Swapping to Math.addExact/multiplyExact is a deliberate opt-in, not Java's default arithmetic behavior.
  • Validating a single field's range but not the combination: a per-field bound only bounds the product at the product of the bounds. @Max(100_000) on a count field and @Max(100_000) on a size field looks restrictive, but count * size can reach 10^10 - well past Integer.MAX_VALUE (2,147,483,647) - so both fields pass validation individually while their product wraps. The bound that matters is on the result, not on the operands.
  • Using int for an accumulator that's incremented in a loop over untrusted-length input: a loop that adds to an int total once per element of an attacker-controlled collection can overflow after enough iterations even if each individual increment looks small and safe.
  • Catching ArithmeticException and silently ignoring it: treating the exception as "just skip this calculation" instead of rejecting the request can leave the caller operating on stale or default data instead of failing closed.

Additional Resources