Skip to content

Commit 9b9628d

Browse files
shaobo-he-awsEmina Torlak
andauthored
TPE soundness proofs (#595)
Signed-off-by: Shaobo He <[email protected]> Signed-off-by: Emina Torlak <[email protected]> Co-authored-by: Emina Torlak <[email protected]>
1 parent b8e1e7c commit 9b9628d

File tree

17 files changed

+1967
-72
lines changed

17 files changed

+1967
-72
lines changed

cedar-lean/Cedar/TPE/Evaluator.lean

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def or : Residual → Residual → CedarType → Residual
7070
| .val false _, r, _ => r
7171
| .error _, _, ty => .error ty
7272
| l, .val false _, _ => l
73-
| l, r, ty => .and l r ty
73+
| l, r, ty => .or l r ty
7474

7575
def apply₁ (op₁ : UnaryOp) (r : Residual) (ty : CedarType) : Residual :=
7676
match r with
@@ -95,46 +95,52 @@ def getTag (uid : EntityUID) (tag : String) (es : PartialEntities) (ty : CedarTy
9595
| .none => .binaryApp .getTag uid tag ty
9696

9797
def apply₂ (op₂ : BinaryOp) (r₁ r₂ : Residual) (es : PartialEntities) (ty : CedarType) : Residual :=
98-
match op₂, r₁, r₂ with
99-
| .eq, .val v₁ _, .val v₂ _ =>
100-
.val (v₁ == v₂) ty
101-
| .less, .val (.prim (.int i)) _, .val (.prim (.int j)) _ =>
102-
.val (i < j : Bool) ty
103-
| .less, .val (.ext (.datetime d₁)) _, .val (.ext (.datetime d₂)) _ =>
104-
.val (d₁ < d₂: Bool) ty
105-
| .less, .val (.ext (.duration d₁)) _, .val (.ext (.duration d₂)) _ =>
106-
.val (d₁ < d₂: Bool) ty
107-
| .lessEq, .val (.prim (.int i)) _, .val (.prim (.int j)) _ =>
108-
.val (i ≤ j : Bool) ty
109-
| .lessEq, .val (.ext (.datetime d₁)) _, .val (.ext (.datetime d₂)) _ =>
110-
.val (d₁ ≤ d₂: Bool) ty
111-
| .lessEq, .val (.ext (.duration d₁)) _, .val (.ext (.duration d₂)) _ =>
112-
.val (d₁ ≤ d₂: Bool) ty
113-
| .add, .val (.prim (.int i)) _, .val (.prim (.int j)) _ =>
114-
someOrError (i.add? j) ty
115-
| .sub, .val (.prim (.int i)) _, .val (.prim (.int j)) _ =>
116-
someOrError (i.sub? j) ty
117-
| .mul, .val (.prim (.int i)) _, .val (.prim (.int j)) _ =>
118-
someOrError (i.mul? j) ty
119-
| .contains, .val (.set vs₁) _, .val v₂ _ =>
120-
.val (vs₁.contains v₂) ty
121-
| .containsAll, .val (.set vs₁) _, .val (.set vs₂) _ =>
122-
.val (vs₂.subset vs₁) ty
123-
| .containsAny, .val (.set vs₁) _, .val (.set vs₂) _ =>
124-
.val (vs₁.intersects vs₂) ty
125-
| .mem, .val (.prim (.entityUID uid₁)) _, .val (.prim (.entityUID uid₂)) _ =>
126-
someOrSelf (inₑ uid₁ uid₂ es) ty self
127-
| .mem, .val (.prim (.entityUID uid₁)) _, .val (.set vs) _ =>
128-
someOrSelf (inₛ uid₁ vs es) ty self
129-
| .hasTag, .val (.prim (.entityUID uid₁)) _, .val (.prim (.string tag)) _ =>
130-
someOrSelf (hasTag uid₁ tag es) ty self
131-
| .getTag, .val (.prim (.entityUID uid₁)) _, .val (.prim (.string tag)) _ =>
132-
getTag uid₁ tag es ty
133-
| _, .error _, _ | _, _, .error _ => .error ty
134-
| _, _, _ => self
135-
where
98+
match r₁.asValue, r₂.asValue with
99+
| .some v₁, .some v₂ =>
100+
match op₂, v₁, v₂ with
101+
| .eq, _, _ =>
102+
.val (v₁ == v₂) ty
103+
| .less, .prim (.int i), .prim (.int j) =>
104+
.val (i < j : Bool) ty
105+
| .less, .ext (.datetime d₁), .ext (.datetime d₂) =>
106+
.val (d₁ < d₂: Bool) ty
107+
| .less, .ext (.duration d₁), .ext (.duration d₂) =>
108+
.val (d₁ < d₂: Bool) ty
109+
| .lessEq, .prim (.int i), .prim (.int j) =>
110+
.val (i ≤ j : Bool) ty
111+
| .lessEq, .ext (.datetime d₁), .ext (.datetime d₂) =>
112+
.val (d₁ ≤ d₂: Bool) ty
113+
| .lessEq, .ext (.duration d₁), .ext (.duration d₂) =>
114+
.val (d₁ ≤ d₂: Bool) ty
115+
| .add, .prim (.int i), .prim (.int j) =>
116+
someOrError (i.add? j) ty
117+
| .sub, .prim (.int i), .prim (.int j) =>
118+
someOrError (i.sub? j) ty
119+
| .mul, .prim (.int i), .prim (.int j) =>
120+
someOrError (i.mul? j) ty
121+
| .contains, .set vs₁, _ =>
122+
.val (vs₁.contains v₂) ty
123+
| .containsAll, .set vs₁, .set vs₂ =>
124+
.val (vs₂.subset vs₁) ty
125+
| .containsAny, .set vs₁, .set vs₂ =>
126+
.val (vs₁.intersects vs₂) ty
127+
| .mem, .prim (.entityUID uid₁), .prim (.entityUID uid₂) =>
128+
someOrSelf (inₑ uid₁ uid₂ es) ty self
129+
| .mem, .prim (.entityUID uid₁), .set vs =>
130+
someOrSelf (inₛ uid₁ vs es) ty self
131+
| .hasTag, .prim (.entityUID uid₁), .prim (.string tag) =>
132+
someOrSelf (hasTag uid₁ tag es) ty self
133+
| .getTag, .prim (.entityUID uid₁), .prim (.string tag) =>
134+
getTag uid₁ tag es ty
135+
| _, _, _ => .error ty
136+
| _, _ =>
137+
match r₁, r₂ with
138+
| .error _, _ | _, .error _ => .error ty
139+
| _, _ => self
140+
where
136141
self := .binaryApp op₂ r₁ r₂ ty
137142

143+
138144
def attrsOf (r : Residual) (lookup : EntityUID → Option (Map Attr Value)) : Option (Map Attr Value) :=
139145
match r with
140146
| .val (.record m) _ => .some m
@@ -210,6 +216,18 @@ decreasing_by
210216
try simp at h
211217
omega
212218

219+
/-- Partially evaluating a policy.
220+
Note that this function actually evaluates a type-lifted version of `TypedExpr`
221+
produced by the type checker, as opposed to evaluating the expression directly.
222+
This design is to simplify proofs otherwise we need to prove theorems that
223+
state type-lifting (i.e, `TypedExpr.liftBoolTypes`) do not change the results
224+
of evaluating residuals. The soundness theorem still holds. That is,
225+
reauthorizing the residuals produces the same outcome as authorizing the input
226+
expressions with consistent requests/entities. It is just that the types in the
227+
residuals are all lifted. We essentially trade efficiency for ease of proofs,
228+
which I (Shaobo) think is fine because the Lean model is a reference model not
229+
used in production.
230+
-/
213231
def evaluatePolicy (schema : Schema)
214232
(p : Policy)
215233
(req : PartialRequest)
@@ -222,7 +240,7 @@ def evaluatePolicy (schema : Schema)
222240
do
223241
let expr := substituteAction env.reqty.action p.toExpr
224242
let (te, _) ← (typeOf expr ∅ env).mapError Error.invalidPolicy
225-
.ok (evaluate te req es)
243+
.ok (evaluate te.liftBoolTypes req es)
226244
else .error .invalidRequestOrEntities
227245
| .none => .error .invalidEnvironment
228246

cedar-lean/Cedar/TPE/Input.lean

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ structure PartialEntityData where
5050

5151
abbrev PartialEntities := Map EntityUID PartialEntityData
5252

53-
private def PartialEntities.get (es : PartialEntities) (uid : EntityUID) (f : PartialEntityData → Option α) : Option α :=
53+
def PartialEntities.get (es : PartialEntities) (uid : EntityUID) (f : PartialEntityData → Option α) : Option α :=
5454
(es.find? uid).bind f
5555

5656
def PartialEntities.ancestors (es : PartialEntities) (uid : EntityUID) : Option (Set EntityUID) := es.get uid PartialEntityData.ancestors
@@ -62,13 +62,12 @@ def PartialEntities.attrs (es : PartialEntities) (uid : EntityUID) : Option (Map
6262
def partialIsValid {α} (o : Option α) (f : α → Bool) : Bool :=
6363
(o.map f).getD true
6464

65-
-- We do not check if `req`'s action is valid (i.e, if it's contained in `env`)
66-
-- because this function is called after validation, which already ensures it
6765
def requestIsValid (env : Environment) (req : PartialRequest) : Bool :=
6866
(partialIsValid req.principal.asEntityUID λ principal =>
69-
instanceOfEntityType principal principal.ty env.ets.entityTypeMembers?) &&
67+
instanceOfEntityType principal env.reqty.principal env.ets.entityTypeMembers?) &&
68+
req.action == env.reqty.action &&
7069
(partialIsValid req.resource.asEntityUID λ resource =>
71-
instanceOfEntityType resource resource.ty env.ets.entityTypeMembers?) &&
70+
instanceOfEntityType resource env.reqty.resource env.ets.entityTypeMembers?) &&
7271
(partialIsValid req.context λ m =>
7372
instanceOfType (.record m) (.record env.reqty.context) env.ets)
7473

@@ -103,11 +102,14 @@ inductive ConcretizationError
103102
| typeError
104103
| requestsDoNotMatch
105104
| entitiesDoNotMatch
105+
| invalidEnvironment
106106

107-
def isConsistent (env : Environment) (req₁ : Request) (es₁ : Entities) (req₂ : PartialRequest) (es₂ : PartialEntities) : Except ConcretizationError Unit :=
108-
do requestIsConsistent; entitiesIsConsistent
107+
def isValidAndConsistent (schema : Schema) (req₁ : Request) (es₁ : Entities) (req₂ : PartialRequest) (es₂ : PartialEntities) : Except ConcretizationError Unit :=
108+
match schema.environment? req₂.principal.ty req₂.resource.ty req₂.action with
109+
| .some env => do requestIsConsistent env; entitiesIsConsistent env
110+
| .none => .error .invalidEnvironment
109111
where
110-
requestIsConsistent :=
112+
requestIsConsistent env :=
111113
if !requestIsValid env req₂ || !requestMatchesEnvironment env req₁
112114
then
113115
.error .typeError
@@ -122,7 +124,7 @@ where
122124
.ok ()
123125
else
124126
.error .requestsDoNotMatch
125-
entitiesIsConsistent : Except ConcretizationError Unit :=
127+
entitiesIsConsistent env : Except ConcretizationError Unit :=
126128
if !entitiesIsValid env es₂ || !(entitiesMatchEnvironment env es₁).isOk
127129
then
128130
.error .typeError

cedar-lean/Cedar/TPE/Residual.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def Residual.evaluate (x : Residual) (req : Request) (es: Entities) : Result Val
9696
Cedar.Spec.hasAttr v a es
9797
| .getAttr e a _ => do
9898
let v ← e.evaluate req es
99-
Cedar.Spec.hasAttr v a es
99+
Cedar.Spec.getAttr v a es
100100
| .set xs _ => do
101101
let vs ← xs.mapM₁ (fun ⟨x₁, _⟩ => evaluate x₁ req es)
102102
.ok (Set.make vs)

cedar-lean/Cedar/Thm.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ import Cedar.Thm.PolicySlice
2020
import Cedar.Thm.Typechecking
2121
import Cedar.Thm.Validation
2222
import Cedar.Thm.WellTyped
23+
import Cedar.Thm.TPE

cedar-lean/Cedar/Thm/Data/Control.lean

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,96 @@ theorem do_error {res : Except ε α} {e : ε} {f : α → β} :
5757
res = .error e
5858
:= by cases res <;> simp
5959

60-
theorem do_ok {res : Except ε α} {f : α → β} :
60+
theorem do_ok_eq_ok {res : Except ε α} {f : α → β} :
6161
(do let v ← res ; .ok (f v)) = .ok b ↔
6262
∃ a, res = .ok a ∧ f a = b
6363
:= by cases res <;> simp
64+
65+
theorem do_eq_ok {res : Except ε α} {f : α → Except ε β} :
66+
(do let v ← res ; f v) = .ok b ↔
67+
∃ a, res = .ok a ∧ f a = .ok b
68+
:= by cases res <;> simp
69+
70+
theorem do_eq_ok₂ {res₁ res₂: Except ε PUnit} :
71+
(do res₁ ; res₂) = .ok () → res₁ = .ok () ∧ res₂ = .ok ()
72+
:= by
73+
cases res₁ <;> cases res₂ <;> simp
74+
75+
theorem to_option_some {v : α} {res: Except ε α} :
76+
res.toOption = .some v ↔ res = .ok v
77+
:= by
78+
constructor
79+
case mp =>
80+
intro h
81+
simp [Except.toOption] at h
82+
split at h <;> simp at h
83+
subst h
84+
rfl
85+
case mpr =>
86+
intro h
87+
simp only [Except.toOption, h]
88+
89+
theorem to_option_none {res: Except ε α} :
90+
res.toOption = .none → (∃ err, res = .error err)
91+
:= by
92+
intro h
93+
simp [Except.toOption] at h
94+
split at h <;> simp at h
95+
rename_i err
96+
exists err
97+
98+
theorem to_option_left_ok {α ε₁ ε₂} {v : α} {res₁ : Except ε₁ α} {res₂ : Except ε₂ α} :
99+
res₁.toOption = res₂.toOption → res₁ = .ok v → res₂ = .ok v
100+
:= by
101+
intro h₀ h₁
102+
simp [Except.toOption] at h₀
103+
repeat split at h₀
104+
· simp only [Except.ok.injEq]
105+
simp only [Option.some.injEq] at h₀
106+
simp only [Except.ok.injEq] at h₁
107+
simp only [← h₀]; exact h₁
108+
· cases h₀
109+
· cases h₁
110+
111+
theorem to_option_right_ok {α ε₁ ε₂} {v : α} {res₁ : Except ε₁ α} {res₂ : Except ε₂ α} :
112+
res₁.toOption = res₂.toOption → res₂ = .ok v → res₁ = .ok v
113+
:= by
114+
intro h
115+
symm at h
116+
exact to_option_left_ok h
117+
118+
theorem to_option_right_ok' {α ε₁ ε₂} {v : α} {res₁ : Except ε₁ α} :
119+
res₁.toOption = ((.ok v) : Except ε₂ α).toOption → res₁ = Except.ok v
120+
:= by
121+
generalize h : ((.ok v) : Except ε₂ α) = res₂
122+
symm at h
123+
intro h₁
124+
exact to_option_right_ok h₁ h
125+
126+
theorem to_option_left_ok' {α ε₁ ε₂} {v : α} {res₂ : Except ε₂ α} :
127+
((.ok v) : Except ε₁ α).toOption = res₂.toOption → res₂ = Except.ok v
128+
:= by
129+
intro h
130+
symm at h
131+
exact to_option_right_ok' h
132+
133+
theorem to_option_left_err {ε₁ ε₂ α} {err₁: ε₁} {res₂ : Except ε₂ α} :
134+
(Except.error err₁).toOption = res₂.toOption → ∃ err₂, res₂ = .error err₂
135+
:= by
136+
intro h
137+
simp [Except.toOption] at h
138+
repeat split at h
139+
· cases h
140+
· simp only [Except.error.injEq, exists_eq']
141+
142+
theorem to_option_right_err {ε₂ ε₁ α} {err₂: ε₂} {res₁ : Except ε₁ α} :
143+
res₁.toOption = (Except.error err₂).toOption → ∃ err₁, res₁ = .error err₁
144+
:= by
145+
intro h
146+
symm at h
147+
exact to_option_left_err h
148+
149+
theorem do_error_to_option {res : Except ε α} {e : ε} :
150+
(do let (_ : α) ← res ; (.error e : Except ε α)).toOption = .none
151+
:= by
152+
cases res <;> simp [Except.toOption]

cedar-lean/Cedar/Thm/Data/List/Lemmas.lean

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ theorem mapM'_ok_iff_forall₂ {α β γ} {f : α → Except γ β} {xs : List
437437
· simp only [reduceCtorEq] at h₁
438438
· simp only [reduceCtorEq] at h₁
439439
· rename_i yhd
440-
replace ⟨ytl, h₁, h₃⟩ := do_ok.mp h₁
440+
replace ⟨ytl, h₁, h₃⟩ := do_ok_eq_ok.mp h₁
441441
subst h₃
442442
exact List.Forall₂.cons h₂ (ih h₁)
443443
case mpr =>
@@ -852,6 +852,17 @@ theorem mapM_some_subset {f : α → Option β} {xs xs' : List α} {ys : List β
852852
simp only [← mapM'_eq_mapM]
853853
exact mapM'_some_subset
854854

855+
theorem forall₂_implies_mapM_eq {α₁ α₂ β ε} {xs : List α₁} {ys : List α₂} (f : α₁ → Except ε β) (g : α₂ → Except ε β):
856+
List.Forall₂ (fun x y => f x = g y) xs ys →
857+
List.mapM f xs =
858+
List.mapM g ys
859+
:= by
860+
intro h
861+
cases h
862+
case nil => simp only [List.mapM_nil]
863+
case cons h₁ h₂ =>
864+
simp only [List.mapM_cons, h₁, forall₂_implies_mapM_eq f g h₂, bind_pure_comp]
865+
855866
/--
856867
our own variant of map_congr, for mapM'
857868
-/

0 commit comments

Comments
 (0)