Component: libiberty / cp-demangle.c
Version: binutils 2.46
CWE: CWE-190 (Integer Overflow)
Summary
`d_compact_number()` in `libiberty/cp-demangle.c:3269` triggers signed
integer
overflow (undefined behavior per C11 §6.5/5) when `d_number()` returns
`INT_MAX` (2147483647). The expression `num = d_number(di) + 1` wraps to a
negative value on two's-complement targets, which is then caught by the
`num < 0` guard, but a conforming optimizer may legally assume signed
overflow
never occurs and elide that guard entirely.
Affected code
```c
/* cp-demangle.c:3261 */
static int
d_compact_number (struct d_info *di)
{
int num;
if (d_peek_char (di) == '_')
num = 0;
else if (d_peek_char (di) == 'n')
return -1;
else
num = d_number (di) + 1; /* ← UB when d_number() == INT_MAX */
if (num < 0 || ! d_check_char (di, '_'))
return -1;
return num;
}
```
`d_number()` (line 1875) uses the guard:
```c
if (ret > ((INT_MAX - (peek - '0')) / 10))
return -1;
```
This allows `ret` to reach exactly `INT_MAX`. For instance, the digit
sequence
`"2147483647"`: on the last iteration `ret = 214748364`, `peek = '7'`, the
guard
evaluates to `214748364 > (2147483640 / 10)` → `214748364 > 214748364` →
false,
so parsing proceeds to `ret = 2147483647`.
Reproducer
Trigger input:a mangled C++ symbol with template parameter number
2147483647:
```
_Z1fIT2147483647_Ev
```
Build with UBSan:
```bash
cd binutils-2.46
mkdir build-ubsan && cd build-ubsan
CFLAGS="-fsanitize=signed-integer-overflow
-fno-sanitize-recover=signed-integer-overflow -g" \
LDFLAGS="-fsanitize=signed-integer-overflow" \
../configure --disable-werror --quiet
make all-binutils
```
Reproduce:
```bash
$ echo '_Z1fIT2147483647_Ev' | ./binutils/cxxfilt
libiberty/cp-demangle.c:3269:9: runtime error: signed integer overflow:
2147483647 + 1 cannot be represented in type 'int'
```
The same overflow triggers through any demangling entry point:
```bash
$ ./binutils/nm-new -C crafted.o # ELF object containing the symbol
libiberty/cp-demangle.c:3269:9: runtime error: signed integer overflow:
2147483647 + 1 cannot be represented in type 'int'
```
Impact
On current two's-complement targets with current compilers, the overflow
wraps
to `INT_MIN` which is `< 0`, so the guard on line 3271 catches it and the
demangle simply fails (returns the mangled name unchanged). The practical
impact
today is nil.
However:
1. Optimizer exploitation: A conforming compiler may assume signed overflow
is unreachable and optimize away the `num < 0` check (GCC and Clang have
exploited similar UB assumptions, cf. CVE-2009-1897). This would allow a
large positive `num` to propagate into `d_make_template_param`,
potentially
causing out-of-bounds access in the template parameter array.
2. Reachability from untrusted input: The code path is reachable from any
tool that demangles attacker-controlled symbols: `c++filt`, `nm -C`,
`objdump -C`, `addr2line -C`, processing crafted ELF objects or symbol
tables.
Suggested fix
Option A: tighten `d_number`'s guard to reject `INT_MAX`:
```c
/* d_number(), around line 1875 */
if (ret > ((INT_MAX - (peek - '0')) / 10))
return -1;
ret = ret * 10 + (peek - '0');
+if (ret == INT_MAX)
+ return -1;
```
Option B: perform the addition safely in `d_compact_number`:
```c
/* d_compact_number(), line 3269 */
- num = d_number (di) + 1;
+ num = d_number (di);
+ if (num == INT_MAX)
+ return -1;
+ num = num + 1;
```
Either fix is a one- or two-line change with no semantic impact on valid
inputs
(no valid mangled symbol references template parameter 2^31).
Discovery method
Found by applying the squeeze loop strategy ("The Squeeze Loop Strategy:
Catching Coherent-and-Wrong Artifacts with an Author-Independent
Executable Oracle," Zenodo 10.5281/zenodo.20787816, 2026) on its C
terrain — the deductive-verification analogue of the paper's Rust
application. The strategy pins each actor between a soft upper bound (a
citable authority — here the C standard and the demangler's
documented contract) and a hard executable lower bound it cannot alter (here
frama-c -wp -wp-rte), and is built to catch the coherent-and-wrong
artifact: code
that passes its own checks while violating the intended property. The move
that
turns the verifier into a bug-finder is to specify the parser for total
absence of
runtime errors with no input-restricting precondition; an RTE obligation
that then
fails to discharge is not a proof gap but a pointer at a reachable defect,
and the
guard that discharges it is the fix.