skip to content

Silent integer wraparound is usually taught as "the number got too big and started again from the bottom". Give the definition that explains why it is a security bug class rather than an arithmetic curiosity, and explain why "just use a wider type" is the wrong frame for it.

level: middleimportance: must knowfreq 45%

answer

  1. arithmetic mod 2^n, not the integers
  2. checked value ≠ used value
  3. silent domain change: wrap, truncate, signedness, float rounding
  4. C signed overflow is UB — compiler deletes your check
  5. wider type moves the modulus, not the proof

basics

~20 s

Fixed-width arithmetic is modular, so a computed value can differ from the mathematical result. Any check written over that computation stops being a proof about the real quantity. The bug is that the value you validated and the value you use disagree; a wider type only moves the modulus, it does not restore the proof.

solid answer

~50 s

A machine integer of n bits does not model the integers; it models arithmetic modulo 2^n. So `count * size` is not the product, it is the product's residue. Security checks are proofs: "this length fits the buffer", "this total is affordable". A wrap breaks the proof silently, and the code then uses the un-wrapped intent (loop `count` times) against a resource sized by the wrapped value. The frame matters because the defect is a **domain mismatch at a boundary**, not a size shortage. Widening to 64 bits only relocates the modulus, and the same class reappears where domains change: signed/unsigned conversion, 64→32 truncation, JavaScript's doubles losing integer exactness past 2^53 without any wrap, C's *undefined* signed overflow letting a compiler delete `if (a + b < a)` entirely. The durable fix is arithmetic that cannot silently produce a wrong value — trapping or checked operations — so the check and the use are guaranteed to talk about the same number.

go deeper

for a junior

Say that fixed-width integers wrap around modulo their size, that the wrap is silent, and that a check computed from wrapped values is no longer checking what you think.

for a middle

Give the checked-value-vs-used-value invariant and at least two divergent languages (defined wrap vs UB vs arbitrary precision vs doubles), plus why widening is not a fix.

for a senior

Frame it as silent domain change covering truncation and signedness too, and place checked arithmetic at the top of the defence ladder with the reason saturating arithmetic sits below it.

for a principal

Talk about where the guarantee lives: which types and helper functions in the codebase are allowed to do arithmetic on untrusted quantities, and how you make the unsafe form hard to write rather than hunting for instances.

## The invariant Every security-relevant check is a claim about a *quantity*: this length is within the buffer, this total is within the quota, this expiry is in the future. The code proves the claim over a computed expression and then acts on the result. The invariant that makes this sound is: **the value that was checked is the value that is used, and both equal the mathematical value the developer reasoned about.** A fixed-width integer breaks the third clause. An n-bit type implements arithmetic in Z/2^n — a ring of residues, not the integers. `a + b` is `(a + b) mod 2^n`. For values inside the range this is indistinguishable from real arithmetic, which is exactly why the defect is silent: it is correct for every value you tested and wrong only in the region an attacker chooses. ## Why that is a security class, not a bug The class is defined by *who chooses the operands*. If a length, count, index, price, quantity, timestamp or offset comes from the network, a file header or a request parameter, the attacker chooses the point at which your arithmetic changes domain. They do not need to "break" anything: they submit a legal value of the declared type and let the language compute a value the code was never written for. That is why it lives beside injection in secure-coding taxonomies — both are cases where untrusted input crosses a boundary and is interpreted under rules the developer did not have in mind. ## The languages diverge, and the divergence is the point - **C/C++**: unsigned overflow is *defined* wraparound; signed overflow is *undefined behaviour*. A compiler may assume it never happens and therefore delete the self-check `if (a + b < a)`, so the defence disappears at -O2 with no diagnostic. `-fwrapv` defines the wrap — which removes the UB but keeps the wrong value. - **Java / C#(unchecked) / Go**: two's-complement wrap, defined and silent. `Integer.MAX_VALUE + 1` is negative, which turns a size into a negative index. - **Rust**: panics on overflow in debug builds, wraps in release, and forces the author to name their intent with checked / saturating / wrapping operations. - **Python / Ruby / arbitrary-precision**: no wrap — the failure becomes unbounded allocation and OOM instead, so the same untrusted-size input yields denial of service rather than corruption. - **JavaScript / JSON**: numbers are doubles. Nothing wraps; past 2^53 you get silent *rounding*, so `n + 1 === n` can be true and two distinct 64-bit identifiers can collide. Bitwise operators additionally coerce to int32. One source construct, five different meanings. That is why "integer overflow" as a phrase understates it: the class is *silent domain change*, and truncation (64→32), signedness confusion (a negative length reinterpreted as a huge unsigned size) and float imprecision are members of it. ## The defence ladder Use the standard ordering, weakening from guarantee to heuristic: 1. **Structural separation** — make the wrong value unrepresentable. Checked/trapping arithmetic that fails fast, or a type whose range provably covers the whole expression (arbitrary precision, a decimal type for money). Nothing downstream can ever see a wrapped number. 2. **Transformation** — saturating arithmetic. Weaker, and here is the reason for this adjacent pair: saturation still yields a *wrong* value, just a monotonic one. It is right for a rendered progress bar or a back-off cap and catastrophic for an allocation size, where clamping to MAX turns a wrap into an enormous allocation. 3. **Validation** — bound each operand before the operation. Sound only if you enumerate the domain of the *whole expression*, and it must be re-derived every time someone edits the formula. 4. **Detection** — UBSan/overflow sanitizers, fuzzing, property tests, asserts in canary builds. Finds instances, guarantees nothing. ## What to carry away Ask of any arithmetic on untrusted numbers: what does this value authorise, and is the computation faithful over the attacker's whole input range? If the answer to the second is "only for reasonable inputs", you have the bug, whatever the width of the type.

  • Is integer overflow only a problem in memory-unsafe languages?
    No. Memory safety removes the corruption outcome but not the class. In a managed language a wrap still produces a negative index (crash/DoS), a wrapped total in a billing or quota calculation, a wrapped counter that resets a rate limiter, or a timestamp that rolls past its epoch so a session never expires. The consequence is decided by what the value authorises, not by the memory model.
  • Why is unsigned arithmetic not automatically safer than signed?
    Unsigned wrap is well defined, which removes the compiler-deletes-your-check hazard, but it introduces the far commoner signedness defect: subtracting past zero yields a huge positive number, and converting a negative signed value to an unsigned size type produces a near-maximal length. Well-defined and safe are different properties — you still need the result to be the mathematical one.

A scale that only shows the last three digits of the weight. Anything under 1000 kg it reports perfectly, so you trust it; a 1002 kg load reads as 2 kg and the lift you sized from that reading fails.

saying these in an interview costs you the question

  • "Just use long/int64 and it goes away" — widening moves the boundary and says nothing about the expression's real range.
  • "Modern compilers warn about it" — the defining property of the class is that it is silent; for signed C the optimiser may remove the check instead.
  • Treating it as a C-only problem, when Java, Go and C# wrap just as silently and JavaScript loses exactness without wrapping at all.
  • Calling it a correctness nit rather than a security class, ignoring that the attacker chooses the operands.
  • Assuming a runtime exception is guaranteed — only some languages/build modes trap.

context