From bf9d1c8add44930ad83e9ab683daef8ce8c7fd6c Mon Sep 17 00:00:00 2001 From: Yan Date: Thu, 3 Sep 2026 15:34:59 +0000 Subject: [PATCH] Fix Clemory.split_backer dropping a nested clemory 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 angr/cle#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. --- cle/memory.py | 5 ++++- tests/test_clemory.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/cle/memory.py b/cle/memory.py index e2de3f139..43300121a 100644 --- a/cle/memory.py +++ b/cle/memory.py @@ -263,7 +263,10 @@ def split_backer(self, addr: int): return if addr <= start_addr: return - if isinstance(backer, ClemoryBase): + # backers() recurses into nested clemories and yields the child's own backers, while remove_backer() below + # removes from self._backers, where the child itself sits. + idx = bisect.bisect_left(self._backers, start_addr, key=lambda x: x[0]) + if idx >= len(self._backers) or self._backers[idx][1] is not backer: raise ValueError("Cannot split a backer which is itself a clemory") if addr >= start_addr + len(backer): return diff --git a/tests/test_clemory.py b/tests/test_clemory.py index 432dfc16b..7dd715cf2 100644 --- a/tests/test_clemory.py +++ b/tests/test_clemory.py @@ -1,10 +1,12 @@ from __future__ import annotations +import os import sys import timeit import unittest import cffi +import pytest import cle @@ -87,6 +89,38 @@ def performance_clemory_contains(): print(t) +def test_split_backer_refuses_to_split_through_a_nested_clemory(): + child = cle.Clemory(None) # type: ignore[arg-type] + child.add_backer(0, b"A" * 0x200) + child.add_backer(0x200, b"B" * 0x200) + + clemory = cle.Clemory(None, root=True) # type: ignore[arg-type] + clemory.add_backer(0, b"C" * 0x400) + clemory.add_backer(0x400, child) + + before = [(start, bytes(backer)) for start, backer in clemory.backers()] + + with pytest.raises(ValueError, match="itself a clemory"): + clemory.split_backer(0x401) + + assert [(start, bytes(backer)) for start, backer in clemory.backers()] == before + assert clemory.load(0x600, 0x10) == b"B" * 0x10 + + +def test_split_backer_refuses_to_split_through_a_loaded_object(): + filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../binaries/tests/x86_64/fauxware") + ld = cle.Loader(filename, auto_load_libs=False) + assert any(isinstance(backer, cle.Clemory) for _, backer in ld.memory._backers) + + addr = ld.main_object.entry + before = [(start, bytes(backer)) for start, backer in ld.memory.backers()] + + with pytest.raises(ValueError, match="itself a clemory"): + ld.memory.split_backer(addr + 1) + + assert [(start, bytes(backer)) for start, backer in ld.memory.backers()] == before + + def test_clemory_contains(): clemory = cle.Clemory(None, root=True) assert clemory.min_addr == 0