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
.
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, since unsigned arithmetic is defined modulo .
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. Division by is also undefined.
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 two 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 the signed operand’s rank
Convert both to unsigned. - Else if the signed type can represent every value of the unsigned type
Convert both to signed. - Else
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);