From eb9ed4349646463ef4836e80ad6ebc347d5eb485 Mon Sep 17 00:00:00 2001 From: Yan Date: Sun, 6 Sep 2026 07:12:25 +0000 Subject: [PATCH] Refuse packing through a read-only Clemory view 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. --- cle/memory.py | 3 +++ tests/test_clemory.py | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/cle/memory.py b/cle/memory.py index e2de3f139..c5355aab0 100644 --- a/cle/memory.py +++ b/cle/memory.py @@ -768,6 +768,9 @@ def load(self, addr: int, n: int) -> bytes: def store(self, addr, data): raise NotImplementedError("ClemoryReadOnlyView does not support storing") + def pack(self, addr: int, fmt: str, *data): + raise NotImplementedError("ClemoryReadOnlyView does not support packing") + def backers(self, addr: int = 0): start_pos = bisect.bisect_right(self._flattened_backers, addr, key=lambda x: x[0]) if start_pos > 0: diff --git a/tests/test_clemory.py b/tests/test_clemory.py index 432dfc16b..ed52940b5 100644 --- a/tests/test_clemory.py +++ b/tests/test_clemory.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os import sys import timeit import unittest @@ -8,6 +9,8 @@ import cle +TEST_BASE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "binaries", "tests") + @unittest.skipIf(sys.platform == "emscripten", "runtime CFFI compilation is unavailable in Pyodide") def test_cclemory(): # pylint: disable=no-member @@ -118,6 +121,30 @@ def test_clemory_contains(): assert clemory.consecutive is True +def test_clemory_read_only_view_refuses_writes(): + loader = cle.Loader(os.path.join(TEST_BASE, "x86_64", "fauxware"), auto_load_libs=False) + loader.gen_ro_memview() + view = loader.memory_ro_view + assert view is not None + + entry = loader.main_object.entry + before = loader.memory.load(entry, 16) + nops = b"\x90" * 8 + + for write in ( + lambda: view.store(entry, nops), + lambda: view.pack(entry, "8s", nops), + lambda: view.pack_word(entry, int.from_bytes(nops, "little")), + ): + refused = False + try: + write() + except NotImplementedError: + refused = True + assert refused + assert loader.memory.load(entry, 16) == before + + def main(): g = globals() for func_name, func in g.items():