Integer Overflow Explained: When Arithmetic Wraps Around
How integer overflow and wraparound lead to undersized allocations and buffer overflows, and the safe-arithmetic practices that prevent it.
Numbers in a program are not the numbers of mathematics. A program integer occupies a fixed number of bits, which gives it a fixed range. Arithmetic that would produce a result outside that range does not raise an error by default, it wraps around, landing on a value that bears no relation to the correct answer. Integer overflow is that wraparound, and its security significance comes from what happens when a wrapped value is trusted as a size or an index. MITRE tracks the core class as CWE-190: Integer Overflow or Wraparound.
The bug is easy to overlook because the arithmetic looks ordinary and the wrap is silent. It becomes dangerous when the wrong number feeds a memory operation, which is why integer overflow is best understood alongside buffer overflow.
Fixed width and wraparound
An integer type reserves a set number of bits, so it can represent only a bounded set of values. When a calculation produces a result larger than the maximum, the extra high bits are discarded and the value wraps to the low end of the range. Add one to the largest representable value and you get the smallest, or in unsigned terms, you get zero.
Three flavors of this cause trouble:
- Unsigned wraparound. Adding two large unsigned values can produce a small one, because the true result exceeded the range and wrapped.
- Signed overflow. Exceeding a signed type's range produces an undefined or wrapped result, which can flip a large positive into a negative.
- Narrowing conversion. Storing a wide integer into a narrower type keeps only the low bits, so a large value becomes a small, unrelated one.
In every case the program continues with a plausible-looking but wrong number, and nothing signals that anything went awry.
The dangerous pattern: size math that wraps
The highest-impact form of integer overflow is a size calculation that feeds a memory allocation, tracked specifically as CWE-680: Integer Overflow to Buffer Overflow. The pattern is common: a program needs to allocate space for some number of elements, so it multiplies a count by an element size, or adds a header length to a body length, and asks the allocator for that many bytes.
If an attacker influences the count or a length, they can push that multiplication or addition past the integer's range. The size wraps to a small number. The allocator, asked for a small block, happily returns one. The program then copies the full, large amount of data into that small block, because the code believes it allocated enough. The result is a heap buffer overflow, and the integer bug is what set it up. The overflow itself may live in code that looks perfectly bounds-aware, because the bound it checked was the wrapped size.
The insidious part is that the copy is often correct with respect to the size variable. The code allocates size bytes and copies size bytes, which looks safe in review. The defect is upstream, where size was computed and silently wrapped to a value smaller than the data it is supposed to describe. Auditing the copy finds nothing. You have to audit the arithmetic that produced the size.
Underflow and negative sizes
Subtraction has a mirror-image problem, CWE-191: Integer Underflow. If a program computes a length by subtracting one value from another, and the subtrahend is larger, an unsigned result wraps to a huge number instead of going negative. A length that should have been a small positive becomes enormous, and a subsequent copy or loop using it runs far past its buffer. Length fields taken from untrusted input are a frequent trigger, because the attacker supplies values chosen to make the subtraction underflow.
Signed-to-unsigned confusion produces a related trap: a negative signed length reinterpreted as unsigned becomes a very large positive size, again driving an oversized operation.
Where these bugs appear
| Location | Why it overflows | Consequence |
|---|---|---|
| Allocation size = count times element size | Attacker-influenced count pushes past the range | Undersized buffer, then heap overflow |
| Total size = header + body length | Two large lengths sum past the maximum | Undersized allocation |
| Remaining = total - consumed | Consumed exceeds total, unsigned underflow | Huge length, over-read or over-write |
| Cast of a length to a narrower type | High bits dropped | Small size for large data |
| Loop bound from arithmetic | Wrapped bound | Out-of-bounds indexing |
How to defend against integer overflow
The defense is disciplined arithmetic wherever a value will be used for allocation, indexing, or copying.
- Check before you compute sizes. Before multiplying a count by an element size or adding lengths, verify the operation will not exceed the type's range. Reject inputs that would overflow rather than trusting the wrapped result.
- Use checked or safe arithmetic. Prefer library functions and language features that detect overflow and signal it, so a wrap becomes an error instead of a silent wrong value. Some platforms offer allocation helpers that perform the count-times-size check internally.
- Be deliberate about signedness and width. Avoid mixing signed and unsigned in size math, and avoid narrowing conversions on values derived from input. Choose types wide enough for the maximum legitimate value.
- Validate length and count fields from input. Bound every attacker-supplied count and length to a sane maximum before it enters any calculation. Most real inputs have a defensible upper limit.
- Turn on sanitizers and compiler checks. Integer overflow sanitizers catch wraps during testing and fuzzing, surfacing the exact arithmetic that goes wrong on hostile input.
- Prefer memory-safe and checked-arithmetic languages. Environments that trap on overflow, or that bounds-check every access, convert a silent wrap into a caught error before it can undersize an allocation.
Bounds checking on the copy is necessary but not sufficient, because a wrapped size passes the bounds check. The durable habit is to validate the arithmetic that produces sizes and indices, at the point of computation, with checked operations or explicit range tests. Combine that with input limits on counts and lengths, and the wrap never reaches the allocator.
Integer overflow is a small discrepancy between how programmers think about numbers and how machines store them, and that gap becomes a memory-corruption vulnerability the moment a wrapped value is trusted as a size. The arithmetic looks innocent, the wrap is silent, and the resulting overflow appears in code that checked its bounds against the wrong number. Guard the computation of every size and index, cap the inputs that feed them, and let checked arithmetic turn silent wraps into loud errors. For which memory-safety flaws attackers exploit in practice, see our Exploit Intelligence dashboard.
A worked example: the multiplication that wraps
Walk through the canonical case. Imagine code that reads an image header, pulls out a width and a height, and allocates a pixel buffer sized as width times height times bytes-per-pixel. On a 32-bit unsigned size type the largest value that fits is 4294967295. Any product larger than that wraps.
Suppose an attacker crafts a file claiming a width of 65536 and a height of 65536, at 4 bytes per pixel. The honest size is 65536 times 65536 times 4, which is 17179869184 bytes, about 16 gigabytes. That number does not fit in a 32-bit unsigned integer. It wraps, and the value that survives is 0. The code asks the allocator for 0 bytes, receives a valid tiny allocation, and reports success. Then it reads the actual pixel data from the file, potentially gigabytes of it, and copies it into a buffer that has room for nothing. The copy runs far past the allocation and corrupts whatever memory follows.
Notice what makes this dangerous. Every individual operation is correct. The multiplication is a legal multiplication. The allocation succeeds. The copy uses the size variable faithfully. No single line is wrong on its own terms. The defect lives in the relationship between the size the code computed and the data it later handled, and that relationship broke silently inside the multiplication. A reviewer reading the copy loop sees memcpy of size bytes into a buffer of size bytes and moves on. The bug is three lines earlier, in arithmetic that looked like arithmetic.
The same shape appears with addition. Code that computes a total as header length plus body length, where both come from an attacker-controlled file, can be pushed so the sum wraps to a value smaller than the body alone. The allocation is sized for the wrapped total and the body overflows it.
How the wrap becomes code execution
An undersized heap allocation followed by an oversized write is a heap buffer overflow, and heap overflows are a well-worn path to control of a process. The attacker who can write past the end of a small allocation may be able to corrupt heap metadata, overwrite a function pointer or a vtable pointer in an adjacent object, or clobber a length field that governs a later operation. From there the techniques are the ones covered under buffer overflow: shape the heap so that something valuable sits right after the undersized buffer, then overwrite it with attacker-chosen bytes. The integer overflow is the setup; the memory corruption is the payoff. This is exactly why CWE-680: Integer Overflow to Buffer Overflow is tracked as its own chain rather than lumped in with generic arithmetic bugs.
Not every integer overflow reaches memory corruption. Some produce a wrong loop count that reads a few bytes out of bounds, some flip a security check from deny to allow, and some merely compute a nonsensical value that crashes the program. The severity depends entirely on what the wrapped number is trusted to do next.
Signed, unsigned, and the conversion trap
The three flavors from earlier deserve a closer look because they fail differently and the fix differs with them.
Unsigned wraparound is defined behavior in most languages: the value simply reduces modulo two to the power of the bit width. That makes it predictable and, for an attacker, controllable. They can compute exactly which input produces the wrapped value they want.
Signed overflow is a sharper hazard in some languages, notably C and C++, where it is undefined behavior. The compiler is permitted to assume it never happens, which means an overflow can produce results that defy the naive wrap model, including optimizations that delete the very bounds check a programmer added. A check written as testing whether a value plus a length is still positive can be optimized away entirely, because the compiler assumes signed overflow cannot occur.
Conversions between widths and signedness cause a third family. Assigning a wide value into a narrow type keeps only the low bits. Assigning a negative signed value into an unsigned type reinterprets the bit pattern as a large positive number. A common real pattern is a signed length that is validated as less than some maximum, then passed to a function that takes an unsigned size. A negative length passes the less-than check because it is small as a signed number, then becomes an enormous positive size when the function reinterprets it. CWE-191: Integer Underflow and signedness confusion travel together this way.
Validating a value after it has already been used in arithmetic is too late. If code computes a size, then checks that the size looks reasonable, the wrap has already happened and the check runs on the wrapped value. The reasonable-looking small size passes. Validate the operands before the operation, or use a checked operation that reports the overflow as it happens, so the test sees the truth rather than the wreckage.
Detection signals
Integer overflow bugs are found by a mix of tooling and pattern-hunting. Concrete signals to look for:
- Sanitizer reports during testing. Undefined-behavior and integer sanitizers instrument arithmetic and report the exact line where a signed overflow, or an unexpected unsigned wrap, occurs under a given input. Running the test suite and a fuzzer under a sanitizer surfaces these directly.
- Fuzzer crashes clustered at allocations or copies. A fuzzer feeding malformed sizes into a parser will produce crashes whose stack traces point at an allocation or a copy. The upstream cause is frequently a wrapped size, even though the crash lands in the copy.
- Arithmetic on attacker-controlled length and count fields. In code review, any multiplication or addition that combines values parsed from input to produce an allocation size is a prime suspect. Grep the codebase for size calculations that feed
malloc,calloc,realloc, or the language's equivalent. - Mixed signed and unsigned operands in size math. Compiler warnings about signed-unsigned comparison are worth treating as real. They often mark the exact spot where a signedness confusion can produce a wrong size.
- Narrowing casts near I/O boundaries. A cast from a wide type to a narrower one, applied to a value that came from a file or the network, is a place where high bits get silently discarded.
The single most productive place to hunt integer overflow is any parser that reads length or count fields from untrusted input: file format parsers, network protocol decoders, decompressors, and image loaders. Fuzz them with a sanitizer enabled. The combination turns silent wraps into reproducible, located crashes before an attacker finds them.
How integer overflow compares to related bugs
Integer overflow is often confused with the memory-safety bugs it can trigger. Keeping the distinctions clear helps you fix the right thing.
| Weakness | Where it lives | Root cause | Typical fix |
|---|---|---|---|
| Integer overflow (CWE-190) | Arithmetic | Result exceeds the type's range and wraps | Checked arithmetic, input limits |
| Integer underflow (CWE-191) | Subtraction | Result goes below zero and wraps to a large value | Order operands, validate before subtracting |
| Integer-to-buffer-overflow (CWE-680) | Allocation then copy | A wrapped size undersizes a buffer | Fix the size math, then the copy is safe |
| Buffer overflow | Memory access | Write past an allocation's end | Bounds checks, safe containers |
| Off-by-one | Loop or index bound | A bound is one too large or small | Correct the bound expression |
The row that matters most is the third. An integer-to-buffer-overflow is not fixed by hardening the copy, because the copy is faithfully using a size that is already wrong. The fix belongs at the arithmetic that produced the size.
Common mistakes
Teams that try to defend against integer overflow tend to stumble in a few predictable ways.
- Checking the result instead of the operands. As noted above, testing whether a computed size is sane runs on a value that may already have wrapped. The check has to constrain the inputs or use an operation that flags the overflow during the computation.
- Assuming a 64-bit type makes it impossible. Wider types raise the ceiling, they do not remove it. A product of two attacker-controlled 64-bit values can still exceed 64 bits, and code that assumes it cannot has simply moved the cliff.
- Trusting a length field because a magic number matched. A file that starts with the right signature can still carry hostile length fields. Format validation and size validation are separate jobs.
- Relying on the copy being bounds-checked. A bounds-checked copy against a wrapped size copies the wrapped amount safely and still ends up with a buffer that does not match the real data, which usually corrupts something else downstream.
- Forgetting that signed overflow is undefined in C and C++. Programmers write overflow checks that depend on the wrap happening, then the compiler, assuming overflow cannot occur, removes the check. The correct pattern tests the operands before the operation or uses builtins that detect overflow.
How the problem has evolved
Integer overflow is as old as fixed-width arithmetic, and it stayed a niche concern while programs mostly handled their own trusted data. It became a security-critical class as software began parsing untrusted input at scale: image and media formats, network protocols, document formats, and archive and compression code. Each of those reads counts and lengths from data an attacker controls, and each multiplies or adds them to size a buffer.
The defensive response matured alongside. Compilers gained overflow-detecting builtins and sanitizers. Standard libraries added allocation helpers that perform the count-times-size check internally and refuse to return an undersized block. Newer languages made a deliberate choice about the default: some trap on overflow in debug builds and wrap deterministically in release builds, some require the programmer to opt into wrapping explicitly, and memory-safe languages bounds-check every access so a wrapped size cannot silently undersize a live buffer. The trajectory has been from a silent, invisible wrap toward a loud, located error, which is exactly the transformation a defender wants. Adopting the modern tools, checked arithmetic, sanitizer-driven fuzzing, and allocation helpers, is how you inherit that progress instead of rediscovering the bugs.
Frequently asked questions
Is integer overflow always a security vulnerability? No. Many overflows produce a wrong number that causes a visible bug or a crash with no security impact. It becomes a vulnerability when the wrapped value is trusted as a size, an index, a bound, or a security decision. The consequence depends on what the number does next.
Does using a 64-bit integer fix the problem? It raises the range but does not eliminate the class. Products and sums of large attacker-controlled 64-bit values can still exceed 64 bits. Wider types are a mitigation for some cases, not a guarantee.
Why is signed overflow considered worse than unsigned in C? Unsigned overflow is defined to wrap modulo the type's range, so it is predictable. Signed overflow is undefined behavior, which lets the compiler optimize based on the assumption that it never happens. That can silently remove overflow checks a programmer wrote, making the bug harder to reason about.
How do memory-safe languages help here? They bounds-check memory accesses, so even if a size calculation produces a wrong value, an out-of-bounds access is caught rather than silently corrupting memory. Several also trap on arithmetic overflow in debug builds, turning the wrap itself into an immediate error.
What is the difference between overflow and underflow in this context? Overflow is a result too large for the type, which wraps to a small value. Underflow here means an unsigned subtraction whose true result is negative, which wraps to a very large value. Both produce a wrong number; the direction of the wrong is what differs.
Where should I focus a limited audit? On any code that parses untrusted input and computes a size or count from it: file format and media parsers, protocol decoders, decompressors, and allocation sizes derived from headers. Fuzz those under an integer sanitizer for the highest return.
Can compiler warnings catch these? Some. Signed-unsigned comparison warnings and conversion warnings flag many of the setups, and they are worth treating as errors. They do not catch every case, so pair them with sanitizers and input validation.
Is checking a + b > MAX a valid overflow test?
For unsigned types the safe form is to check whether a is greater than MAX minus b before doing the addition, because the sum itself may already have wrapped. For signed types in C and C++ the addition is undefined if it overflows, so the reliable approach is a checked-addition builtin that reports overflow rather than an after-the-fact comparison the compiler may remove.
Do these bugs still appear in modern codebases? Yes, wherever new code parses untrusted binary formats or performs size arithmetic in a language without checked defaults. The tooling to catch them is far better than it once was, which is why enabling sanitizers, treating conversion warnings as errors, and using allocation helpers that check count-times-size matters on every new parser you write.
Related guides
Sources & further reading
Related guides
- Format String Vulnerabilities: When Input Becomes Format
How uncontrolled format strings turn user input into read and write primitives, why the bug is so severe, and how to defend against it.
- Buffer Overflow Explained: Overwriting the Return Address
How stack buffer overflows work, why writing past a buffer overwrites the return address, and the mitigations that stop hijacked control flow.
- Stack vs Heap Overflow: Two Memory Regions, Two Exploits
How stack and heap buffer overflows differ, why their exploitation models diverge, and what defends each region of memory.