There was long a tension between what the C standard requires for signed integer overflow, and what traditional C programs commonly assumed. The standard allows aggressive optimizations based on assumptions that overflow never occurs, but traditionally many C programs relied on overflow wrapping around. Although these programs did not conform to the standard, they formerly worked in practice because traditionally compilers did not optimize in such a way that would break the programs. Nowadays, though, compilers do perform these optimizations, so portable programs can no longer assume reliable wraparound on signed integer overflow.
The C Standard says that if a program has signed integer overflow its behavior is undefined, and the undefined behavior can even precede the overflow. To take an extreme example:
if (password == expected_password) allow_superuser_privileges (); else if (counter++ == INT_MAX) abort (); else printf ("%d password mismatches\n", counter);
If the int
variable counter
equals INT_MAX
,
counter++
must overflow and the behavior is undefined, so the C
standard allows the compiler to optimize away the test against
INT_MAX
and the abort
call.
Worse, if an earlier bug in the program lets the compiler deduce that
counter == INT_MAX
or that counter
previously overflowed,
the C standard allows the compiler to optimize away the password test
and generate code that allows superuser privileges unconditionally.
Here is an example derived from the 7th Edition Unix implementation of
atoi
(1979-01-10):
char *p; int f, n; … while (*p >= '0' && *p <= '9') n = n * 10 + *p++ - '0'; return (f ? -n : n);
Even if the input string is in range, on most modern machines this has
signed overflow when computing the most negative integer (the -n
overflows) or a value near an extreme integer (the +
overflows).
Here is another example, derived from the 7th Edition implementation of
rand
(1979-01-10). Here the programmer expects both
multiplication and addition to wrap on overflow:
static long int randx = 1; … randx = randx * 1103515245 + 12345; return (randx >> 16) & 077777;
In the following example, derived from the GNU C Library 2.15
implementation of mktime
(2012-03-21), the code assumes
wraparound arithmetic in +
to detect signed overflow:
time_t t, t1, t2; int sec_requested, sec_adjustment; … t1 = t + sec_requested; t2 = t1 + sec_adjustment; if (((t1 < t) != (sec_requested < 0)) | ((t2 < t1) != (sec_adjustment < 0))) return -1;
Although some of these examples will likely behave as if signed integer overflow wraps around reliably, other examples are likely to misbehave when optimization is enabled. All these examples should be avoided in portable code because signed integer overflow is not reliable on modern systems, and it’s not worth worrying about which of these examples happen to work on most platforms and which do not.