Skip to content

Add COFF objects whose relocations and tables run past the file - #223

Open
zardus wants to merge 3 commits into
masterfrom
feature/coff-reloc-bounds
Open

Add COFF objects whose relocations and tables run past the file#223
zardus wants to merge 3 commits into
masterfrom
feature/coff-reloc-bounds

Conversation

@zardus

@zardus zardus commented Sep 2, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Problem

cle's COFF backend computes read and write addresses from fields in the object's own header and
checks none of them against the file, so a relocation can write outside its section and a
truncated object can leave the loader by an exception a caller cannot name. Nothing here tests
either. At this pull request's merge base, the 1068 files tracked under tests/ hold 5 COFF
objects with an I386 or AMD64 machine type, none with a relocation field outside its section or
the file, and none declaring a table the file does not hold.

Root cause

No toolchain emits these shapes, so the repository has never picked one up.

Fix

Five objects. Two carry a relocation whose four-byte field falls outside the section that owns
it, tests/x86/coff_reloc_outside_section.obj on the first byte of the next section and
tests/x86/coff_reloc_outside_file.obj past the end of the file.

Three declare a table larger than the file holding it, one per table the parser reads.
tests/x86/coff_truncated_section_table.obj and tests/x86/coff_truncated_symbol_table.obj
need no assembly at all: they are the first 512 and 2048 bytes of the tracked
tests/x86/fauxware.obj, whose header declares 29 sections in a table ending at 0x49c and
152 symbols at 0x31c1 with the string table after them, so 512 cuts the section table and
2048 keeps it whole and cuts the symbol and string table.
tests/x86/coff_reloc_table_past_file.obj is assembled, because a well-formed 108-byte object
pointing its relocation table past the file is not something a toolchain produces.

All five come from tests_src/coff/build_coff_objects.py, whose docstring says which are cut
from a tracked file rather than assembled. Sizes, hashes and header fields are in the
validation record.

Testing

The same scan at this pull request's head finds 10 such COFF objects, 2 with a loose relocation
and 3 declaring a short table, and both runs are in the comment below. Rerunning the builder at
the head reproduces all eleven objects it owns byte for byte, so nothing already committed
moves.

The consumer is a cle pull request that bounds those offsets and adds five regressions that
load these objects; it references this pull request by number. This one has to merge first, or
those tests load fixtures that are not on master yet.

Validation: #223 (comment)

session: sharpen

cle's COFF backend takes a relocation's patch address as the section's
PointerToRawData plus the relocation's VirtualAddress and never checks the
result. These two objects give that check something to fail on.

coff_reloc_outside_section.obj holds a .text of 0x10 bytes and a .data of 0x10
bytes, and puts .text's only relocation at offset 0x10, so its four-byte field
starts on the first byte of .data. .data is filled with 0xaa, so a loader that
resolves the relocation against the whole file leaves visibly rewritten bytes in
a section no relocation names.

coff_reloc_outside_file.obj puts its relocation at offset 0x4000000 of a 108-byte
object, past the end of the file rather than merely past the section.

No toolchain emits either shape, so both are assembled by
build_coff_objects.py beside the other hand-built COFF objects, and its docstring
names the new shape alongside the ones it already enumerates.
cle's COFF parser reads the section table, the symbol and string table and each
section's relocation table at offsets and counts the file supplies, and bounds
none of them. These three objects declare each of those tables larger than the
file that holds it.

coff_truncated_section_table.obj and coff_truncated_symbol_table.obj are the
first 512 and 2048 bytes of the tracked x86/fauxware.obj, so they need no
assembly at all. Its header declares 29 sections, whose table ends at 0x49c, and
152 symbols at 0x31c1 with the string table after them. 512 bytes cuts the
section table; 2048 keeps the section table whole and cuts the symbol and string
table, so each object isolates one read.

coff_reloc_table_past_file.obj is assembled, because a well-formed 108-byte
object whose .text points its relocation table at 0x4000000 is not something a
toolchain emits. Everything else in it is in range, so the parser reaches the
relocation table with nothing else to complain about first.

The builder's docstring says which objects are cut from a tracked file rather
than assembled.
@zardus

zardus commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head 5ef7fa13421f04babb0e783f07465971d251e326 against baseline 981989f741dad616983a685f66a5566d2aed5bcf.

  • Contents: 5 objects added and tests_src/coff/build_coff_objects.py modified, in two commits.
Object Bytes sha256 What it declares
tests/x86/coff_reloc_outside_section.obj 164 b88d43ff1b83ed42a8bb8c303a9f942b50074644738cdc072fa0f663713edcf0 .text SizeOfRawData 0x10 at 0x64 and .data at 0x74; .text's one DIR32 relocation at VirtualAddress 0x10, so a patch offset of 0x74
tests/x86/coff_reloc_outside_file.obj 108 4c0b68028d678f8b6fb4671b962c80854e1702ecbe3e2a53fc85989bc2df4437 one .text SizeOfRawData 0x10 at 0x3c; its one DIR32 relocation at VirtualAddress 0x4000000, so a patch offset of 0x400003c
tests/x86/coff_reloc_table_past_file.obj 108 8a1a196c0c9959d36c0d34e773c11dca47a55b5a1d3e23a4c72d27fc8f5d8783 one .text with PointerToRelocations 0x4000000 and 1 relocation; everything else in range
tests/x86/coff_truncated_section_table.obj 512 605d7e411605d173579f29e1860086349412fe74f54783354fd1afbbd9f9e590 29 sections, whose table ends at 0x49c
tests/x86/coff_truncated_symbol_table.obj 2048 e73eab0f58ea6b69ff3a68a7a58c8984bd51b0dc2a50e10cadc3c8e8955c320a 152 symbols at 0x31c1; with the string table size after them the read ends at 0x3c75
  • Provenance of the two truncations: cmp against head -c 512 and head -c 2048 of the tracked tests/x86/fauxware.obj (16676 bytes, sha256 2036902da521301be298bcdb5f3e3fcf5c3d750eec06874c499758b8a7f5ce65) is clean for both.
  • Rebuild: python3 tests_src/coff/build_coff_objects.py, run from the repository root at this head, reproduces all five new objects and the six pre-existing ones byte for byte — git status --porcelain is empty afterwards. The script is stdlib only (os and struct), the TimeDateStamp field is written as 0, and nothing in it reads the clock or the environment.
  • Shape scan: at the baseline, 1068 tracked files under tests/, 5 COFF objects with an I386 or AMD64 machine type, 0 loose relocations and 0 short tables. At the head, 1073 files, 10 such objects, 2 loose relocations and 3 short tables. Full output is in the before/after comment.
  • Provenance: the three assembled objects hold no compiler output; strings -a over them returns only their section names and _target. The two truncations are a prefix of an object this repository already ships. None of the five contains a path, a user name or a build root.
  • Consumer: with the consumer branch applied, cle's tests/test_coff.py goes from 5 tests to 11, and five of the six new tests load these objects; the sixth loads the pre-existing tests/x86/fauxware.obj and passes on master. On cle master, coff_reloc_outside_file.obj raises KeyError: 67108924, coff_reloc_outside_section.obj loads with the first three bytes of .data rewritten from aa aa aa to 16 ab ea, coff_reloc_table_past_file.obj raises ValueError: Buffer size too small (108 instead of at least 67108874 bytes), and the two truncations raise struct.error: unpack requires a buffer of 4 bytes.
  • CI: angr/binaries has no workflow, so this pull request has no checks to report. The consumer's own checks resolve this pull request's head through angr/ci-settings/actions/binaries-ref and resolve_refs.py while it is open.

Caveat: this pull request has to merge before the consumer, or cle master will load fixtures that are not there yet.

@zardus

zardus commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Full output of a scan for the two shapes these fixtures supply, at this pull request's merge
base and at its head. It reads every blob under tests/ straight out of git, parses the COFF
header and section table of anything whose machine type cle's COFF backend supports, and
reports a relocation whose four-byte field does not fit inside its section's SizeOfRawData
or inside the file, and a header declaring a table that ends past the file.

Before — nothing in the repository carries either shape, so neither bound cle is missing
can be tested:

angr/binaries at the merge base
revision: origin/master = 981989f741dad616983a685f66a5566d2aed5bcf
tracked files under tests/: 1068
COFF objects with an I386 or AMD64 machine type: 5
relocations outside their section or the file: 0
objects declaring a table the file does not hold: 0

After — two objects carry a loose relocation and three declare a table the file does not
hold, one per table the COFF parser reads:

with this change
revision: HEAD = 5ef7fa13421f04babb0e783f07465971d251e326
tracked files under tests/: 1073
COFF objects with an I386 or AMD64 machine type: 10
relocations outside their section or the file: 2
  tests/x86/coff_reloc_outside_file.obj: section .text relocation at 0x4000000 width 4 vs SizeOfRawData 0x10 PointerToRawData 0x3c file size 0x6c
  tests/x86/coff_reloc_outside_section.obj: section .text relocation at 0x10 width 4 vs SizeOfRawData 0x10 PointerToRawData 0x64 file size 0xa4
objects declaring a table the file does not hold: 3
  tests/x86/coff_reloc_table_past_file.obj: relocation table of section 0 with 1 entries at 0x4000000 ends at 0x400000a, past a 0x6c byte file
  tests/x86/coff_truncated_section_table.obj: section table of 29 entries ends at 0x49c, past a 0x200 byte file
  tests/x86/coff_truncated_symbol_table.obj: symbol table of 152 entries at 0x31c1 and the string table size after it end at 0x3c75, past a 0x800 byte file

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