Conversation
PE._load_symbols_from_coff_header walks the COFF symbol table some toolchains still leave in a linked image and turns each symbol into an RVA by adding its value to the start of the section its record names. The section number came out of the file and nothing checked it against the number of sections the image has, so a symbol naming a section that is not there raised IndexError and the load failed. Bounding the index and skipping that one record is not enough. On both images this was measured on, a table that names a missing section does so wholesale and its remaining numbers do not describe this image either: over half of all records name a missing section, the values are already image RVAs rather than section offsets, and adding a section address to them puts the entry-point symbol 0x1000 past the entry point. Skipping only the out-of-range records loads a hundred symbols of which a dozen fall outside the image. So treat it the way this function already treats a symbol table that runs past the end of the file: warn, load no symbols from it, and leave the image's imports to speak for it. Symbols are collected and added after the walk so a table condemned part-way through leaves none behind. Nothing that loads today reaches the new branch, because reaching it is exactly the state that raises IndexError. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS The two images, on master, on a bare bounds check, and on this branchBoth objects come from a corpus whose redistribution is prohibited, so the probe Before cle at 0e77adeThe bound-and-skip variant, for comparison the smaller fix: bound the index, skip the recordAfter this branchThe two
The middle arm is why a bounds check is not the fix. It loads, and produces 104 Nothing else movesEvery MZ file under Why the regression is a unit test and not a fixtureOf Truncating a tracked fixture cannot produce one, for a reason that does not A wider survey finds none either: 183,459 distinct PE objects, 28,911 of them
The 2,031 non-generated ones are 1,411 Wine PEs, 561 from MSYS2, 30 Windows |
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head Local gateAll fourteen suites ran; none was skipped. Twelve pass. The two that fail are the
The two failures. Neither touches cle, and both were checked rather than
Neither failure involves The gate builds every package from the worktrees and runs the tests against the The hosted Lint and Typecheck jobs, predicted locallyCI scores each changed file with pylint and counts pyright errors in it, and fails The hosted jobs that collect the new test are The regression fails on master for the reason claimedSame test file, two store builds, the branch's and master's: Truncating a tracked fixture cannot produce this shapeEvery tracked PE with a COFF symbol table, truncated at 1,582 lengths between them, The margin is never positive, and the reason is structural rather than a property of Nothing already tracked changesEvery MZ file under The second line is the same harness, unchanged, over the two objects that fail today, |
|
Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_832 |
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
Problem
cle.LoaderraisesIndexErroron a PE whose retained COFF symbol table names asection the image does not have.
The two images it was measured on are 32-bit
Machine=0x14cPEs with foursections whose symbol table names sections 5 and 6. Both come from a
corpus whose redistribution is prohibited, so there is no committed binary; the
regression is a unit test and Testing says what else was measured.
Root cause
The section number in a symbol record is read out of the file as a signed short
and used as an index:
section > 0rejects the reserved values --IMAGE_SYM_UNDEFINEDis 0,ABSOLUTEis -1,DEBUGis -2 -- and nothing checks the other end.Bounding the index and skipping that record stops the crash and leaves a worse
problem behind. On both of these images the table is not numbered for the image
at all:
section, and 208 of image B's 415.
_mainCRTStartupcarries
Value = 0x11cb, which isAddressOfEntryPointexactly; adding.text's0x1000puts the entry symbol at0x21cb.So skipping only the out-of-range records trades one loud
IndexErrorfor ahundred quiet symbols: 104 and 108 of them, of which 14 and 13 fall outside
[min_addr, max_addr].That is two images, not a law about the format. What makes the disposition safe
regardless is below.
Fix
Treat it the way this function already treats a symbol table that runs past the
end of the file, a few lines above -- warn, and load no symbols from it:
Nothing that loads today can lose a symbol by this. The new branch needs
section > 0andsection > len(sections), which is exactly the state thatraises
IndexErroron master. An image with one stray out-of-range record in anotherwise sound table would be a reason to prefer a bare bounds check -- but such
an image does not load today either, so there is no input whose symbols get
worse.
The two images load with 38 and 36 symbols, all inside the image, from their
import tables. Symbols are collected and added after the walk so that a table
condemned part-way through leaves none behind, and
_handle_exportsgets thesame empty mapping the existing guard already hands it.
Testing
tests/test_pe_coff_symbols.py::test_coff_symbols_are_dropped_when_a_section_number_is_out_of_rangedrives
_load_symbols_from_coff_headerwith three records, the middle one naminga section the fixture does not have, and asserts both that the mapping is empty
and that the symbol built before it was not kept. It fails on master with the same
IndexErrorout ofpe.py:1235and passeshere; the file's two existing tests pass on both.
It adds no binary: it packs symbol records against the same fake
_make_pealready in that file.
There is no fixture because there is no object to make one from.
angr/binariesatfc07821ctracks 106 PEs, 23 with a COFF symbol table and 22with it in bounds. Not one of the 22 has a single record, whether or not it would
reach the indexing, that names a section past the section count. Ten of them name
their own last section and the other twelve stop short of it, the widest being a
16-section image whose table never names a section above 3.
Truncating a tracked fixture cannot produce one. Cutting a file's tail cannot
raise the section number written in a symbol record, and over the 23, truncated
at 1,582 lengths between them, it never lowered the section count either: every
truncation still entered
_load_symbols_from_coff_header, and of the 129 thatgot past the file-extent guard and walked 556,365 records between them, not one
saw a section count different from the untruncated file's.
A wider survey of 183,459 distinct PE objects finds 28,911 carrying a retained,
in-bounds COFF symbol table -- 1,411 Wine, 561 MSYS2, 30 Windows release
binaries, 12 decbench, 9 from the copy of
angr/binariesthis corpus carries at718e154d, 8 from areal-language matrix, 26,880 generated -- and 0 whose records reaching the
indexing name a section past the count. Most of that corpus is not public, so this is
evidence rather than a survey you can re-run.
Nothing else moves. Every MZ file under
angr/binariesloaded on master0e77ade3and on this branch, comparing section list, entry point,is_dotnetand the full sorted symbol list of
(name, RVA, type, is_export):The same harness unchanged over the two failing images reports 2 of 2 records
differing, so an empty diff is a result and not a broken instrument.
Validation: #832 (comment)
🤖 Generated with Claude Code
session: sharpen