FForge
← Labsdebug-12Runtime debugging●●●○

Signed integer overflow (UB)

debug/12_signed_overflow.c3 steps
0/3 steps
evidence to leave with
UBSan runtime error; -O2 changes the observable result
walk it, one step at a time
run this
$ clang -g -O0 12_signed_overflow.c -o bin/12_signed_overflow
$ ./bin/12_signed_overflow ; echo "exit=$?"
what it printed on the machine that wrote this lab
y = -2147483648
exit=0
^ No crash, no warning at runtime. The wrong answer is the only symptom.

gdb runs to a clean exit; `print y` would just show -2147483648. There is no signal and no invalid memory, so gdb has nothing to catch. Arithmetic UB is a job for a sanitizer, not a debugger.

run this
$ clang -g -O0 -fsanitize=undefined 12_signed_overflow.c -o bin/12_signed_overflow.ubsan
$ ./bin/12_signed_overflow.ubsan ; echo "exit=$?"
what it printed on the machine that wrote this lab
  12_signed_overflow.c:75:15: runtime error: signed integer overflow:
      2147483647 + 1 cannot be represented in type 'int'
  SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior 12_signed_overflow.c:75:15
  y = -2147483648
  exit=0
  How to read it:
    "signed integer overflow: 2147483647 + 1 cannot be represented in type
     'int'" -> the exact operation and operands that overflowed, at line
     75 column 15. UBSan instruments arithmetic and tells you in plain
     English. By default it PRINTS and CONTINUES (so you still see y and
     exit 0); add -fno-sanitize-recover=undefined to make it abort instead.

  And the contrast that proves the point:
run this
$ valgrind -q ./bin/12_signed_overflow
what it printed on the machine that wrote this lab
  y = -2147483648
  (valgrind says NOTHING.)
  META-LESSON: valgrind/memcheck only sees MEMORY errors. Signed overflow is
  a pure arithmetic/UB issue with no bad memory access, so memcheck is blind
  to it. Sanitizers and valgrind are COMPLEMENTARY, not interchangeable —
  pick by the SYMPTOM (see the README cheat-sheet).

ROOT CAUSE vs CRASH SITE:
  There is no crash. The "site" and the cause are the same expression, but
  the deeper cause is trusting that `INT_MAX + 1` wraps. It is UB; treat it
  as a logic error to be prevented (check before adding), not a wrap to
  exploit.
category

UB / arithmetic

difficulty

2 ------------------------------------------------------------

what's wrong

`x = INT_MAX` then `x + argc`. Since argc >= 1, this is INT_MAX + 1, which overflows a signed int. Signed overflow is Undefined Behavior in C (unlike UNSIGNED, which wraps by definition). Buggy line: `int y = x + argc;`. (argc is used so the compiler can't constant-fold it away.)

symptom

SILENT. On this machine it "wraps" to INT_MIN and prints -2147483648, exit 0 — it LOOKS like it works. But it is UB: the compiler is entitled to assume signed overflow never happens and optimize accordingly, so at -O2 the same code can do something entirely different. Never rely on the wrap.

the fix

Check before the operation, or use a wider/unsigned type deliberately: if (x > INT_MAX - argc) { ...handle the overflow... } long y = (long)x + argc; // compute in a wider type

take it further

Rebuild at -O2 with `-fsanitize=undefined` and also try `-fno-sanitize-recover=undefined` so UBSan ABORTS (exit != 0) on the overflow — exactly what you want in CI to fail the build on UB. ============================================================

read the source · debug/12_signed_overflow.c
#include <limits.h> #include <stdio.h> int main(int argc, char **argv) { int x = INT_MAX; int y = x + argc; /* <-- THE BUG: INT_MAX + (argc>=1) overflows */ printf("y = %d\n", y); (void)argv; return 0; }
◇
Finish the lab: write the evidence note

A lab is done when you can say what it showed on your machine — not when you have read it.