Stop container objects from claiming their children's addresses - #730
Stop container objects from claiming their children's addresses#730zardus wants to merge 2 commits into
Conversation
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head Re-keyed from Measured configuration: a detached worktree of cle at the revision named, this workspace's pinned Python 3.12 environment,
What the second commit is about. #735 made the rebase search start above the main object, so a container mapped at 0 falls below the search range in the ordinary case. The skip in The defect first showed up in a corpus sweep, on three CaRT containers holding crash dumps in minidump format (sha256 Caveats: the fix is in Corpus evidence, 2026-08-28Load-only measurement over a private corpus, so objects are cited by architecture, container format and sha256 only. Each object is loaded in its own process as 1,991 distinct objects terminate in 1,989 of them are blob firmware images under a 16-bit SLEIGH language — 6502 1,625, z80 252, 65c02 61, z180 25, x86 real mode 21, cp1600 5. The other two are singletons, an ihex and a minidump, whose recipes name no base address and which the arithmetic below therefore leaves out.
50 of a 50-object sample are refused on master and all 50 are still refused on the head arm, as they should be. What moves is the sentence. Master says only naming a base address that is not by itself the problem, while this head says Hosted CI at head |
|
Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_730 |
1288a51 to
b680cde
Compare
b680cde to
ab3869a
Compare
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS This came out of assembling the angr/vibr preview snapshot (every green open PR merged together, then each component's full test suite run against the result with an With #765 applied, this PR's |
An outer object holds the objects it unpacks and backs no memory, so Backend.min_addr and Backend.max_addr both report the base it was placed at: a one-byte span. _map_object inserts it into all_objects anyway, and _is_range_free counted it, so a position-dependent child linked at that same address was refused for overlapping its own container. A CaRT file keeps its linked base of 0 and blocks a child linked at 0; StaticArchive and Universal2 are position independent and land on 0x400000, which is where a non-PIE x86-64 member is linked. find_object_containing already excludes outer objects; placement now does too, in _is_range_free and in _free_gaps, where the same byte was moving the address the loader picks for the objects it rebases and laying a wrapped binary out differently from the same binary unwrapped. The same raise covered three unrelated conditions and described only the last one, so an object that simply did not fit in the architecture's address space was reported as an overlap. Split the conditions apart in _describe_range_conflict and append the reason to the message. Name the object by binary_basename, and give the object a CaRT file unpacks the wrapper's unpacked_name, so an object loaded from a stream is no longer reported as "None". Every case is covered from a fixture that is already in angr/binaries. tests/x86_64/1after909.cart is a real container: loading its contents at 0 with the blob backend reproduces the refusal exactly as the wrapped crash dumps that first showed it do. Since _free_gaps searches upward from the main object, the placement case loads the image high, which puts the free space, and so the container's byte, below it; the wrapped binary then rebases a granule away from where the unwrapped one does. The address-space case loads tests/i386/manysum as a blob near the top of the 32-bit space. No test writes a container of its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ab3869a to
b2fe3d9
Compare
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Full placement report before and after this change, on Before — the child is refused for overlapping its own container, a dynamically loaded PIC object lands a granule higher when wrapped, and all three refusals share one message that names neither the obstacle nor the object: cle at the merge base, 46a3733After — the child maps at 0 and owns the address, wrapped and unwrapped layouts agree, and each refusal names its cause: with this change, b2fe3d9 |
The regression asserted rebase(wrapped) == rebase(plain) == 0. The equality is the invariant -- an outer object holds no address space, so wrapping a binary must not move what the loader rebases -- while the 0 is whichever address _find_safe_rebase_addr happens to return. cle#765 changes that search to try the space above the image before the null page, so the same correct layout comes out at 0xffffffff80100000 and the pinned 0 fails. Drop it. The assertion still fails on this branch's merge base, where the container's one byte at 0 pushes the wrapped loader's answer to 0x100000 against the plain loader's 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DqcAcuGLrNJViJrpdtFYCS
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Interaction with #765, and the assertion it movedThis branch and #765 are each green alone and cannot both be in a rollup: the
assert rebase(wrapped) == rebase(plain) == 0The equality is this branch's invariant -- an outer object holds no address The invariant holds; only the literal was wrong. 6c31c31 drops it, keeping What that costs, stated plainlyOnce #765 lands, this test stops discriminating the That is a property of #765's placement policy, not of this test: with the search Verificationmaster + #765 + #730 + #721, all three at the heads below, cle's whole suite: Heads: #765 session: sharpen |
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
Problem
A binary that loads on its own fails inside a container backend when it is linked at the address the container was placed at. Loading
tests/x86_64/1after909.cartwith its child forced to a position-dependent blob at 0:The message names
None, because the wrapper handed the loader a stream rather than a path, and says nothing about what is in the way. The quieter half of the same defect is that a wrapped binary does not lay out like the unwrapped one — with the main object mapped high, so the free space starts at 0:Root cause
An outer object holds no memory: a CaRT wrapper exists only to unpack the object inside it. But it is mapped first, at its linked base of 0, where both its bounds report that base — a one-byte span — and
Loader._is_range_freecounts every mapped object:so placement counted that byte against the object the container unpacks.
_possible_addressescounted it too, which is what pushes everything the loader rebases up by a full granule. The refusal itself wasraise CLEError(f"Position-DEPENDENT object {obj.binary} cannot be loaded at {base_addr:#x}"), one message covering three unrelated conditions.Fix
find_object_containingalready skips outer objects;_is_range_freeand_possible_addressesnow do too. The boolean check becomes_describe_range_conflict, which returns a fragment naming the object in the way or the end of the address space, andCARTFilesets the child'sbinary_basenameto the unpacked name so the message can identify it.Being outer, not being memoryless, is what takes an object out of the address space: a
NamedRegionbacks no memory either and still reserves its range.Testing
tests/test_overlap.py::test_outer_object_does_not_move_rebased_objectsassertsrebase(wrapped) == rebase(plain), the address itself being whatever_find_safe_rebase_addrreturns;::test_outer_object_does_not_occupy_address_spaceloads the container's contents at 0 to reproduce the refusal;::test_memoryless_region_still_reserves_address_spaceand::test_placement_past_the_end_of_the_address_spacepin the two new messages.tests/test_cart.py::test_cart_child_is_named_after_the_wrapperand::test_cart_layout_matches_unwrappedcompare against the unwrappedtests/x86_64/1after909. Every fixture is already onangr/binariesmaster, so nothing here waits on a companion PR.Validation: #730 (comment)
session: sharpen