Skip to content

Do not refresh clobber state for store address registers (#36)#37

Merged
ricardojrdez merged 1 commit into
masterfrom
fix-issue36-store-clobber
Jul 3, 2026
Merged

Do not refresh clobber state for store address registers (#36)#37
ricardojrdez merged 1 commit into
masterfrom
fix-issue36-store-clobber

Conversation

@ricardojrdez

Copy link
Copy Markdown
Member

Closes #36. Follow-up to #34 / #35.

Problem

The explicit-dst clobber refresh added to the ROP chain search in PR #35 zeroes the clobber counter of a gadget's dst whenever a step writes it, so later steps can reuse that register:

norm_dst = arch.normalize_reg(gad.dst) if gad.dst else None
saved_dst = side_effected[norm_dst] if norm_dst else 0
if saved_dst:
    side_effected[norm_dst] = 0

This is correct for register writes (mov dst, src, lc(dst), xchg dst, src), but wrong for store operations. A store st is mov [dst], src, so gad.dst holds the address base register ([rax] -> rax), which is read as an address, not written. Zeroing its clobber count marks it as freshly produced when it is not, so a stale clobber from an earlier step can be masked and an invalid chain produced. This is a correctness issue, not a regression (a store's dst was never protected by the side-effect guard).

Fix

Guard the refresh on whether the gadget actually writes dst. A new helper Gadget.writes_reg(normalized_reg) reports whether the gadget explicitly or implicitly writes a register (mirroring calculate_side_effects). The refresh now applies only when dst is genuinely written:

  • register writes (mov, pop, xchg) -> dst in written regs -> refresh (unchanged behavior);
  • stores (mov [dst], src) -> dst not written -> no refresh (fixed).

Tests

Adds test_store_dst_does_not_clear_clobbered_address_register, which is discriminating: it fails on the pre-fix code (an invalid chain is produced) and passes with the fix. It complements the existing test_explicit_dst_clears_clobbered_register (the register-write case). Full suite passes locally (103 tests) and covers Python 3.11, 3.12 and 3.13 in CI.

The explicit-dst clobber refresh added in the ROP chain search (PR #35)
zeroed the clobber counter of a gadget's dst whenever a step wrote it.
For store operations (mov [dst], src), dst is the address base register,
which is read as an address rather than written, so refreshing it could
mask an earlier clobber and yield an invalid ROP chain.

Guard the refresh on whether the gadget actually writes dst
(Gadget.writes_reg), so it applies to register writes (mov, pop, xchg)
but not to store address registers. Add a discriminating regression test.

Closes #36

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ricardojrdez ricardojrdez merged commit 4982592 into master Jul 3, 2026
3 checks passed
@ricardojrdez ricardojrdez deleted the fix-issue36-store-clobber branch July 3, 2026 07:17
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.

Explicit-dst clobber refresh wrongly applies to store (st) operations

1 participant