Code & binaries · Code anatomy
The bit that fell off.
From an eight-bit sum to a C allocation: how arithmetic quietly changes the size of a security boundary.
Kinwilder research desk · · AI-assisted analysis
Code specimen
Kinwilder illustration · GCC checked arithmetic · not compiled in this edition
size_t bytes;
if (__builtin_mul_overflow(count, sizeof *items, &bytes)) {
return SIZE_ERROR;
}
if (bytes > application_limit) return SIZE_ERROR;
/* Allocate only after both arithmetic and policy checks. */- Line 2: Preserve the mathematical question — The builtin checks whether the product fits the destination type. It returns true on overflow.
- Line 3: Reject the wrapped size — Do not allocate or keep using the result after an overflow report.
- Line 5: Representable is not affordable — A size may fit size_t while being far beyond the application’s memory budget.
The smallest useful picture of an integer-overflow bug fits in one byte. Start with eight one-bits. Add one. The mathematical answer needs nine bits. Keep only eight and the stored value becomes zero.
That example describes a width-limited unsigned result. It is not a statement that every C expression on an eight-bit variable is evaluated at eight bits: C integer promotions are part of the story. Nor should it be generalized to signed overflow, whose language rules differ. The picture is a way to see the missing information.
From a count to an allocation
An application receives a count, multiplies by an element size, allocates the resulting number of bytes, and then processes the original count. If the allocation-size calculation wraps but the later work still follows the original count, those two operations disagree about the available space. A representable small number can hide an unrepresentable large request.
Our C specimen is an original defensive sketch, not extracted exploit code. It uses GCC's __builtin_mul_overflow to compute and check the product. GCC documents the overflow builtins as using an infinite-precision operation followed by conversion to the destination type, with a Boolean indicating whether the result can be represented. GCC may implement these checks using hardware flags and conditional instructions. GCC documentation.
The names count, items, SIZE_ERROR, and application_limit stand for application-specific values. This fragment has not been compiled as a complete program in this edition. It omits allocation, initialization, and error-handling details to keep the arithmetic boundary visible.
Read the machine code as a question
When reviewing compiler output, look for how the generated instructions distinguish the low-width result from evidence that significant bits were lost. The exact sequence depends on architecture, operand types, optimization, and compiler version. An x86 carry flag, an overflow flag, and a multiply's high result are not interchangeable concepts; first determine whether the source operation is signed or unsigned and which width it promises.
Do not infer a missing check from one isolated instruction. Follow the value and the relevant flags through branches to the operation that consumes the size. Conversely, seeing a conditional jump somewhere nearby does not establish that it checks the arithmetic you care about. The relationship between the producer and consumer is the evidence.
Two independent limits
The first limit is representability: does the result fit the selected type? The second is policy: should this request be allowed to consume that many resources? A successful overflow check does not stop an otherwise valid multi-gigabyte allocation from exhausting a service.
Our recommendation is to carry a checked size forward instead of recomputing it later with different types. Validate before narrowing conversions. Keep the resource budget next to the point where the resource is consumed. Review failure paths for accidental retries that discard the check.
A small lab with a precise outcome
In a local test project, compile a complete checked-size function for your supported targets. Exercise zero, one, the largest count that fits the chosen element width, and the next count. Assert the Boolean result and avoid using the output size on failure. Then test a value that is mathematically valid but exceeds the application's budget.
Inspect the generated assembly with the compiler version and flags recorded. Change the destination width and compare the result. This builds an intuition you can use in native parsers, image loaders, serialization code, and foreign-function boundaries without requiring a live target or an exploit.
This article explains a vulnerability class and a defensive API. It does not identify a new vulnerability in a particular product. The discipline is to keep mathematical size, stored size, and permitted size distinct until the program has proved they agree.