Walk the chain from a silent arithmetic wraparound to an attacker's actual capability. What determines whether the same wrap yields memory corruption, an authorisation bypass, or nothing at all?
answer
- untrusted operand → silent wrap → guard passes → sink acts
- count × size wraps small, loop still writes count
- wrapped total/counter/expiry = logic bypass without memory
- reachability × privilege × value authority
- audit backwards from sinks, not forwards from arithmetic
basics
~20 sThe wrap only matters through the sink it feeds. A wrapped size drives an undersized allocation that a full-length write then overruns; a wrapped length passes a bounds test and yields out-of-range access; a wrapped total or counter defeats a quota, price or expiry decision. What the value authorises decides the impact.
solid answer
~60 sThe chain has four links: **untrusted operand → silent wrap → a check that now passes → a sink that acts on the un-wrapped intent.** The last link decides impact. - **Allocation sink**: `count * size` wraps small, the buffer is undersized, the loop still writes `count` elements — heap corruption, potentially code execution in an unmanaged runtime. - **Bounds sink**: `offset + length` wraps, the range check passes, and the read or write goes out of range — disclosure of adjacent memory or corruption. - **Decision sink**: no memory involved. A wrapped total defeats a spend or quota limit; a wrapped counter resets a rate limiter or retry cap; a wrapped timestamp makes a token permanently valid or instantly invalid; a negative index throws and takes the process down. So the same defect ranges from crash to full compromise. Rank exposure across a codebase by **reachability × privilege of the executing principal × what the value authorises**: arithmetic on a pre-auth parsing path that feeds an allocation outranks arithmetic deep behind authentication that feeds a progress bar.
go deeper
Name the chain and one concrete outcome: a size computation wraps small, the buffer is too small, the code writes the full amount past the end.
Distinguish allocation, bounds and decision sinks, and give a memory-safe example such as a wrapped quota or expiry.
Lead with the four-link chain, then triage by reachability × privilege × value authority and say you audit backwards from sinks.
Turn it into policy: enumerate the sink categories that must use checked arithmetic, put them behind reviewed helpers, and accept the harmless remainder explicitly rather than chasing every expression.
## The chain An arithmetic wrap is never the vulnerability by itself; it is a step. The chain is: 1. **An untrusted operand reaches arithmetic** — a length or count from a request, a file header, a protocol frame, a configuration value an attacker can influence. 2. **The operation wraps silently**, producing a value in the same type that no longer equals the intended quantity. 3. **A guard evaluates the wrapped value and passes**, because the wrapped value looks reasonable. 4. **A sink acts** — sometimes on the wrapped value, sometimes on the original operands. The mismatch between those two is where the capability comes from. Step 4 is the whole story for impact. Steps 1–3 are identical whether the outcome is a cosmetic glitch or remote code execution. ## Sink taxonomy **Allocation sinks.** The classic: total bytes are computed as `count * element_size`, the product wraps to something small, an allocator returns a buffer of that small size, and the population loop still iterates `count` times using the original count. The buffer is written far past its end. In an unmanaged runtime this is heap corruption — adjacent object headers and function pointers become attacker-influenced, which is the standard route to code execution. In a managed runtime the allocation itself is bounds-checked, so you get an exception rather than corruption, but a size of zero or a wrap to a negative value can also raise on allocation and take out the request — or the whole process, if it happens on a shared thread. **Bounds sinks.** A window check (`offset + length` within the buffer) is computed with wrapping arithmetic, passes, and the subsequent read or write covers memory the request had no claim to. Read side leaks adjacent heap contents — session tokens, keys, other users' data — into a response that looks well formed. Write side corrupts. **Conversion sinks.** A signed length that passed a naive upper-bound check is converted to an unsigned size type: −1 becomes the maximum. Or a 64-bit declared length is truncated into a 32-bit field for the check while the full value is used for the transfer. Both are the same class: the checked value and the used value are different numbers. **Decision sinks — no memory involved.** These matter in memory-safe stacks where people wrongly assume the class is irrelevant: - A cart total or fee computed as `price * quantity` wraps to a small or negative amount and the payment authorisation succeeds. - A quota or balance check `used + requested <= limit` wraps and grants an unbounded request. - An attempt counter or retry budget wraps to zero and resets a lockout, converting a rate limit into no rate limit. - A timestamp or duration wraps at an epoch or unit boundary (seconds vs milliseconds, 32-bit epoch): a token's expiry lands in the past or the far future, so sessions never expire or all users are logged out. - An index computed from a wrapped value goes negative, throwing on every request that reaches it — a cheap, remote, unauthenticated denial of service. **Non-sinks.** Plenty of wraps reach nothing that authorises anything: a display counter, a metric, a log line. These are correctness bugs, and treating them with the same urgency is how a hardening effort loses credibility. ## Ranking exposure across a codebase Three factors, multiplied: - **Reachability.** Can untrusted input actually reach the operands, and how far in? Arithmetic in a protocol or file-format parser runs *before* authentication and is the highest-value surface; the same arithmetic behind an admin-only screen is barely reachable. - **Privilege of the executing principal.** What the process can do when the sink misbehaves — a parser running with broad file or network access, or a routine executing with elevated database rights, converts a modest wrap into a large capability. - **Authority of the value.** What the number decides: bytes of a buffer, money, entitlement, expiry, or pixels. This ranking transfers directly to the other secure-coding classes — it is the same reachability × privilege × sensitivity triage you use to prioritise injection sinks — which is why it is worth internalising once. ## Practical consequence Do not audit for "places that might overflow"; the list is unbounded and mostly harmless. Audit **backwards from sinks**: every allocation size, every bounds computation, every money, quota, counter and expiry calculation. Then ask which of those take an operand that untrusted input can move, and make exactly those go through checked arithmetic. The audit is finite and the result is defensible.
- Your service is written in a memory-safe language. Which of these outcomes remain?Everything except memory corruption. Wrapped totals, quotas, counters and expiries still produce authorisation and billing bypasses, and a wrapped value used as an index or a size raises an exception, which on a pre-auth path is a cheap remote denial of service. Memory safety removes one sink class; it does not remove the defect.
- Where in a request's lifetime is this class most dangerous?In parsing, before authentication and authorisation run. Header, frame and length-prefix arithmetic executes on input from anyone who can reach the port, so reachability is maximal and no identity has been established to attribute or rate-limit the attempt. Fields declaring a size or count in a length-prefixed format are the canonical starting point for a review.
saying these in an interview costs you the question
- Treating every arithmetic expression as equally risky instead of triaging by sink.
- Assuming a managed runtime makes the class irrelevant — quotas, money, counters and expiries still wrap.
- Believing an undersized allocation is harmless because "the write will fail" — nothing checks it in an unmanaged runtime.
- Ignoring signed-to-unsigned conversion as a separate step in the chain.
- Rating a wrap by how easy it is to trigger rather than by what the value authorises.