CWE-191: Integer Underflow (Wrap or Wraparound)
Overview
Integer underflow occurs when arithmetic operations produce results smaller than the minimum value the integer type can hold, causing unsigned integers to wrap to large positive values or signed integers to become negative. This leads to buffer overflows, incorrect memory allocations, and bypassed security checks.
Risk
High: Integer underflows cause buffer overflows (when unsigned wraps to large value), authentication bypass (negative values become positive), incorrect loop bounds, memory corruption, and logic errors. Especially dangerous with size_t and unsigned types used in allocations.
Remediation Steps
Core principle: Validate ranges before arithmetic to prevent integer underflow/wraparound.
Locate integer underflow vulnerabilities in your code
- Review the flaw details to identify the specific file, line number, and code pattern
- Identify vulnerable operations: subtractions with unsigned integers, loop counters that can wrap, buffer size calculations, array index calculations
- Determine the data types involved: size_t, unsigned int, uint32_t, or other unsigned types
- Trace untrusted input that influences arithmetic operations
Validate before subtraction (Primary Defense)
- Check minuend >= subtrahend before subtracting:
if (dest_size < src_size) return ERROR; remaining = dest_size - src_size; - Use safe arithmetic libraries: C11 _Generic macros, SafeInt library (C++), Math.subtractExact() (Java), checked blocks (C#)
- Avoid unsigned arithmetic with untrusted input: Use signed integers for calculations, only use unsigned for actual sizes or bit operations
- Check for negative results after operations: For signed types, verify result didn't become negative unexpectedly
Use signed types for arithmetic operations
- Use signed integers for calculations that can go negative: int, long, ssize_t instead of unsigned types
- Only use unsigned for bit operations or actual sizes: Reserve unsigned for when negative values are logically impossible
- Check range before converting signed to unsigned:
if (signed_value < 0 || signed_value > UINT_MAX) return ERROR; - Be aware of comparison pitfalls with mixed types:
unsigned int u = 10; int s = -5; if (s < u)is FALSE due to implicit conversion
Implement pre-condition checks for loops and arrays
- Validate inputs before subtraction: Check that subtraction won't wrap before performing it
- Check loop conditions don't underflow: Prefer forward iteration
for (i = 0; i < count; i++)over reverse with unsigned counters - Validate array indices can't go negative: When using signed indices, check
index >= 0 && index < array_size - Use assertions in development:
assert(minuend >= subtrahend)to catch errors early
Enable compiler sanitizers and static analysis
- Use -fsanitize=unsigned-integer-overflow (Clang) to detect underflows at runtime
- Enable runtime bounds checking where available
- Test with edge cases: 0, MIN_INT, boundary values
- Run Static Analysis tools regularly as part of your SDLC
Test the fix thoroughly
- Test with edge cases: 0 values, minimum values, maximum values
- Verify arithmetic operations with negative test values don't wrap
- Use fuzzing to test with random inputs
- Run with sanitizers enabled to catch any remaining issues
- Re-scan with security scanner to confirm the issue is resolved
Common Vulnerable Patterns
Unsigned Subtraction Without Validation
#include <stdlib.h>
#include <string.h>
// Dangerous: unsigned underflow
void copy_buffer(char *dest, size_t dest_size,
char *src, size_t src_size) {
// Attack: src_size=100, dest_size=10
size_t space_left = dest_size - src_size;
// Underflow! space_left = SIZE_MAX - 90 (huge number)
if (space_left > 0) {
memcpy(dest, src, src_size); // Buffer overflow!
}
}
Unsigned Loop Counter Underflow
// Dangerous: loop underflow
void process_array(unsigned int count) {
// Attack: count=0
for (unsigned int i = count - 1; i >= 0; i--) {
// First iteration: i = 0-1 = UINT_MAX
// Infinite loop or out-of-bounds access!
process(array[i]);
}
}
Size Calculation Wraparound
#include <stdlib.h>
// Dangerous: size calculation underflow
void allocate_remaining(size_t total, size_t used) {
// Attack: used > total
size_t remaining = total - used; // Wraps to huge value
char *buffer = malloc(remaining); // Huge allocation
}
Secure Patterns
Pre-Validation for Buffer Copy (C)
#include <stdlib.h>
#include <string.h>
int copy_buffer_safe(char *dest, size_t dest_size,
char *src, size_t src_size) {
// Check: will subtraction underflow?
if (src_size > dest_size) {
return -1; // Not enough space
}
memcpy(dest, src, src_size);
return 0;
}
Why this works: Checking src_size > dest_size before any subtraction prevents underflow. The comparison is safe because both operands are unsigned, and we avoid computing a potentially wrapped value.
Safe Unsigned Loop Iteration (C)
// Correct: use forward iteration or reverse with proper check
void process_array_safe(unsigned int count) {
// Option 1: Forward iteration (preferred)
for (unsigned int i = 0; i < count; i++) {
process(array[i]);
}
// Option 2: Reverse with decrement in condition
if (count > 0) {
for (unsigned int i = count; i-- > 0; ) {
process(array[i]);
}
}
}
Why this works: Forward iteration never subtracts from unsigned values. For reverse iteration, the decrement occurs in the loop condition (i--), which is evaluated before the comparison, preventing wraparound from causing infinite loops.
Validated Subtraction for Allocation (C)
#include <stdlib.h>
void *allocate_remaining_safe(size_t total, size_t used) {
// Validate before subtraction
if (used > total) {
return NULL; // Invalid state
}
size_t remaining = total - used;
// Additional sanity check
if (remaining > 1024 * 1024 * 10) { // 10MB limit
return NULL;
}
return malloc(remaining);
}
Why this works: Validating used > total before subtraction ensures the result won't underflow. The additional upper-bound check prevents unreasonably large allocations even when the arithmetic is valid.
Safe Subtraction Helper Functions (C)
#include <stdbool.h>
#include <stddef.h>
bool safe_subtract(size_t a, size_t b, size_t *result) {
if (b > a) {
return false; // Would underflow
}
*result = a - b;
return true;
}
int process_range(size_t start, size_t end, size_t offset) {
// Validate offset can be subtracted
if (offset > start) {
return -1;
}
size_t adjusted_start = start - offset;
// Now check range is valid
if (adjusted_start >= end) {
return -1;
}
// Process range [adjusted_start, end)
for (size_t i = adjusted_start; i < end; i++) {
process_item(i);
}
return 0;
}
Why this works: Using helper functions with explicit validation centralizes underflow checks and makes the code more maintainable. Boolean return values clearly indicate success or failure.
Exception-Throwing Subtraction (Java)
public class SafeSubtraction {
public static int safeSubtract(int a, int b) {
try {
return Math.subtractExact(a, b);
} catch (ArithmeticException e) {
throw new IllegalArgumentException("Integer underflow");
}
}
public static byte[] allocateRemaining(int total, int used) {
if (used < 0 || total < 0) {
throw new IllegalArgumentException("Negative values");
}
if (used > total) {
throw new IllegalArgumentException("Used exceeds total");
}
int remaining = total - used; // Safe: checked above
if (remaining > 10_000_000) {
throw new IllegalArgumentException("Too large");
}
return new byte[remaining];
}
}
Why this works: Java's Math.subtractExact() throws an exception on underflow, preventing wrap-around. Pre-validation with range checks ensures values are within expected bounds.
Safe Integer Arithmetic with Validation (Python)
def copy_buffer(dest: bytearray, src: bytes, offset: int) -> bool:
"""Copy src to dest at offset, checking for underflow."""
# Validate offset
if offset < 0 or offset > len(dest):
return False
# Calculate remaining space
remaining = len(dest) - offset # Python handles this safely
if len(src) > remaining:
return False # Not enough space
dest[offset:offset+len(src)] = src
return True
Why this works: Python handles integer arithmetic safely by default with arbitrary precision integers. Explicit validation ensures values are within practical system limits.