Arbitrary Code Execution in objdump -g: How a Missing Bounds Check Becomes a Full Exploit
A heap out-of-bounds write in the FR30 relocation handler lets a crafted object file run attacker code when inspected with `objdump -g` — and the exploit defeats ASLR, PIE, and heap hardening in a single shot.
The researchers behind OOBdump have a track record of finding bugs in the tools developers use to find bugs — IDA Pro, Ghidra, Binary Ninja, radare2. Their latest target is objdump, specifically the -g flag that prints DWARF debug information. The result: a crafted FR30 object file that executes arbitrary code on the machine running objdump -g, while defeating ASLR, PIE, and heap hardening mitigations with a single input.
If you run binary analysis pipelines, CI toolchains, or SDK builds that use --enable-targets=all, read on.
Why Does objdump -g Even Apply Relocations?
The answer is in how object files work. When a C compiler produces a .o file, it doesn't know where each section will ultimately land in memory. It emits placeholder values and records relocations — metadata entries that say "patch this offset with the final address of that symbol." Debug sections like .debug_addr and .debug_info are no different.
Normally the linker processes these records when producing an executable. But objdump -g operates on the raw object file, before any linking happens. So BFD (the Binary File Descriptor library, the engine underlying all binutils tools) has to apply the relocations itself before it can parse and print the DWARF.
This relocation application is architecture-specific — each target defines its own relocation types and handler functions. That makes it a historically bug-prone surface in a multi-architecture library like BFD.
The Missing Bounds Check
The vulnerability lives in the FR30 relocation handler, fr30_elf_i32_reloc in bfd/elf32-fr30.c. The relevant portion:
typedef uint64_t bfd_vma;
static bfd_reloc_status_type
fr30_elf_i32_reloc (bfd *abfd, arelent *reloc_entry, asymbol *symbol,
void *data, asection *input_section, ...)
{
bfd_vma relocation =
symbol->value
+ symbol->section->output_section->vma
+ symbol->section->output_offset
+ reloc_entry->addend;
bfd_put_32 (abfd, relocation,
(char *) data + reloc_entry->address + 2);
return bfd_reloc_ok;
}
The function calculates a value to write (relocation) and then writes it via bfd_put_32 at data + reloc_entry->address + 2. data is the heap buffer holding the target section's contents. The offset comes straight from reloc_entry->address — an attacker-controlled field — with no check against the buffer's size.
Two things make this immediately exploitable:
- The attacker controls the write value:
symbol->valueandreloc_entry->addendare both read from the crafted file. - The attacker controls the write offset:
reloc_entry->addressis read from the file's relocation section with no bounds validation. - Unlimited writes: the handler fires once per relocation entry, and the file can contain as many entries as desired.
Note that Anthropic discovered this bug and shared it with the researchers before they wrote the exploit.
Turning a Heap OOB Write into Code Execution
A forward-only write (the offset is unsigned, so you can only write past the buffer, never before it) with no information leak sounds limited. The exploit clears both obstacles by targeting specific heap neighbors.
The researchers observe that each objdump -g run allocates the same heap chunks in the same order, making inter-object distances deterministic:
| Heap object | Offset from data buffer |
|---|---|
bfd struct (holds xvec function-pointer table, iostream FILE pointer) |
−8,400 bytes (behind the buffer — unreachable directly) |
arelent array (the in-memory relocation records) |
+47,440 bytes (ahead — reachable) |
Because the bfd struct is before data, the forward-only write can't reach it directly. The exploit uses the reachable arelent array as an intermediate target: by corrupting relocation metadata in that array, it can redirect subsequent relocation writes backward into the bfd struct. Once the bfd struct is under attacker control, the function pointers in xvec can be overwritten, and the next BFD operation dispatches to attacker code.
The result is a true single-shot exploit that defeats ASLR and PIE without a separate information-leak stage — a technique the researchers call Relocation-Oriented Programming.
Who Is Actually Exposed?
The FR30 is a Fujitsu embedded RISC architecture from the late 1990s. Stock, distribution-packaged objdump builds almost never enable the FR30 backend. Exposure concentrates in:
- Builds compiled with
--enable-targets=all - Explicit
fr30-*-elftarget configurations - SDK toolchains that bundle a wide-target binutils
- CI/CD images and binary-analysis environments that configure a single
objdumpto recognize every format
If your pipeline runs objdump -g against untrusted object files — malware samples, third-party libraries, fuzzer outputs — and you're using a wide-target build, you're in the realistic threat model.
Binutils' security policy explicitly excludes vulnerabilities in rarely-enabled configurations from being treated as security issues, so this was disclosed publicly rather than under embargo. The fix was applied promptly after disclosure.
What Developers Should Do
- Audit your objdump build flags. Run
objdump --versionand check whether FR30 appears in the supported targets list (objdump -ilists all enabled BFD backends). If it does and you process untrusted inputs, update your binutils. - Update binutils. The fix is already in the upstream codebase. Rebuild or upgrade if you maintain your own toolchain packages.
- Sandbox binary analysis. Processing untrusted binaries with any analysis tool — objdump, readelf, radare2, or otherwise — should happen inside a container, VM, or seccomp sandbox with no meaningful access to the host. This vulnerability is a reminder that inspection tools are an attack surface.
- Wide-target CI images are attractive targets. A
--enable-targets=allobjdump build is often the most convenient single tool for a CI pipeline that deals with cross-compiled code. That convenience trades surface area for capability; consider whether you actually need every backend enabled. - Prefer alternatives for DWARF parsing. Tools like
llvm-dwarfdumporpyelftoolsuse separate, often better-fuzzed parsers. For pipelines that only need DWARF data, not full disassembly, a dedicated DWARF tool reduces the attack surface.
The broader lesson here isn't new, but it bears repeating: tools built to analyze potentially hostile input inherit that input's attack surface. Relocation handling in a library that supports dozens of obscure architectures is exactly the kind of code that receives minimal fuzzing attention relative to the complexity it contains. The FR30 backend is decades old and rarely exercised — a perfect place for a bounds check to quietly go missing.
Sources & further reading
- Arbitrary code execution in objdump -g — blog.calif.io
Ji-ho covers the increasingly tangled overlap between cloud architecture and security, drawing on a background as a penetration tester to keep his reporting grounded in real-world attack paths. He never lets a vendor claim go unquestioned and insists that every buzzword come with a proof of concept.
Discussion 0
No comments yet
Be the first to weigh in.