From 91ed3fa8f20eadd115ca158ee6a3ed1df5ea3a59 Mon Sep 17 00:00:00 2001 From: Yan Date: Sun, 6 Sep 2026 05:37:13 +0000 Subject: [PATCH] Stop Clemory views yielding bytes when iterated ClemoryView, ClemoryTranslator and ClemoryReadOnlyView define no __iter__, and neither does ClemoryBase, which declares __getitem__, __setitem__, __contains__, load, store, backers and find and raises NotImplementedError in each. Python therefore falls back to the legacy __getitem__ sequence protocol, which calls obj[0], obj[1], ... and stops only on IndexError. These classes raise KeyError, so iterating a view whose address space starts at 0 yields the byte stored at each address, and iterating one that starts anywhere else raises KeyError: 0. On binaries/tests/x86_64/fauxware the first six values a ClemoryView over loader.memory yields are [127, 69, 76, 70, 2, 1], which is the start of the ELF header rather than any address in it. Declare __iter__ on ClemoryBase beside the other seven, so a class that does not implement it refuses instead of falling through. Clemory keeps its own implementation. The alternative is to implement address iteration on the views, and it is a larger change than it looks. ClemoryReadOnlyView.backers() is a point query that yields nothing when asked to enumerate a whole clemory, ClemoryView.backers() mis-clamps a backer that runs past the end of the view, and ClemoryTranslator refuses backers() and find() outright because it has no address space to walk. That belongs in its own change. --- cle/memory.py | 3 +++ tests/test_clemory_views.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 tests/test_clemory_views.py diff --git a/cle/memory.py b/cle/memory.py index e2de3f139..e3a3f5b1e 100644 --- a/cle/memory.py +++ b/cle/memory.py @@ -32,6 +32,9 @@ def __setitem__(self, k, v): def __contains__(self, k): raise NotImplementedError + def __iter__(self): + raise NotImplementedError + def load(self, addr, n): raise NotImplementedError diff --git a/tests/test_clemory_views.py b/tests/test_clemory_views.py new file mode 100644 index 000000000..a3bef3bc6 --- /dev/null +++ b/tests/test_clemory_views.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +import os + +import cle + +test_location = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "binaries", "tests") + + +def test_clemory_views_refuse_iteration(): + loader = cle.Loader(os.path.join(test_location, "x86_64", "fauxware"), auto_load_libs=False) + memory = loader.memory + start = memory.min_addr + + for view in ( + cle.ClemoryView(memory, start, start + 0x100), + cle.ClemoryTranslator(memory, lambda addr: addr + start), + cle.ClemoryReadOnlyView(loader.main_object.arch, memory), + ): + try: + iter(view) + except NotImplementedError: + continue + raise AssertionError(f"{type(view).__name__} still falls back to the sequence protocol") + + assert len(list(memory)) == sum(len(backer) for _, backer in memory.backers()) + + +if __name__ == "__main__": + test_clemory_views_refuse_iteration()