Skip to content

Add two i386 ELFs for CLE's linked-image ELF handling - #219

Open
zardus wants to merge 3 commits into
masterfrom
feature/elf-linked-image-fixtures
Open

Add two i386 ELFs for CLE's linked-image ELF handling#219
zardus wants to merge 3 commits into
masterfrom
feature/elf-linked-image-fixtures

Conversation

@zardus

@zardus zardus commented Aug 29, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Problem

cle has no test input for a linked image that keeps .hash and .dynsym after
its linker script discarded .dynstr. Every u-boot build has that shape: the
linker leaves sh_link = 0 behind, pyelftools refuses to build the two
sections, and cle answers by throwing the whole section header table away,
.symtab and its 11631 symbols with it.

Root cause

None of the 826 ELF files here has a section whose sh_link points at the wrong
section type, and none keeps its link-time .rel sections in an image that is
already linked. Both are ordinary for embedded firmware and absent from this
corpus.

Fix

Two i386 ELFs, both freestanding -- no libc, no start files -- built by clang
and GNU ld from sources in tests_src/elf/, and both deterministic: two builds
give the same sha256.

tests/i386/hash_without_dynstr, 5360 bytes, keeps .hash and .dynsym while
its linker script discards .dynstr, so the linker writes sh_link = 0 into
both and no ELF reader can build them.

tests/i386/linked_with_emit_relocs, 5204 bytes, has an ordinary section table
but is linked at 0x1000 rather than at 0 and keeps its link-time .rel.text
and .rel.data through --emit-relocs, the way u-boot's build does.

Testing

cle's new tests/test_elf_resiliency.py and
tests/test_linked_image_relocations.py load them. On current cle the first
image loads with no sections and no symbols, and the second comes back with 5
bytes of .text rewritten and its function-pointer table reading 0x2020, 0x2040 for functions at 0x1020 and 0x1040.

Consumed by angr/cle#802, and by the relocation fix it should land behind, cle PR 801. Validation: #219 (comment)

session: sharpen

@zardus

zardus commented Aug 29, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head 9f5433b0e1f3418fc23c723af4d4883f5a880e7b against baseline d4ffa2f06ee274779244b95816a0a4fa739d262c.

Both fixtures rebuild byte-identically. Running each build.sh twice gives the same sha256:

9c2bf2889fc95fd60a4b01dacd5d823acb5875c13077840026952cd4dd263fac  tests/i386/hash_without_dynstr
5ee80c7bf09b970d224f018dd948a73e8c3c5305eb5f883032a394b2247bbd90  tests/i386/linked_with_emit_relocs

Nothing in the build reads a clock, a path or a VCS: clang --target=i386-unknown-linux-gnu -c -fPIC -O1 -ffreestanding -fno-asynchronous-unwind-tables and ld -m elf_i386 -pie --emit-relocs -T table.lds, with no libc and no start files. 5360 and 5204 bytes.

What each carries, read out of the section headers with struct:

  • hash_without_dynstr: .hash with sh_link = 7 and .dynsym with sh_link = 0, both pointing at something that is not a symbol table, plus a valid .symtab of 16 entries. pyelftools raises ELFError on both.
  • linked_with_emit_relocs: an ordinary section table that every reader parses, .text at 0x1000, non-allocated .rel.text and .rel.data from --emit-relocs, and an allocated .rel.dyn of two R_386_RELATIVE entries covering the function-pointer table in .data.

Consumed by angr/cle#802 and angr/cle#801. On current cle the first loads with 0 sections and 0 symbols, and the second comes back with 5 of its 128 .text bytes rewritten and its pointer table reading 0x2020, 0x2040 for functions at 0x1020 and 0x1040.

The complete angr workspace gate was run on each consuming branch with this head checked out as the binaries worktree, and passed: cle 243/244 passed with 9 skipped, angr 2530 passed with 46 skipped and 2 xfailed, angr Rust 35 passed, every configured pre-commit hook, and the test-inputs check that no test input lives outside this repository.

This repository has no executable suite of its own.

Rebased 2026-08-29. Re-keyed onto binaries master d4ffa2f06ee274779244b95816a0a4fa739d262c. The opening named 756427a171c37695d5c910b2086ce21847a25b2f on baseline a87538bc248531d2a1d22434143e8b9752459bac; the branch has since passed through cf3cfdeba0f2a929551b36483dce80b30271e0c6 on baseline 9d9d9e0079a1f67e99046f1ebb9d4625b41152de. git range-diff a87538bc248531d2a1d22434143e8b9752459bac..756427a171c37695d5c910b2086ce21847a25b2f d4ffa2f06ee274779244b95816a0a4fa739d262c..9f5433b0e1f3418fc23c723af4d4883f5a880e7b reports the commit unchanged and the two git diff <merge-base>..<head> outputs are byte-identical, so the figures above describe the same two fixtures on a new base. The branch was moved because a pull request that pins this one is checked out at its branch tip rather than merged with master, so being behind master was failing unrelated tests on the consumer. This repository schedules no checks: at the new head both check-runs and actions/runs report 0, as they do for every angr/binaries pull request.

@zardus

zardus commented Aug 29, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

What the two fixtures show, loaded with cle.Loader(path, auto_load_libs=False)
on cle master and on the branches that consume them.

Beforehash_without_dynstr loses its whole section header table because
.hash and .dynsym cannot be built, and linked_with_emit_relocs comes back
with .text rewritten and its function-pointer table 0x1000 past the
functions:

cle master
$ python -c 'import cle; o=cle.Loader("binaries/tests/i386/hash_without_dynstr", auto_load_libs=False).main_object; print([s.name for s in o.sections]); print(sorted(s.name for s in o.symbols if s.is_function))'
ERROR:cle.backends.elf.elf:PyReadELF couldn't load this file. Trying again without section headers...
sections: []
function symbols: []

$ python probe_linked_with_emit_relocs.py
WARNING:cle.backends.elf.relocation:Unknown reloc 9 on X86
relocations registered: 8
.text matches the file: False
entries[]: ['0x2020', '0x2040']
table_entry_one: 0x1020
table_entry_two: 0x1040

After — the section table survives, and the loaded image is the file's:

with the consuming branches
$ python -c 'import cle; o=cle.Loader("binaries/tests/i386/hash_without_dynstr", auto_load_libs=False).main_object; print([s.name for s in o.sections]); print(sorted(s.name for s in o.symbols if s.is_function))'
WARNING:cle.backends.elf.elf:Section .dynsym is malformed; loading it without interpreting its contents.
WARNING:cle.backends.elf.elf:Section .hash is malformed; loading it without interpreting its contents.
sections: ['', '.text', '.rel.text', '.data', '.rel.data', '.got.plt', '.bss', '.hash', '.dynsym', '.rel.dyn', '.symtab', '.strtab', '.shstrtab']
function symbols: ['_start', 'table_entry_one', 'table_entry_two', 'table_helper']

$ python probe_linked_with_emit_relocs.py
relocations registered: 2
.text matches the file: True
entries[]: ['0x1020', '0x1040']
table_entry_one: 0x1020
table_entry_two: 0x1040

@zardus
zardus force-pushed the feature/elf-linked-image-fixtures branch from 756427a to cf3cfde Compare August 29, 2026 18:57
hash_without_dynstr keeps .hash and .dynsym while its linker script discards
.dynstr, so the linker writes sh_link = 0 into both and no ELF reader can build
them. linked_with_emit_relocs has an ordinary section table but is linked at
0x1000 rather than at 0 and keeps the link-time .rel.text and .rel.data that
--emit-relocs left behind. Both shapes are ordinary u-boot output and neither
was represented here.

Freestanding, so no libc and no start files: clang and GNU ld build both from
tests_src/elf, and each build.sh is deterministic.
@zardus
zardus force-pushed the feature/elf-linked-image-fixtures branch from cf3cfde to 9f5433b Compare August 29, 2026 21:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant