https://sourceware.org/bugzilla/show_bug.cgi?id=34159
Bug ID: 34159
Summary: Heap-buffer-overflow WRITE in BFD `fr30_elf_i32_reloc`
via crafted FR30 ELF (`nm -l` / `objdump -g`)
Product: binutils
Version: unspecified
Status: UNCONFIRMED
Severity: normal
Priority: P2
Component: binutils
Assignee: unassigned at sourceware dot org
Reporter: junrong at calif dot io
Target Milestone: ---
Created attachment 16719
--> https://sourceware.org/bugzilla/attachment.cgi?id=16719&action=edit
Includes PoC and full bug report
## Summary
A heap-buffer-overflow **write** exists in the FR30 BFD backend at
[`bfd/elf32-fr30.c:309`
(`fr30_elf_i32_reloc`)](https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=bfd/elf32-fr30.c;hb=1ba40beb4c5fe20652fd886e42146e9fa5bb01b1#l309).
The `R_FR30_48` (and `R_FR30_20`) special-function reloc handlers write a
4-byte value at `data + reloc_entry->address` without calling
`bfd_reloc_offset_in_range()`, which `bfd_perform_relocation` explicitly
delegates to special functions. A 475-byte FR30 relocatable ELF with `r_offset
= 0x100` against an 8-byte `.debug_info` section triggers an
attacker-controlled-offset, attacker-controlled-value 4-byte heap write when a
user runs `nm -l` or `objdump -g` on the file. The sibling handler
`fr30_elf_i20_reloc` (line 269–271) has the identical defect.
> Discovered by Claude (Anthropic). Confirmed and reported by Calif
> ([calif.io](http://calif.io)).
## Technical Details
`bfd_perform_relocation()`
([reloc.c:694–698](https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=bfd/reloc.c;hb=1ba40beb4c5fe20652fd886e42146e9fa5bb01b1#l694))
intentionally skips its own range check when a `howto->special_function` is
present, with the comment *"It is up to the special_function itself to call
bfd_reloc_offset_in_range if needed."* Neither `fr30_elf_i20_reloc` nor
`fr30_elf_i32_reloc` performs this check before dereferencing `data +
reloc_entry->address`.
Attack chain:
1. Crafted ELF32 MSB object with `e_machine = EM_CYGNUS_FR30`, an 8-byte
`.debug_info` section, and a `.rela.debug_info` entry `{r_offset=0x100,
r_type=R_FR30_48, r_sym=1, r_addend=0}`.
2. `nm -l` (or `objdump -g`/`-W`) reads DWARF line info →
`_bfd_dwarf2_slurp_debug_info` → `read_section` →
`bfd_simple_get_relocated_section_contents` allocates an 8-byte heap buffer for
`.debug_info` and applies relocations via
`bfd_generic_get_relocated_section_contents`
([reloc.c:8165](https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=bfd/reloc.c;hb=1ba40beb4c5fe20652fd886e42146e9fa5bb01b1#l8165)).
3. `bfd_perform_relocation` dispatches to `fr30_elf_i32_reloc`, which executes
`bfd_put_32(abfd, relocation, data + reloc_entry->address + 2)`
([elf32-fr30.c:309](https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=bfd/elf32-fr30.c;hb=1ba40beb4c5fe20652fd886e42146e9fa5bb01b1#l309))
— a 4-byte big-endian store **258 bytes past** the 8-byte allocation.
4. Both the offset (`r_offset`, full 32-bit) and the value written
(`symbol->value + section vma + r_addend`, all from the input file) are
attacker-controlled, yielding a constrained write-what-where on the heap.
Under ASan the wild write lands in either a freed chunk (reported as
heap-use-after-free WRITE under `nm -l`) or a redzone (heap-buffer-overflow
WRITE under `objdump -g`); both are the same primitive.
## Reproduce
```bash
git clone --depth=1 git://sourceware.org/git/binutils-gdb.git && cd
binutils-gdb
git checkout 1ba40beb4c5fe20652fd886e42146e9fa5bb01b1
CC=gcc CFLAGS=’-fsanitize=address -g -O1’ LDFLAGS=’-fsanitize=address’ \
./configure --enable-targets=all --disable-gdb --disable-gdbserver \
--disable-sim --disable-gprofng --disable-werror
make -j MAKEINFO=true
./binutils/nm-new -l poc_ANT-2026-03091.bin
# or
./binutils/objdump -g poc_ANT-2026-03091.bin
```
Key ASan output (`objdump -g`):
```
==39621==ERROR: AddressSanitizer: heap-buffer-overflow on address
0xffffa22007f2 ...
WRITE of size 1 at 0xffffa22007f2 thread T0
#0 bfd_putb32 bfd/libbfd.c:955
#1 fr30_elf_i32_reloc bfd/elf32-fr30.c:309
#2 bfd_perform_relocation bfd/reloc.c:698
#3 bfd_generic_get_relocated_section_contents bfd/reloc.c:8165
#4 bfd_get_relocated_section_contents bfd/bfd.c:2769
#5 bfd_simple_get_relocated_section_contents bfd/simple.c:276
#6 load_specific_debug_section objdump.c:4341
SUMMARY: AddressSanitizer: heap-buffer-overflow bfd/libbfd.c:955 in bfd_putb32
```
Full sanitizer logs (both `nm -l` and `objdump -g`, before/after patch) are in
`poc_ANT-2026-03091_output.txt`.
## Impact
- **Affected:** GNU Binutils (BFD library) through current git HEAD
`1ba40beb4c5f` (2026-04-24); any build configured with `--enable-targets=all`
or targeting `fr30-*-elf` (this includes distro `binutils-multiarch` packages).
- **Prerequisite:** Victim runs `nm -l`, `objdump -g`/`-W`/`-S`, `addr2line`,
or any libbfd consumer that resolves DWARF debug info on an attacker-supplied
ELF object.
The primitive is a 4-byte heap write at a fully attacker-chosen 32-bit offset
from a small fresh allocation, with attacker-chosen contents. This is
materially stronger than the typical binutils OOB-read fuzz finding. FR30 is an
obscure embedded target, which limits real-world exposure, but the code path is
reachable in any all-targets build via the stock CLI with no special flags
beyond `-l`/`-g`.
As the affected FR30 parser is not enabled in default builds of binutils, and
no escalation of privileges is observed, this issue is being reported as a
regular public bug instead of a security bug.
## Recommendations
Add the mandatory `bfd_reloc_offset_in_range` check to both FR30
special-function handlers, matching the established pattern in `elf32-bfin.c`,
`elf32-m32r.c`, `elf32-ppc.c`, etc.:
```diff
diff --git a/bfd/elf32-fr30.c b/bfd/elf32-fr30.c
index 1460aed7..dc805596 100644
--- a/bfd/elf32-fr30.c
+++ b/bfd/elf32-fr30.c
@@ -266,6 +266,10 @@ fr30_elf_i20_reloc (bfd *abfd,
if (relocation > (((bfd_vma) 1 << 20) - 1))
return bfd_reloc_overflow;
+ if (!bfd_reloc_offset_in_range (reloc_entry->howto, abfd, input_section,
+ reloc_entry->address))
+ return bfd_reloc_outofrange;
+
x = bfd_get_32 (abfd, (char *) data + reloc_entry->address);
x = (x & 0xff0f0000) | (relocation & 0x0000ffff) | ((relocation &
0x000f0000) << 4);
bfd_put_32 (abfd, (bfd_vma) x, (char *) data + reloc_entry->address);
@@ -306,6 +310,10 @@ fr30_elf_i32_reloc (bfd *abfd,
+ symbol->section->output_offset
+ reloc_entry->addend;
+ if (!bfd_reloc_offset_in_range (reloc_entry->howto, abfd, input_section,
+ reloc_entry->address + 2))
+ return bfd_reloc_outofrange;
+
bfd_put_32 (abfd, relocation, (char *) data + reloc_entry->address + 2);
return bfd_reloc_ok;
```
> Patch verified: PoC no longer triggers after applying — `nm -l` exits 0 with
> clean output; `objdump -g` reports `Can’t get contents for section
> ’.debug_info’.` and exits 0.
## Artifacts
| filename | purpose |
| --- | --- |
| `poc_ANT-2026-03091.bin` | 475-byte FR30 ELF reproducer |
| `poc_ANT-2026-03091_output.txt` | Full ASan output (`nm -l`, `objdump -g`,
readelf) before & after patch |
| `fix_ANT-2026-03091.patch` | Unified diff against binutils-gdb @
`1ba40beb4c5f` |
| `run_poc.sh` | Self-contained clone+build+reproduce script (`--patched` /
`--skip-build`) |
| `README.md` | Quick-start for reviewers |
--
You are receiving this mail because:
You are on the CC list for the bug.