Conversation
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head Reproducer, run from a import shutil, struct
from elftools.elf.elffile import ELFFile
shutil.copy("../binaries/tests/x86_64/cpp_qualified_symbols.so", "no_dynstr.so")
with open("no_dynstr.so", "r+b") as f:
e = ELFFile(f)
i = next(i for i, s in enumerate(e.iter_sections()) if s["sh_type"] == "SHT_DYNAMIC")
f.seek(e["e_shoff"] + i * e["e_shentsize"] + 40) # sh_link in Elf64_Shdr
f.write(struct.pack("<I", 0))
f.seek(18) # e_machine -> EM_NONE
f.write(struct.pack("<H", 0))
import cle
cle.Loader("no_dynstr.so", auto_load_libs=False, main_opts={"backend": "elf"})
Notes:
Corpus measurement of the open queue, 2026-08-15 — this change clears none of the class it was filed againstCorrecting the record. The open pull-request queue was scored against 733 objects drawn from a sweep's own failing units (35 error classes, 49 architectures, 16 containers), with each repository's current master as the baseline rather than the revisions the sweep pinned. Each object is loaded with The class this PR was filed against is the message-less That also refines the last bullet above. These objects do not go on to stop in The change still stands on its own terms — pyelftools reporting a missing dynamic string table with a bare Re-keyed 2026-08-28. The figures above were measured at |
|
Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_731 |
e0ee7e4 to
faf9706
Compare
…RELRO MetaELF.extract_soname() and _get_relro() both walk a dynamic table with an unfiltered iter_tags(), which makes pyelftools resolve the dynamic string table for every tag it yields. An object whose .dynamic section has sh_link 0 has no string table to resolve, and pyelftools reports that with a bare assert rather than an ELFError, so it escaped extract_soname()'s ELFError handler and killed Loader.__init__() and Loader.find_object() outright. Both are best-effort heuristics with a defined "cannot tell" answer, so give it instead of raising. extract_soname() also asks for DT_SONAME specifically, which lets an object that has no soname still fall back to its basename.
faf9706 to
2cbeef7
Compare
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Full soname and RELRO report before and after this change. The script copies Before — every query dies with a message-less cle at the merge base, 46a3733After — the heuristics answer, and the with this change, 2cbeef7 |
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS How much of a corpus's failure surface this removes, measured rather than Sample. 12,000 objects drawn uniformly at random, from a seeded permutation, Method. Each object is loaded with the catalogue's declared load recipe and Before. 53 / 11,989 = 0.44% of the sample (CI 0.34–0.58) die on After. This head clears all 8 of the Residual. The 45 at Control. 722 objects that already reached CFG on The corpus is not redistributable, so its objects are described by architecture, session: sharpen |
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
Problem
An ELF whose
.dynamicsection hassh_link0 names no dynamic string table, and pyelftools reports that with a bareassert. The message-lessAssertionErrorescapesMetaELF.extract_soname, so on a copy oftests/x86_64/cpp_qualified_symbols.sowith that one header field cleared:Loader.find_object()runs that heuristic over files it never loaded, so it dies too, and_get_relro()fails the same way a moment later. Both run before the architecture is resolved, so an object that is broken in some other way — here one whosee_machineis alsoEM_NONE— never reaches its real diagnosis.Root cause
extract_sonameiterated the whole dynamic table to find one tag:pyelftools resolves the dynamic string table for every tag it yields, so an object with no string table cannot be iterated at all, even though
DT_SONAMEis the only tag here that needs a string. The surrounding handler wasexcept elftools.common.exceptions.ELFError, which anAssertionErrorwalks straight past._get_relrohas the identical shape in[tag for tag in dyn_sec.iter_tags() if tag.entry.d_tag == "DT_FLAGS"], andDT_FLAGSis a number.Fix
Both are best-effort heuristics with a defined "cannot tell" answer, so they now give it.
extract_sonameasks forseg.iter_tags("DT_SONAME"), so no other tag's strings are resolved, and an object with no soname never needs a string table at all;_get_relroreturnsRelro.PARTIAL, which is what not being able to confirmBIND_NOWalready means. The handler is widened toexcept Exceptiondeliberately: which ofELFErrorandAssertionErrora malformed file produces has already changed between pyelftools releases.Nothing tries to recover a soname from a broken string table, and such an object still will not load, since
ELF.__register_dyn()genuinely needs the strings — it now fails on that.Testing
tests/test_soname.py::test_extract_soname_without_dynamic_strtabassertsMetaELF.extract_soname(no_soname) == "cpp_qualified_symbols.so";::test_extract_soname_reads_dt_sonamepins the unmutatedtests/x86_64/liblzma.so.5.6.1still answeringliblzma.so.5;::test_load_without_dynamic_strtabpins that the load fails in the architecture lookup. All three build their inputs by zeroing that one header field in copies of binaries already onangr/binariesmaster, and all three fail on the merge base.Validation: #731 (comment)
session: sharpen