fuzz: add a target for the ELF symbol-resolution paths - #112
Open
perbu wants to merge 1 commit into
Open
Conversation
fuzz.cpp drives the loader only. section_by_name(), resolve_symbol() and
resolve() hang off AddressOf() / address_of() / resolve(), which the loader
never calls, so those three have had zero fuzz coverage -- despite
resolve() running on every guest fault, since handle_exception() calls it
to symbolise the faulting address. An adversarial ELF reaches the section
and symbol walkers with nothing more than a guest that crashes on purpose.
elfsymfuzzer calls all three with an input-derived symbol name and rip.
Two things about the harness are load-bearing:
- The image is copied into an aligned buffer rather than pointed at in
place. machine_elf.cpp casts the base pointer straight to Elf64_Ehdr*,
and libFuzzer's input is not 8-aligned once the name/rip header is
stripped, so every single input would otherwise report misaligned
access under UBSan and bury everything else.
- resolve() formats into a fixed 2048-byte stack buffer, so a returned
string at least that long means it copied past the end. That needs an
explicit assertion: the copy compiles to an inline rep movsb, which
ASan does not instrument, so a moderate over-read runs clean.
gen_elf_symbol_seeds.py writes nine minimal ELFs, each with one poisoned
header field, because bit-flipping a valid binary essentially never
produces an e_shnum past EOF or an offset that wraps. Two details worth
keeping if these are edited: the searched section must be *absent* or
section_by_name() returns on the match long before it walks off the end,
and .symtab must be 8-aligned or UBSan reports the cast before the loop
reaches the bug the seed is aiming at.
Against the current tree the seeds produce, all within a couple of seconds:
shnum_huge SEGV machine_elf.cpp:251 (#100)
shstrndx_oob SEGV machine_elf.cpp:246 (#100)
stname_wild SEGV machine_elf.cpp:278 (#100)
symtab_size_huge heap-buffer-overflow machine_elf.cpp:328 (#100)
symbol_name_4k stack-buffer-overflow machine_elf.cpp:303 (#100)
symtab_misaligned UBSan misaligned access machine_elf.cpp:277
valid, header_only, shoff_wrap clean
symtab_misaligned is not part of #100: resolve_symbol() casts a file offset
to Elf64_Sym* with no alignment check, so the ELF author picks the
alignment. Benign on x86-64, a fault on strict-alignment targets -- it is
the one seed here whose value is realised by running this target under
UBSan on AArch64.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fuzz.cppdrives the loader only.section_by_name(),resolve_symbol()andresolve()hang offAddressOf()/address_of()/resolve(), which the loader never calls — so those three have had zero fuzz coverage.That is not a niche corner:
resolve()runs on every guest fault, becausehandle_exception()calls it to symbolise the faulting address. An adversarial ELF reaches the section and symbol walkers with nothing more than a guest that crashes on purpose.elfsymfuzzercalls all three with an input-derived symbol name and rip.Results against the current tree
All nine seeds run in about two seconds:
shnum_hugemachine_elf.cpp:251shstrndx_oob:246stname_wild:278symtab_size_huge:328symbol_name_4k:303symtab_misaligned:277valid,header_only,shoff_wrapsymtab_misalignedis not part of #100.resolve_symbol()casts a file offset straight toElf64_Sym*with no alignment check, so the ELF author picks the alignment. Benign on x86-64, a fault on strict-alignment targets — this is the seed whose value is realised by running the target under UBSan on AArch64, which is the highest-value harness gap on the list.Two harness details that are load-bearing
machine_elf.cppcasts the base pointer toElf64_Ehdr*, and libFuzzer's input is not 8-aligned once the name/rip header is stripped off it. Without the copy, every input reports misaligned access and buries everything else. (I hit this; it is why the first version of this target looked like it found nothing.)resolve()'s return length is asserted against its 2048-byte format buffer. That needs to be explicit: the copy compiles to an inlinerep movsb, which ASan does not instrument, so a moderate over-read runs clean under the sanitizer.On the seeds
gen_elf_symbol_seeds.pywrites minimal ELFs with one poisoned header field each, because bit-flipping a valid binary essentially never produces ane_shnumpast EOF or an offset that wraps. Two things to preserve if these are edited, both of which cost me a debugging round:section_by_name()returns on the match long before it walks off the end;.symtabmust be 8-aligned, or UBSan reports the cast before the loop reaches the bug the seed is aiming at.The phdr-side wraps in
is_dynamic_elf()/elf_load_ph(the rest of #100) belong to theelffuzzertarget, which drives the loader — seeds for them would be dead weight here.🤖 Generated with Claude Code