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/@Minon request DTO fields reject out-of-range values before they reach arithmetic, but validate the combination of fields separately (a class-level@AssertTrueor 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/@Maxon 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.addExactactually throw for the overflow case, and that the calling code handles the exception (rejects the request) rather than letting it propagate uncaught. - For
BigIntegerpaths, confirm the application-level practical limit still rejects unreasonably large inputs even thoughBigIntegeritself won't overflow.
Common Pitfalls
- Assuming Java's exceptions catch every overflow automatically: only the
Math.*Exactmethods throw on overflow - the plain+,-,*operators wrap silently, exactly like C. Swapping toMath.addExact/multiplyExactis 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 acountfield and@Max(100_000)on asizefield looks restrictive, butcount * sizecan reach 10^10 - well pastInteger.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
intfor an accumulator that's incremented in a loop over untrusted-length input: a loop that adds to aninttotal once per element of an attacker-controlled collection can overflow after enough iterations even if each individual increment looks small and safe. - Catching
ArithmeticExceptionand 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.