CWE-128, the Wrap-around Error, covers any case where a value is incremented past the maximum for its type and so wraps around to a very small, negative or undefined value. A 32-bit unsigned counter that reaches 4,294,967,295 and is incremented becomes 0.
MITRE lists C and C++ as the affected languages. The weakness is old and well understood, and it keeps feeding more serious bugs because the wrapped number often decides how much memory to allocate or how many times to loop.
How it happens
Fixed-width integers do not grow. Arithmetic that exceeds the range silently wraps in the unsigned case, and is undefined behavior in C for signed types. The danger comes from what the program does next with the wrong number.
// Vulnerable: len + 1 wraps to 0 when len is UINT32_MAX
uint32_t len = read_len(input);
char *buf = malloc(len + 1); // allocates 0 bytes after wrap
memcpy(buf, input_data, len); // writes about 4 GB into a tiny block
Loops are the other classic. If a loop index is an 8-bit or 16-bit type and the loop bound is larger than that type can hold, the index wraps and the loop never ends.
// Vulnerable: uint8_t index can never reach 300
for (uint8_t i = 0; i < 300; i++) {
process(i);
}
The related allocation bug is CWE-131, Incorrect Calculation of Buffer Size, and wrap-around is a common way to get there.
What an attacker gains
MITRE lists three consequences.
- Crashes and denial of service. Wrap-around generally leads to undefined behavior and therefore crashes. With loop index variables, infinite loops become likely, which burns CPU and memory.
- Data corruption. If the value matters to data rather than control flow, simple corruption has occurred. If the wrap leads to a buffer overflow, further memory corruption may follow.
- Code execution. The weakness can sometimes trigger buffer overflows that can be used to execute arbitrary code, outside the program's implicit security policy.
How to find it
MITRE names automated static analysis, which can find some instances by analyzing source code without running it. In practice you also want these checks.
- Compile with warnings for sign conversion and integer truncation.
- Use undefined behavior and integer sanitizers in test builds where your compiler supports them.
- Test arithmetic at the boundaries: zero, one, maximum and maximum minus one.
- Review every allocation whose size is computed from external input.
How to fix it
The record lists no mitigations. These steps follow from the definition.
- Check before you add or multiply. Compare against the type maximum, or use checked arithmetic such as
__builtin_add_overflow. - Choose types wide enough for the data, and use the same type for sizes and loop indexes.
- Cap values from untrusted input at a realistic limit before doing arithmetic.
- Be careful with conversions between signed, unsigned and different widths.
// Fixed: check before use
uint32_t len = read_len(input);
if (len > MAX_ALLOWED || len == UINT32_MAX) return -1;
char *buf = malloc((size_t)len + 1);
if (!buf) return -1;
memcpy(buf, input_data, len);
buf[len] = '\0';
Quick check
Does wrap-around only matter for unsigned values?
No. Unsigned values wrap by definition, while signed overflow is undefined behavior in C and C++. Both can produce wrong sizes and loops.
Why do wrap-around errors lead to buffer overflows?
The wrapped value is often used as an allocation size or a bound. A tiny result makes the buffer too small while the data copied is still large.