labs
§ A laboratory primer

INTEGER overflow

An interactive walkthrough of integer overflow — what happens when an arithmetic result no longer fits in the bits a program reserved for it. Seven chapters: how integers are stored, how the same bits can mean two different numbers, when addition and subtraction wrap, which operators are safe and which are not, what real software has lost to overflow, what overflow looks like in working C, and how to defend against each kind.

ISpecimen one — storage

How a number fits in fixed bits.

A computer cannot literally "store the number 42." It stores a fixed pattern of on and off switches — bits — and reads that pattern as 42 by convention. N bits accommodate exactly 2N patterns: an 8‑bit byte holds 256, no more. Integer overflow is what happens when a value a program tries to store does not fit in the bits reserved for it — and from this single mismatch every behaviour in this lab descends.

Each bit represents a power of two. To form a value the machine sums the powers whose bits are on. The cage is exactly its size — N bits, never more — and that ceiling is the same on every machine.

Take an 8‑bit unsigned byte. Eight bits; 256 unique patterns, from 00000000 to 11111111. Ask the byte to hold a value larger than 255 and the high bits silently fall off — the byte keeps only the rightmost 8 bits and forgets the rest. That silent loss is integer overflow, and every example in this lab is a different costume on the same idea.

Below: the four canonical widths, their unsigned spans, and their signed spans (using two's complement, the encoding every modern CPU uses, in which the leftmost bit carries a negative weight). Click a row to designate that width as the working width for this section's gauge.

INSTRUMENT 01 ⁄ CAGE GAUGE Comparative ranges by width.
Width Patterns (2N) Unsigned span Signed span Common name
82560 — 255−128 — 127byte
1665 5360 — 65 535−32 768 — 32 767short
32≈ 4.29 × 10⁹0 — 4 294 967 295−2 147 483 648 — 2 147 483 647int
64≈ 1.84 × 10¹⁹0 — 18 446 744 073 709 551 615±9.22 × 10¹⁸long
selected width
32
bits
addresses (unsigned MAX)
4 294 967 295
signed MIN ⁄ MAX
−2 147 483 648 ⁄ 2 147 483 647
IISpecimen two — signed vs unsigned

The same bits, read two ways.

The same eight bits mean 255 to one reader and −1 to another, depending on whether the leftmost bit is read as a sign. Signed and unsigned overflow look different from each other for exactly this reason — the same bytes, two contracts.

In two's complement, the leftmost bit carries a negative weight. To negate a number, invert every bit and add one. This single rule produces every quirk of signed arithmetic — and explains why signed overflow lands on a negative number while unsigned overflow lands on a small positive one.

The instrument below holds 8, 16, 32, or 64 bits — your choice. Edit the bits directly (click them), sweep the slider, type a decimal value, or randomise. The same bit pattern produces two distinct decimal readings: one if the leftmost bit is treated as a sign, one if it is not. The reading depends entirely on which type the program declared, never on the bytes themselves.

INSTRUMENT 03 ⁄ TWIN INTERPRETERS N bits. Two readings.
read as unsigned u8
200
All bits contribute positive weight. Leftmost bit is worth 128 (2⁷).
read as signed i8
−56
Leftmost bit reverses sign — its weight becomes −128 (−2⁷).
SIGN BIT ↗
IIISpecimen three — wraparound

When addition and subtraction overflow.

Add or subtract two numbers in any of four cages. The arithmetic is computed on the host machine in arbitrary precision first; then the cage truncates the result to its width. When the truncated value disagrees with the true result, that disagreement is overflow.

Edit either operand; the calculation runs immediately. Switch widths to see how the same arithmetic produces overflow at one width and not at another.

The instrument below shows the same operation in two columns. The left column is the arithmetic in decimal: A, B, and the result a real CPU would store after truncating to N bits. The right column is the same values shown as bits, so the truncation is visible directly. When the arithmetic on the left disagrees with the true mathematical answer, the bits on the right show what was lost — and that disagreement is overflow, observed.

INSTRUMENT 03 ⁄ ADD ⁄ SUB CALCULATOR Two operands. One cage. Two readings.
ARITHMETIC
A200
+100
=44
BITS · u8
A1100 1000
+0110 0100
=0010 1100
IVSpecimen four — operators

Which operators can overflow, which cannot.

Of the integer operations a C program performs, only some can produce overflow or truncation. The rest always fit. Here is the complete catalog — four categories, two unsafe, two safe — with one interactive demo per category.

Edit any operand, switch any width or sign; the result updates immediately. The red-striped cards are the operations that can silently corrupt a value; the green-striped cards are the ones that always produce a defined result within range.

The unsafe pair — assignment and arithmetic — covers every example you have seen so far in this lab. The safe pair — bitwise/shift and compare — is what well-written C falls back to when an arithmetic operation cannot be guaranteed to fit. Knowing which is which is the first step in writing code that doesn't lose information by accident.

UNSAFE · TRUNCATION

Assignment

=

Storing a wide value into a narrower variable silently keeps only the low N bits.

value → stored as
stored: 232   1110 1000
UNSAFE · OVERFLOW & TRUNCATION

Arithmetic

+ − × ÷

Sums, differences, products, and quotients can exceed the cage and silently wrap.

raw: 300
stored: 44   0010 1100
SAFE

Bitwise & shift

<< >> & | ^

Operate on bits directly; the result is always defined and stays within N bits.

A: 1100 1000
B: 3 positions
= 64   0100 0000
✓ SAFE — bitwise operators produce a defined result for every input.
SAFE

Compare

== ≠ < > ≤ ≥

Returns true or false. Cannot overflow — but the same bytes can compare differently in signed vs unsigned.

unsigned: false (200 == 100)
signed: false (−56 == 100)
✓ SAFE — compares always produce true/false, never overflow.
VSpecimen five — real-world bugs

Overflow bugs at large in real software.

Six historical incidents in which integer overflow caused real damage. In every case the program followed its rules precisely; the bug was that nobody had written a rule for what should happen when a counter exceeded its width.

CLASSIC
04 JUN 1996 ⁄ KOUROU, FRENCH GUIANA

Ariane 5, flight 501

CASE FILE / OVERFLOW · 16‑BIT SIGNED

A horizontal‑velocity reading from the inertial reference system was converted from a 64‑bit float to a 16‑bit signed integer. Forty seconds into flight the value exceeded 32 767; the conversion threw an exception; the rocket self‑destructed.

damage
≈ US$ 370 million
cage
i16 · [−32 768, 32 767]
Wikipedia · Ariane flight V88
PENDING
19 JAN 2038 ⁄ EVERYWHERE

The Year 2038 problem

CASE FILE / OVERFLOW · UNIX TIME · I32

POSIX time counts seconds since 1 Jan 1970 in a 32‑bit signed integer. At 03:14:07 UTC on 19 January 2038 the counter will reach 2 147 483 647 — and the next tick will roll it to −2 147 483 648, suddenly placing the world in December 1901.

seam at
2 147 483 647 s
cage
i32 · time_t (legacy)
Wikipedia · Year 2038 problem
CULTURAL
03 DEC 2014 ⁄ INTERNET

Gangnam Style

CASE FILE / NEAR‑MISS · I32

The video accumulated views toward 2³¹ − 1 = 2 147 483 647, the maximum of YouTube's signed 32‑bit counter. To prevent a wraparound to negative views, the platform quietly upgraded the counter to 64‑bit before the seam was reached.

migration
i32 → i64
new ceiling
≈ 9.22 × 10¹⁸
Wikipedia · Gangnam Style
ARCADE
1980 ⁄ NAMCO PAC‑MAN

The kill screen

CASE FILE / OVERFLOW · U8 LEVEL COUNTER

Pac‑Man stores the level number in a single unsigned byte. Reaching level 256 wraps the byte to 0, and a routine that draws fruit reads invalid memory — corrupting the right half of the maze into garbled letters. Unwinnable by design accident.

seam
level 256
cage
u8 · [0, 255]
Wikipedia · Kill screen
SECURITY
RECURRING ⁄ CVE DATABASE

The integer‑overflow exploit

CASE FILE / WEAPONISED OVERFLOW

An attacker requests a buffer of n × m bytes, choosing values whose product overflows to a small number. The system allocates a tiny buffer, then attempts to write the originally requested bytes — overrunning the allocation and yielding arbitrary code execution.

vector
size_t multiplication
defence
checked arithmetic
MITRE · CWE‑190 Integer Overflow
FINANCE
1983 ⁄ VANCOUVER STOCK EXCHANGE

Truncation, compounded

CASE FILE / FIXED‑POINT TRUNCATION

The exchange's index truncated each calculation to three decimals rather than rounding. Three thousand updates per day, twenty‑two months later, the index had silently bled 574 points — about half its value — through repeated downward drift.

drift
≈ 25 points / month
fix
recomputation, four decimals
Wikipedia · Vancouver Stock Exchange
VISpecimen six — live C programs

Six overflow bugs you can run.

A small ANSI‑C runtime, built into this page, executes real programs in the browser. Edit the highlighted operands, press compile & run, and watch the bug emerge in the terminal — exactly as it would on a host machine.

Six C programs, each demonstrating a different kind of integer overflow. Every program is real C: declarations, types, control flow, printf. The runtime honours fixed‑width integer semantics down to the byte.

The integer types you have studied — uint8_t, int32_t, size_t — appear here as they would in working production code. The operands shown in amber are editable; everything else is the program itself, untouched. After a run, if overflow occurred, the offending line is marked in red.

The terminal beside the code is not decoration: it shows the program's stdout, character by character, with the same exit semantics a real ./a.out would produce.

INSTRUMENT 06 ⁄ LIVE COMPILER Scenarios in C, executed in‑page.

        
tty · stdout
// no output yet — alter the inputs above and press compile & run.
SCENARIO · · target: x86‑64 · gcc 13.2 (simulated)
What just happened
Press compile & run and the program will execute. The verdict appears here, explaining whether the cage held — and if it did not, where the silent corruption was injected.
VIISpecimen seven — secure fixes

A secure fix for every live program.

Each of the six C programs in Live Specimens above has a corresponding patch below — and only those programs do. Every fix is a small, idiomatic change: no new dependencies, just different choices about types, validation, and where the check runs.

Each card is a side‑by‑side diff between the original program and the secured version. Tokens that changed are framed in red on the vulnerable side and green on the patched side. New lines are striped in green with a leading +.

Almost every defense against integer overflow falls into one of two complementary strategies, marked on each remedy's tag. PRE‑CHECK — a precondition test — asks "would this operation overflow?" before running it, and refuses if so (Remedies 02, 03, 05). POST‑CHECK — a postcondition test — runs the operation and then asks "did it overflow?", undoing the result if it did (Remedies 04, 06). The patched code in every remedy carries an explicit /* PRE-CONDITION */ or /* POST-CHECK */ comment naming the strategy in use. The third option, TYPE‑level prevention, picks a wider type so neither check is needed at all — that is what Remedy 01 does, and is the cheapest defense when storage permits.

The remedies cluster further around four habits: widen before you narrow, validate the original input (not what survives a cast), use checked arithmetic when wrap‑around is possible, and let the standard library compute sizes for you when it can. The closing card distils these into a single defensive checklist.

CLOSING ⁄ Defensive principles Read this twice

Three strategies, three habits — read this first.

i. Precondition testing. Ask "would this operation overflow?" before performing it; refuse if so. Example: if (a > UINT_MAX − b) return ‑1; result = a + b; Cheap, portable, doesn't need any builtin or library — but the rearranged inequality must be expressible in the operand's type.
ii. Postcondition testing. Run the operation and ask "did it overflow?"; undo the result if it did. Example: if (__builtin_add_overflow(a, b, &r)) return ‑1; The compiler folds this into a single CPU instruction (CF/OF flag) and is the only safe option for signed integers, where naïve if (a + b < a) is undefined behaviour.
iii. Type‑level prevention. Choose a type wide enough that overflow cannot happen. uint16_t bytes = a × b can wrap; size_t bytes = (size_t)a × b cannot, for any 16‑bit a, b. The cheapest defense — no runtime cost — but only available when a wider type fits in your storage budget.
iv. Trust no input, including your own. Sizes from network protocols, file formats, and user APIs are all hostile. Even values your own code computes can be hostile if part of the pipeline came from outside.
v. Compile with the safety nets engaged. Each flag earns its place: -Wall -Wextra enable the broad set of compiler warnings (uninitialised variables, suspicious comparisons, dead branches); -Wconversion warns on every implicit narrowing — exactly the silent int → int8_t cast that the live programs exploit; -Wsign-conversion catches the signed‑↔‑unsigned mismatches that produce CWE‑195 (the bug in Remedy 05); -fsanitize=undefined (UBSan) instruments the binary so that any signed overflow, division by zero, or out‑of‑bounds index aborts immediately with a precise diagnostic — use it in CI and during fuzzing; -ftrapv (or its modern equivalent -fsanitize=signed-integer-overflow,trap) emits CPU trap instructions on signed overflow, faulting the program rather than continuing with corrupted state — recommended for long‑running services where any silent wrap is unacceptable.
vi. Pick a language that defaults to safe. Rust panics on signed overflow in debug builds and requires explicit wrapping_add / checked_add / saturating_add for the wrap, trap, or clamp behaviour you actually want; the type system carries the choice. Zig distinguishes + (trap) from +% (wrap). Java, C#, Python, Swift all check signed arithmetic by default. C and C++ make safety a per‑expression decision — make it, but if the budget allows, write the new code in something that makes that decision for you.
REMEDY 01 ⁄ The Counter TYPE · Widen the result

Promote the accumulator, not the operands.

VULNERABLE

        
PATCHED

        

A byte cannot hold a sum that exceeds 255. By widening the destination type — and casting at least one operand so the addition itself happens in the wider type — the result has nowhere to overflow into. For two‑byte sums, sixteen bits suffice; for two 32‑bit values, use int64_t.

REMEDY 02 ⁄ The Loop PRE‑CHECK · Validate the bound

Size the counter to the limit it must reach.

VULNERABLE

        
PATCHED

        

A loop counter must accommodate the maximum value its condition compares against — and one more, since the increment runs first. The idiomatic choice for "an index into a thing in memory" is size_t; it is large enough on every conforming platform. With a correctly‑sized counter, the safety cap becomes unnecessary.

REMEDY 03 ⁄ The Index PRE‑CHECK · Validate before narrow

Check the original, then narrow.

VULNERABLE

        
PATCHED

        

Narrowing destroys information; a bounds check on the destination type sees only the residue. Always validate the untouched input — both lower and upper bound — and cast to the narrower index type only after the check has passed. Using size_t for the index also forecloses the negative‑index path entirely.

REMEDY 04 ⁄ The Allocator POST‑CHECK · Library handles size

Use calloc, which detects size overflow.

VULNERABLE

        
PATCHED

        

The standard library's calloc already detects size overflow and returns NULL — using it is the simplest and most portable defence. Where you must compute a size yourself, GCC and Clang provide __builtin_mul_overflow (and _add, _sub) that report wrap‑around through their boolean return value.

REMEDY 05 ⁄ The Conversion PRE‑CHECK · Validate signed input

Reject negatives before the implicit cast.

VULNERABLE

        
PATCHED

        

A signed‑to‑unsigned conversion happens silently every time a signed int meets a function that takes a size_t (memcpy, memset, read, fread). The fix is to validate both bounds — lower and upper — on the original signed value, before the implicit cast erases the sign. if (len < 0 || (size_t)len > sizeof(buf)) stops the negative path that the original "len < BUFFER_SIZE" check missed. This is CWE‑195 in the wild.

REMEDY 06 ⁄ The Packet POST‑CHECK · Library handles size

Let calloc reject impossible sizes.

VULNERABLE

        
PATCHED

        

The original CVE‑2002‑0639 patch widened the type of nresp and used a checked allocator. Both choices matter: a wide size_t for count means the multiplication has room; calloc(count, 8) additionally short‑circuits the allocation when the product would overflow size_t, returning NULL rather than handing back a tiny chunk for a huge write. The lesson generalises: any size computed as n × m on attacker‑influenced inputs should run through calloc or __builtin_mul_overflow.

SET IN

Fraunces, by Phaedra Charles & Flavia Zimbardi. Newsreader, by Production Type. JetBrains Mono, by JetBrains.

NOTES

All arithmetic herein is performed in the host machine's BigInt engine and then truncated, in software, to the chosen cage. The viewport is rendered with three.js. Findings are illustrative; the laws are universal.

FILED

Bureau of Numerical Artifacts, Annex IV. — end of file —