-
Notifications
You must be signed in to change notification settings - Fork 249
Expand file tree
/
Copy pathEx10a.fst
More file actions
114 lines (91 loc) · 3.76 KB
/
Ex10a.fst
File metadata and controls
114 lines (91 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
(*
Copyright 2008-2018 Microsoft Research
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*)
module Ex10a
open FStar.All
open FStar.List.Tot
open FStar.Ref
type file = string
(* Each entry is an element in our access-control list *)
type entry =
| Readable of string
| Writable of string
type db = list entry
(* We define two pure functions that test whether
the suitable permission exists in some db *)
let canWrite db file =
Some? (tryFind (function Writable x -> x=file | _ -> false) db)
let canRead db file =
Some? (tryFind (function Readable x | Writable x -> x=file) db)
(* The acls reference stores the current access-control list, initially empty *)
val acls: ref db
#push-options "--warn_error -272" //Warning_TopLevelEffect
let acls = ST.alloc []
#pop-options
(*
Here are two stateful functions which alter the access control list.
In reality, these functions may themselves be protected by some
further authentication mechanism to ensure that an attacker cannot
alter the access control list
F* infers a fully precise predicate transformer semantics for them.
*)
(*
// Uncomment these types and make them precise enough to pass the test
// BEGIN: Ex10aExercise
val grant : e:entry -> ST unit (requires (fun h -> True))
(ensures (fun h x h' -> True))
val revoke: e:entry -> ST unit (requires (fun h -> True))
(ensures (fun h x h' -> True))
// END: Ex10aExercise
*)
let grant e = acls := e::!acls
let revoke e =
let db = filter (fun e' -> e<>e') !acls in
acls := db
(* Next, we model two primitives that provide access to files *)
(* We use two heap predicates, which will serve as stateful pre-conditions *)
type canRead_t f h = canRead (sel h acls) f == true
type canWrite_t f h = canWrite (sel h acls) f == true
(* In order to call `read`, you need to prove
the `canRead f` permission exists in the input heap *)
assume val read: file:string -> ST string
(requires (canRead_t file))
(ensures (fun h s h' -> modifies_none h h'))
(* Likewise for `delete` *)
assume val delete: file:string -> ST unit
(requires (canWrite_t file))
(ensures (fun h s h' -> modifies_none h h'))
(* Then, we have a top-level API, which provides protected
access to a file by first checking the access control list.
If the check fails, it raises a fatal exception using `failwith`.
As such, it is defined to have effect `All`, which combines
both state and exceptions.
Regardless, the specification proves that `safe_Delete`
does not change the heap.
*)
val safe_delete: file -> All unit
(requires (fun h -> True))
(ensures (fun h x h' -> modifies_none h h'))
let safe_delete file =
if canWrite !acls file
then delete file
else failwith "unwritable"
(* Finally, we have a top-level client program *)
val test_acls: file -> ML unit
let test_acls f =
grant (Readable f); (* ok *)
let _ = read f in (* ok --- it's in the acl *)
//delete f; (* not ok --- we're only allowed to read it *)
safe_delete f; (* ok, but fails dynamically *)
revoke (Readable f);
//let _ = read f in (* not ok any more *)
()