Conversation
ClemoryReadOnlyView overrides store and __setitem__ to raise, but it inherits ClemoryBase.pack, which calls struct.pack_into on the backer that backers() yields. _flatten_backers stores the parent Clemory's own bytearray objects rather than copies, so a pack through the view lands in the parent. Loader.gen_ro_memview hands this view out and CFGFast builds one, so packing through it silently rewrites loader memory. On binaries/tests/x86_64/fauxware, loader.memory at the entry point goes from 31ed4989d15e4889 to 9090909090909090 while store on the same view raises. ClemoryBase.pack_word ends in a call to self.pack, so overriding pack closes both.
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head
Caveats: the fixture is |
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Writing through import cle
ld = cle.Loader("../binaries/tests/x86_64/fauxware", auto_load_libs=False)
ld.gen_ro_memview()
view = ld.memory_ro_view
entry = ld.main_object.entry
print("loader.memory before:", ld.memory.load(entry, 8).hex())
for label, call in [
("store", lambda: view.store(entry, b"\x90" * 8)),
("pack", lambda: view.pack(entry, "8s", b"\x90" * 8)),
("pack_word", lambda: view.pack_word(entry, 0x9090909090909090)),
]:
try:
call()
print(f"{label}: returned, no exception")
except NotImplementedError as e:
print(f"{label}: NotImplementedError: {e}")
print("loader.memory after: ", ld.memory.load(entry, 8).hex())Before — cle master 0e77adeAfter — all three are refused and loader memory is unchanged: with this change |
|
Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_826 |
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
Problem
Loader.gen_ro_memview()hands out aClemoryReadOnlyView, and it is not read-only.storeand__setitem__refuse, butpackandpack_wordwrite straight into the parentClemory. Loadingbinaries/tests/x86_64/fauxware, callinggen_ro_memview(), and then trying all three writes at the entry point:The write lands in loader memory, so the entry point is the rewritten one.
CFGFastbuilds one of these views in_pre_analysisand discards it in_post_analysis, andBlock.bytes,Clinic._convert_vex_fastand both the VEX and p-code lifters read through it while it exists.No code in cle, angr or angr-management packs through the view today, so this is an unguarded public API rather than a live analysis bug. The validation record names the revisions, and an instrumented run counted zero
packandpack_wordcalls with a view receiver duringCFGFastandDecompilerover eight fixtures on six architectures.Root cause
ClemoryReadOnlyViewinheritsClemoryBase.pack, which takes the backer out ofself.backers(addr)and callsstruct.pack_intoon it.ClemoryReadOnlyView._flatten_backersappends the parentClemory's ownbytearrayobjects rather than copies -- that aliasing is what the class is for, since it exists to make reads fast -- so thepack_intowrites into the parent.ClemoryBase.pack_wordends inreturn self.pack(...), so it inherits the same hole rather than having one of its own.Fix
Override
packonClemoryReadOnlyViewto raiseNotImplementedError, the waystoreand__setitem__already do. One method covers both spellings, becausepack_wordgoes throughself.pack.Deliberately not done: holding
memoryview(backer).toreadonly()in_flatten_backersinstead. That would close hand-mutation of a backer as well and cost no copy, but it changes the typebackers()yields, and angr rejects that type. The VEX lifter failed on every fixture I ran, while cle's own suite stayed green.Testing
New
test_clemory_read_only_view_refuses_writesintests/test_clemory.pyloadsbinaries/tests/x86_64/fauxware, callsgen_ro_memview(), and asserts forstore,packandpack_wordboth that the call is refused and thatloader.memoryat the entry point is byte-identical afterwards. It fails on master, where the twopackcalls succeed and rewrite the entry point.CFGFastandDecompilerover eight tracked fixtures give identical function and decompilation counts on both revisions.Validation: #826 (comment)
session: sharpen