Fix Clemory.split_backer destroying a nested clemory - #809
Conversation
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head This head is Measured configuration: a clone of cle at the revision named, Python 3.12.13,
Overlap and conflicts, measured against today's heads with
Not run here: |
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Full import cle
ld = cle.Loader("binaries/tests/x86_64/fauxware", auto_load_libs=False)
print("nested children in Loader.memory:",
sum(1 for _, b in ld.memory._backers if isinstance(b, cle.Clemory)))
print("backers before:", [(hex(s), len(b)) for s, b in ld.memory.backers()])
print("total backer bytes before:", sum(len(b) for _, b in ld.memory.backers()))
try:
ld.memory.add_backer(ld.main_object.entry + 1, b"\x90" * 4, overwrite=True)
print("add_backer(overwrite=True) returned")
except ValueError as e:
print("add_backer(overwrite=True) raised ValueError:", e)
print("backers after :", [(hex(s), len(b)) for s, b in ld.memory.backers()])
print("total backer bytes after :", sum(len(b) for _, b in ld.memory.backers()))Before — the overwrite drops the 568-byte backer at #718 aloneAfter — the overwrite is refused and the loader's memory is untouched: with this change |
|
Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_809 |
split_backer finds its target with backers(), which recurses into nested clemories and yields the child's own bytearrays, and then removes it with remove_backer(), which only looks at self._backers, where the child itself sits. Splitting on an address inside a child would remove the whole child and put back two slices of one of the backers it held, dropping everything else. On master remove_backer raises before that happens; with #718 applied it does not. Loader.memory is that shape, since a loaded object's memory is a nested clemory inside it. The guard meant to cover this tested the leaf backers() yielded, which is never a clemory, so it could not fire. Raise unless the backer that was found is one of this clemory's own, which is the precondition for remove_backer to remove the thing being split.
43e23fa to
bf9d1c8
Compare
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
Problem
Clemory.split_backer(addr)removes the wrong thing whenaddrfalls inside anested clemory, and
Loader.memoryis exactly that shape --Loader._map_objectadds a loaded object's memory to it as a nested clemory.
On master the damage is hidden behind #718 --
remove_backernever findsits target, so the call raises
ValueError: Can't find backer to removeandnothing moves. With #718 applied it succeeds and silently drops memory. On
binaries/tests/x86_64/fauxware:The 568-byte backer at
0x600e28is gone -- 3293 backer bytes before, 2725after -- and it is nowhere near the four bytes the caller asked to overwrite.
Root cause
split_backerfinds its target withbackers()and removes it withremove_backer(), and those two do not look at the same collection.backers()recurses into a nested clemory and yields the child's own bytearrays at absolute
addresses, while
_backers, whichremove_backerpops from, holds the childclemory itself. So the split reads a start that names a backer inside the
child, removes the whole child, and puts back two slices of that one inner
backer.
The guard that was meant to cover this cannot fire:
backers()recurses past nested clemories rather than yielding them, sobackeris never a clemory.
Fix
Raise unless the backer
backers()found is one of this clemory's own, which isthe precondition for
remove_backer(start_addr)to remove the thing being split.The guard stays where it is and the rest of the function is unchanged.
Raising rather than recursing into the child is deliberate.
split_backer's onlycaller in the tree is
add_backer(overwrite=True), which afterwards callsremove_backeron the outer clemory, and that can only remove from_backers. Arecursive split would therefore leave the outer holding both the child and the
new backer over the same addresses.
Testing
tests/test_clemory.pygains two tests, one on a nested clemory built in thetest and one on
Loader.memoryfrombinaries/tests/x86_64/fauxware. Both failon the merge base.
Nothing in cle, angr or angr-management reaches this on a load. Loading the 745
ELF files under
binaries/testscallssplit_backer5,722 times and no callgets as far as the removal, so the new guard never fires and no loaded object
changes. It is reachable through the public API, which is what the fauxware
example above uses.
Validation: #809 (comment)
session: sharpen