Integer overflow is not itself a memory safety violation. It leads to one when the overflowed value is used:
- For pointer arithmetic
- As a
mallocargument - As an array index
An overflowed value used this way often leads to a buffer overflow, or directly to an out-of-bounds read or write.
Unsigned Overflow
An -bit unsigned integer represents with . On overflow, C++11 requires the value be reduced modulo , so unsigned arithmetic never triggers undefined behaviour.
An -bit unsigned integer can overflow in multiple cases, for :
- Addition
- Subtraction
if - Multiplication
Examples:
- Stagefright
uint8_t *buffer = malloc(size + chunk_size)overflows ifsize + chunk_sizeexceeds the unsigned range, allocating a buffer smaller than intended. Fixed by checkingsize > UINT_MAX - chunk_sizebefore adding, rejecting the allocation instead of letting the sum wrap.
Signed Overflow
By the C++11 standard, if evaluating an expression produces a result not mathematically defined or not representable by its type, the behaviour is undefined. This applies only to signed integers.
An -bit signed integer can overflow in multiple cases, for :
- Addition or subtraction
or . - Negation
, an asymmetry of two’s complement since is not representable. - Multiplication
or . Multiplication by reduces to negation. - Division
, reducing to negation.
Examples:
- Java’s
binarySearchcomputedmid = (low + high) / 2. For large arrays,low + highoverflows a signed 32-bit int before the division, producing a negative or invalid index. Fixed by computingmid = low + (high - low) / 2instead, sincehigh - lowstays within the array’s valid index range and cannot overflow.
Type Conversion
C automatically converts between types, known as coercion, performed by the compiler and capable of unintended consequences.
floattoint
Truncates the fractional part.doubletofloat
Rounds to the nearest representable value.
Converting a smaller type to a larger one uses:
- Sign extension
High bits set to the sign bit, used when the source is signed. - Zero extension
High bits set to0, used when the source is unsigned.
For an arithmetic operation between 2 operands, the compiler chooses a common type:
- Same type, same rank
No conversion. - Same type, different rank
Convert the smaller type to the larger. - Unsigned operand with rank at least the signed operand’s rank
Convert both to unsigned. - Signed type can represent every value of the unsigned type
Convert both to signed. - None of the above
Convert both to unsigned, using the signed operand’s type as the unsigned width.
Fixing Integer Overflow
Checking width * height > UINT_MAX after computing width * height is unsound, since the multiplication has already overflowed by the time the check runs.
A correct check divides instead of multiplying, guarding against division by :
void* new_8bit_image(unsigned int width, unsigned int height)
{
if (!width || (UINT_MAX / width < height)) return NULL;
unsigned int memory = width * height;
void* data = malloc(memory);
return data;
}
GCC and clang also provide built-in overflow-checking arithmetic, e.g. __builtin_umul_overflow, __builtin_add_overflow, __builtin_sub_overflow, for various integer types.
unsigned int memory;
if (__builtin_umul_overflow(width, height, &memory)) {
return NULL;
}
void* data = malloc(memory);