Skip to content

Fix ClemoryView.__setitem__ dropping the write - #816

Open
zardus wants to merge 1 commit into
masterfrom
feature/clemoryview-setitem
Open

zardus wants to merge 1 commit into
masterfrom
feature/clemoryview-setitem

Conversation

@zardus

@zardus zardus commented Sep 6, 2026 •

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Problem

A write through a ClemoryView is silently lost. cle exports the class, and assigning a
byte through one leaves the memory unchanged and reports no error:

>>> import archinfo, cle
>>> arch = archinfo.arch_from_id('x86_64')
>>> m = cle.Clemory(arch, root=True)
>>> m.add_backer(0x1000, b'AAAABBBB')
>>> view = cle.ClemoryView(m, 0x1000, 0x1008)
>>> view[0] = 0x5a
>>> view[0]
65
>>> m.load(0x1000, 8)
b'AAAABBBB'

Root cause

ClemoryView.__setitem__ carries __getitem__'s body. After the range check it reads the
byte at the translated address and returns it, and v is never used:

def __setitem__(self, k, v):
    if not self._offset <= k < self._endoffset:
        raise KeyError(k)
    return self._backer[k + self._rebase]

Fix

Assign to the translated address instead of reading from it. The range check and the
KeyError it raises are unchanged.

Nothing in cle, angr or angr-management constructs a ClemoryView or subclasses one,
so this has no in-tree caller today; the class is exported from cle and the method is wrong
for anyone who does.

Testing

tests/test_clemory.py gains test_clemory_view_setitem, which writes a byte through a view
and reads it back through the view, through the parent Clemory and through Clemory.load,
and checks that an index outside the view still raises KeyError. It fails on master with
assert 65 == 90.

Validation: #816 (comment)

session: sharpen

__setitem__ carries __getitem__'s body: after the range check it returns the
byte at the translated address instead of storing v there, so a write through a
ClemoryView is silently lost.
@zardus

zardus commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head 804383fd831dc2cd347ca5be854971a22aba7943 against baseline
0e77ade3c39a3cee05f65051e57955675e1ac21b, with angr/binaries at
003e82a2bfa641530924055695b36cec8af483ab.

  • Regression: the head's tests/test_clemory.py run against the baseline's cle —
    python -m pytest <head>/tests/test_clemory.py -q from the baseline worktree — gives
    FAILED test_clemory_view_setitem - assert 65 == 90, 1 failed 3 passed. The same file at
    the head gives 4 passed
  • Focused: python -m pytest tests/test_clemory.py -q — baseline 3 passed, head 4 passed
  • Full suite: python -m pytest tests -q — baseline 261 passed, 9 skipped; head 262 passed,
    9 skipped. No failures on either arm
  • Lint/type: run-ci-diff-checks.py --base gh/master — pylint cle/memory.py 10.00 -> 10.00,
    tests/test_clemory.py 5.12 -> 5.70; pyright errors cle/memory.py 0 -> 0,
    tests/test_clemory.py 9 -> 9. The # type: ignore[arg-type] on the new
    cle.Clemory(None, root=True) line is load-bearing: without it the count is 10 and the
    Typecheck job fails
  • Hooks: pre-commit run --all-files exit 0, all 24 hooks pass or skip, no file rewritten
  • Guards: check-test-inputs.py exit 0 ("1 checkout(s) add no binary or manufactured input
    outside angr/binaries"); check-stale-pins.py --rev HEAD --base refs/remotes/gh/master
    exit 0, and structurally null here — the diff touches no pyproject.toml and gh/master
    is the baseline
  • Workspace gate: not run. The shared toolchain on the machine this was built on could not be
    rebuilt for it. What ran instead is the scoped equivalent above: cle's own suite in an
    isolated worktree, the merge-base lint and type comparison, and the full hook set. The angr,
    angr-management, pyvex, archinfo, pypcode and native Rust suites did not run; hosted CI
    covers them

Caveats: no code in cle 0e77ade3, angr 87411a719c96ddc3f3954590b1833b1789e19697 or
angr-management aa843e5c10645ba361620768d59642a460805e80 constructs a ClemoryView or
subclasses it, so nothing in the ecosystem exercises this path today; the search was
grep -rn ClemoryView over all three trees. No loader path reaches ClemoryView.__setitem__,
so a corpus load comparison would be a guaranteed null and none is offered.

@zardus

zardus commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

A write through a ClemoryView, before and after this change. Same script on both arms.

Before — the assignment is dropped and the byte keeps its old value:

cle master 0e77ade
>>> import archinfo, cle
>>> arch = archinfo.arch_from_id('x86_64')
>>> m = cle.Clemory(arch, root=True)
>>> m.add_backer(0x1000, b'AAAABBBB')
>>> view = cle.ClemoryView(m, 0x1000, 0x1008)
>>> view[0] = 0x5a
>>> view[0]
65
>>> m[0x1000]
65
>>> m.load(0x1000, 8)
b'AAAABBBB'

After — the assignment reaches the underlying Clemory:

with this change
>>> import archinfo, cle
>>> arch = archinfo.arch_from_id('x86_64')
>>> m = cle.Clemory(arch, root=True)
>>> m.add_backer(0x1000, b'AAAABBBB')
>>> view = cle.ClemoryView(m, 0x1000, 0x1008)
>>> view[0] = 0x5a
>>> view[0]
90
>>> m[0x1000]
90
>>> m.load(0x1000, 8)
b'ZAAABBBB'

@angr-bot

angr-bot commented Sep 6, 2026

Copy link
Copy Markdown
Member

Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_816

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants