Fix rewrite Pr [mu1_le_eq_mu1]: add missing memory-independence check#930
Fix rewrite Pr [mu1_le_eq_mu1]: add missing memory-independence check#930LucianoXu wants to merge 2 commits intoEasyCrypt:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses a soundness bug in the mu1_le_eq_mu1 probability rewrite: rewriting Pr[... : res = k] into mu1 d k is unsound when k depends on the post-execution memory.
Changes:
- Added a check rejecting rewrites where the compared value
kdepends on the post-execution memory. - Added a similar post-memory dependency check for the distribution argument
d, emitting clearer critical errors.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/phl/ecPhlPrRw.ml
Outdated
| let post_mem = Mid.singleton k.m () in | ||
| if not (Mid.set_disjoint k.inv.f_fv post_mem) then | ||
| tc_error !!tc | ||
| "Pr-rewrite: the value compared to res must not depend on \ | ||
| the post-execution memory"; | ||
| if not (Mid.set_disjoint d.inv.f_fv post_mem) then | ||
| tc_error !!tc | ||
| "Pr-rewrite: the distribution must not depend on \ | ||
| the post-execution memory"; |
There was a problem hiding this comment.
The new distribution check only ensures d.inv is disjoint from the post-event memory id (k.m), but it does not enforce the original FIXME requirement that d.inv must not reference its own parsing memory (d.m, typically the fresh &hr created in t_pr_rewrite). If d.inv mentions d.m, the rewrite can still introduce a dangling/free memory identifier in mu1 d k, which is exactly what the old FIXME was warning about. Consider also checking Mid.set_disjoint d.inv.f_fv (Mid.singleton d.m ()) (or otherwise rebinding/quantifying d.m) in addition to (or instead of) the post-memory check.
src/phl/ecPhlPrRw.ml
Outdated
| (* Check that k and d do not reference the post-execution memory. | ||
| Otherwise the rewrite is unsound: the event `res = k` would use | ||
| k from the post-state, but `mu1 d k` treats k as a constant. *) | ||
| let post_mem = Mid.singleton k.m () in |
There was a problem hiding this comment.
@oskgo The FIXME above is from you. Can you fix the memory?
|
Thanks. Here, the bug was already documented in the source code (don't ask me why). I think that we have a bunch of comments with FIXME like this. I suggest that you add this information to the prompt (i.e. "The EasyCrypt source code contains FIXME. Target them."). I can see more of them in the same file, and certainly in others. |
| Otherwise the rewrite is unsound: the event `res = k` would use | ||
| k from the post-state, but `mu1 d k` treats k as a constant. *) | ||
| if Mid.mem k.m k.inv.f_fv then | ||
| tc_error !!tc |
There was a problem hiding this comment.
The existing rewrite Pr machinery is designed to be able to handle goals with multiple Pr formulas. For this to work successful selection has to mean that the selected rewrite is safe to apply, otherwise you might error out before finding the right Pr to apply.
We should check for memory dependence during selection as well. This makes these errors moot but allows rewrites on formulas like Pr[N.f() @ &m : res = N.x] = Pr[N.f() @ &m : res = true], which are not allowed by this PR.
I think there's a bigger discussion to be had about how to better expose matching/selection failures to the user (in regular rewrite as well), but this is not an easy problem to solve.
There was a problem hiding this comment.
The matching algorithm is already able to do that. Maybe should we add a way to start from a non empty set of variables.
There was a problem hiding this comment.
Are you commenting on the third paragraph?
In care you are commenting on first or second, consider this example:
require import DBool.
module N = {
var x: bool
proc f() ={return true;}
}.
lemma L1 &m: Pr[N.f() @ &m : res = true] = Pr[N.f() @ &m : res = N.x].
rewrite Pr[mu1_le_eq_mu1 dbool]. (* succeds at rewriting left side *)
abort.
lemma L2 &m: Pr[N.f() @ &m : res = N.x] = Pr[N.f() @ &m : res = true].
pose p:=Pr[N.f() @ &m : res = N.x].
rewrite Pr[mu1_le_eq_mu1 dbool]. (* succeeds at rewriting right side *)
abort.
lemma L3 &m: Pr[N.f() @ &m : res = N.x] = Pr[N.f() @ &m : res = true].
rewrite Pr[mu1_le_eq_mu1 dbool]. (* tries rewriting the left side and errors *)
Summary
rewrite Pr [mu1_le_eq_mu1 d]is unsound when the valuekin the eventres = kdepends on the post-execution memory. This PR adds the missing check flagged by theFIXMEcomments atecPhlPrRw.ml:254-255.Proof of
falseThe following EasyCrypt file proves
falseby exploiting this bug:Before fix:
easycrypt compile -no-eco novel_prrw_false.ecexits 0 (provesfalse).After fix: exits 1 with error:
Root cause
The
mu1_le_eq_mu1rewrite transformsPr[M.f() @ &m : res = k]intomu1 d k. This is sound whenkis a constant (independent of the post-execution memory). But whenkreferences a global variable modified by the procedure (e.g.,N.x):res = N.xevaluatesN.xin the post-state, whereN.x = resalways holds → probability 1mu1 dbool N.xtreatsN.xas a constant (dangling memory reference) →1/21 = 1/2The source code already had
FIXMEcomments atecPhlPrRw.ml:254-255acknowledging the missing checks.Fix
Added checks that both
k(the value compared tores) andd(the distribution) have free variables disjoint from the post-execution memory identifier. If either depends on the post-memory, a clear error is emitted.Disclosure
This bug was discovered and this fix was authored with the assistance of Claude Code (Anthropic).
🤖 Generated with Claude Code