1use crate::*;
5#[allow(unused_imports)]
6use vstd::prelude::*;
7
8verus! {
9
10pub open spec fn is_reject(d: Decision) -> bool {
11 match d {
12 Decision::Reject(_) => true,
13 Decision::Allow { .. } => false,
14 }
15}
16
17pub open spec fn asks_or_rejects(d: Decision) -> bool {
19 match d {
20 Decision::Reject(_) => true,
21 Decision::Allow { tier, .. } => tier == Tier::AskFirst,
22 }
23}
24
25pub open spec fn strictness(d: Decision) -> int {
27 match d {
28 Decision::Allow { tier, .. } => tier.rank() as int,
29 Decision::Reject(_) => 4,
30 }
31}
32
33pub open spec fn at_least_as_strict(x: Decision, y: Decision) -> bool {
35 (is_reject(y) && x == y) || (!is_reject(x) && !is_reject(y) && strictness(x) >= strictness(y))
36}
37
38pub open spec fn approver_of(d: Decision) -> ApproverClass {
40 match d {
41 Decision::Allow { approver, .. } => approver,
42 Decision::Reject(_) => ApproverClass::AnyApprover,
43 }
44}
45
46pub open spec fn asks_admin_or_rejects(d: Decision) -> bool {
48 match d {
49 Decision::Reject(_) => true,
50 Decision::Allow { tier, approver } => tier == Tier::AskFirst && approver == ApproverClass::Administrator,
51 }
52}
53
54pub open spec fn same_but_autonomy(a: Input, b: Input) -> bool {
56 &&& *a.transition == *b.transition
57 &&& *a.facts == *b.facts
58 &&& a.known_good == b.known_good
59 &&& a.live_tested == b.live_tested
60 &&& *a.cumulative == *b.cumulative
61 &&& *a.authz == *b.authz
62 &&& a.settings.user_controllable_units == b.settings.user_controllable_units
63 &&& a.settings.user_controllable_changes == b.settings.user_controllable_changes
64 &&& a.settings.user_controllable_options == b.settings.user_controllable_options
65 &&& *a.tables == *b.tables
66 &&& *a.locked == *b.locked
67 &&& a.revert_to == b.revert_to
68}
69
70pub proof fn i5_autonomy_never_touches_reject(a: Input, b: Input)
74 requires
75 same_but_autonomy(a, b),
76 ensures
77 uncapped_spec(a) == uncapped_spec(b),
78 is_reject(decide_spec(a)) == is_reject(decide_spec(b)),
79 is_reject(decide_spec(a)) ==> decide_spec(a) == decide_spec(b),
80 is_reject(decide_spec(a)) ==> decide_spec(a) == uncapped_spec(a),
81 !is_reject(decide_spec(a)) ==> (strictness(decide_spec(a)) <= a.settings.max_tier.rank()
82 || asks_or_rejects(decide_spec(a))),
83 approver_of(decide_spec(a)) == approver_of(uncapped_spec(a)),
85 a.settings.max_tier.rank() <= b.settings.max_tier.rank() ==> at_least_as_strict(
86 decide_spec(a),
87 decide_spec(b),
88 ),
89{
90}
91
92pub proof fn i11_non_routine_session_asks(i: Input)
96 requires
97 cumulative_spec(i),
98 ensures
99 asks_or_rejects(decide_spec(i)),
100{
101}
102
103pub open spec fn same_but_session(a: Input, b: Input) -> bool {
105 &&& *a.transition == *b.transition
106 &&& *a.facts == *b.facts
107 &&& a.known_good == b.known_good
108 &&& a.live_tested == b.live_tested
109 &&& *a.authz == *b.authz
110 &&& *a.settings == *b.settings
111 &&& *a.tables == *b.tables
112 &&& *a.locked == *b.locked
113 &&& a.revert_to == b.revert_to
114}
115
116pub proof fn i11_session_only_raises(a: Input, b: Input)
119 requires
120 same_but_session(a, b),
121 b.cumulative.options.entries@.len() == 0,
122 ensures
123 at_least_as_strict(decide_spec(a), decide_spec(b)),
124{
125 assert(!diff_non_routine(*b.tables, *b.cumulative));
126}
127
128pub proof fn global_floors(i: Input)
130 ensures
131 i.facts.recovery == Recovery::NonReversible ==> asks_or_rejects(decide_spec(i)),
132 ((i.facts.disruption.session_loss || i.facts.disruption.data_interruption) && !(is_user(i.authz.actor)
133 && own_session(i.authz.actor, *i.facts))) ==> asks_or_rejects(decide_spec(i)),
134 (i.facts.disruption.connectivity_loss && !is_local(i.authz.actor)) ==> asks_or_rejects(decide_spec(i)),
135 (i.facts.other_sessions_affected && (i.facts.disruption.session_loss
136 || i.facts.disruption.connectivity_loss) && !is_admin(*i.authz)) ==> asks_or_rejects(decide_spec(i)),
137{
138}
139
140pub proof fn own_session_is_per_session(i: Input)
144 requires
145 i.facts.disruption.session_loss,
146 i.facts.affected_sessions@.len() != 1 || match i.authz.actor {
147 Actor::User { session, .. } => match session {
148 Some(s) => i.facts.affected_sessions@[0]@ != s@,
149 None => true,
150 },
151 _ => true,
152 },
153 ensures
154 asks_or_rejects(decide_spec(i)),
155{
156}
157
158pub proof fn i16_below_floor_needs_administrator(i: Input)
162 requires
163 i.facts.below_security_floor,
164 ensures
165 asks_admin_or_rejects(decide_spec(i)),
166{
167}
168
169pub proof fn administrator_class_only_on_ask_first(i: Input)
171 ensures
172 approver_of(decide_spec(i)) == ApproverClass::Administrator ==> asks_or_rejects(decide_spec(i)),
173{
174}
175
176pub proof fn security_override_held_for_administrator(i: Input)
180 requires
181 is_override(*i.transition),
182 i.facts.sensitivity.security,
183 is_user(i.authz.actor),
184 !is_admin(*i.authz),
185 ensures
186 decide_spec(i) != Decision::Reject(RejectReason::Unauthorized),
187 asks_admin_or_rejects(decide_spec(i)),
188{
189}
190
191pub proof fn security_restart_runs_alone_only_to_restore(i: Input)
197 requires
198 match *i.transition {
199 Transition::RestartUnit(_) => true,
200 _ => false,
201 },
202 i.facts.sensitivity.security,
203 ensures
204 match decide_spec(i) {
205 Decision::Allow { tier, .. } => tier != Tier::AskFirst ==> (i.facts.restores_unattributed_stop
206 && (!i.facts.sensitivity.external || i.facts.restarts_failed_unit)) || i.facts.restarts_held_unit,
207 _ => true,
208 },
209{
210}
211
212pub proof fn i19_no_self_approval(approver: Authorization, proposer: PrincipalId, class: ApproverClass, g: Grant)
216 ensures
217 may_approve_spec(approver, proposer, class) ==> approver.principal != proposer && approver.capabilities.approve,
218 may_approve_spec(approver, proposer, ApproverClass::Administrator) ==> approver.capabilities.administrator,
219 may_grant_spec(approver, proposer, g) ==> approver.principal != g.to && approver.principal != proposer
220 && approver.capabilities.has(g.capability),
221{
222}
223
224pub proof fn agents_and_unattributed_are_refused(i: Input)
228 requires
229 i.authz.actor is Agent || i.authz.actor is Model || i.authz.actor is Unattributed,
230 ensures
231 decide_spec(i) == Decision::Reject(RejectReason::Unauthorized),
232{
233}
234
235pub proof fn i10_canonical_probe_changes_ask(i: Input)
238 requires
239 i.facts.touches_canonical_probe,
240 ensures
241 asks_or_rejects(decide_spec(i)),
242{
243}
244
245pub proof fn unauthorized_is_refused_first(i: Input)
247 requires
248 !authorized_spec(*i.transition, *i.facts, *i.authz, *i.settings),
249 ensures
250 decide_spec(i) == Decision::Reject(RejectReason::Unauthorized),
251{
252}
253
254pub proof fn core_is_never_a_target(i: Input)
256 requires
257 i.facts.touches_core || i.facts.sensitivity.core,
258 ensures
259 decide_spec(i) == Decision::Reject(RejectReason::CoreTarget) || decide_spec(i) == Decision::Reject(
260 RejectReason::Unauthorized,
261 ),
262{
263}
264
265pub proof fn only_administrators_resolve(i: Input)
267 requires
268 i.transition is Resolve,
269 ensures
270 !is_reject(decide_spec(i)) ==> is_admin(*i.authz),
271{
272}
273
274pub proof fn users_cannot_change_generations(i: Input)
277 requires
278 is_user(i.authz.actor),
279 !i.authz.capabilities.administrator,
280 i.transition is SwitchGeneration || i.transition is Revert || i.transition is Rollback
281 || i.transition is LoadModel || i.transition is RestoreSnapshot || i.transition is CollectGarbage
282 || i.transition is BindDriver || i.transition is Resolve,
283 ensures
284 decide_spec(i) == Decision::Reject(RejectReason::Unauthorized),
285{
286}
287
288pub proof fn i14_facts_caller_is_not_authority(a: Input, b: Input)
291 requires
292 *a.transition == *b.transition,
293 a.facts.affected_units == b.facts.affected_units,
294 a.facts.sensitivity == b.facts.sensitivity,
295 a.facts.disruption == b.facts.disruption,
296 a.facts.recovery == b.facts.recovery,
297 a.facts.touches_manifest == b.facts.touches_manifest,
298 a.facts.touches_canonical_probe == b.facts.touches_canonical_probe,
299 a.facts.changes_desired == b.facts.changes_desired,
300 a.facts.touches_core == b.facts.touches_core,
301 a.facts.below_security_floor == b.facts.below_security_floor,
302 a.facts.other_sessions_affected == b.facts.other_sessions_affected,
303 a.facts.affected_sessions == b.facts.affected_sessions,
304 a.facts.restores_unattributed_stop == b.facts.restores_unattributed_stop,
305 a.facts.restarts_failed_unit == b.facts.restarts_failed_unit,
306 a.facts.removes_sensitivity == b.facts.removes_sensitivity,
307 a.facts.removes == b.facts.removes,
308 a.facts.removes_each == b.facts.removes_each,
309 a.facts.restarts_held_unit == b.facts.restarts_held_unit,
310 a.facts.config_diff == b.facts.config_diff,
311 a.known_good == b.known_good,
312 a.live_tested == b.live_tested,
313 *a.cumulative == *b.cumulative,
314 *a.authz == *b.authz,
315 *a.settings == *b.settings,
316 *a.tables == *b.tables,
317 *a.locked == *b.locked,
318 a.revert_to == b.revert_to,
319 ensures
320 decide_spec(a) == decide_spec(b),
321{
322}
323
324pub proof fn i17_administrators_never_empty(in_admin_group: bool)
328 ensures
329 administrator_spec(Some(0u32), in_admin_group),
330 exists|uid: Option<u32>| #[trigger] administrator_spec(uid, false),
331{
332 assert(administrator_spec(Some(0u32), false));
333}
334
335}