-
Notifications
You must be signed in to change notification settings - Fork 88
Expand file tree
/
Copy pathraceAnalysis.ml
More file actions
397 lines (347 loc) · 16.8 KB
/
raceAnalysis.ml
File metadata and controls
397 lines (347 loc) · 16.8 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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
(** Data race analysis ([race]). *)
open GoblintCil
open Analyses
(** Data race analysis with tries for offsets and type-based memory locations for open code.
Accesses are to memory locations ({{!Access.Memo} memos}) which consist of a root and offset.
{{!Access.MemoRoot} Root} can be:
+ variable, if access is to known global variable or alloc-variable;
+ type, if access is to unknown pointer.
Accesses are (now) collected to sets for each corresponding memo,
after points-to sets are resolved, during postsolving.
Race checking is performed per-memo,
except must additionally account for accesses to other memos (see diagram below):
+ access to [s.f] can race with access to a prefix like [s], which writes an entire struct at once;
+ access to [s.f] can race with type-based access like [(struct S).f];
+ access to [(struct S).f] can race with type-based access to a suffix like [(int)].
+ access to [(struct T).s.f] can race with type-based access like [(struct S)], which is a combination of the above.
These are accounted for lazily (unlike in the past).
Prefixes (a.k.a. inner distribution) are handled using a trie data structure enriched with lattice properties.
Race checking starts at the root and passes accesses to ancestor nodes down to children.
Type suffixes (a.k.a. outer distribution) are handled by computing successive immediate type suffixes transitively
and accessing corresponding offsets from corresponding root tries in the global invariant.
Type suffix prefixes (for the combination of the two) are handled by passing type suffix accesses down when traversing the prefix trie.
Race checking happens at each trie node with the above three access sets at hand using {!Access.group_may_race}.
All necessary combinations between the four classes are handled, but unnecessary repeated work is carefully avoided.
E.g. accesses which are pairwise checked at some prefix are not re-checked pairwise at a node.
Thus, races (with prefixes or type suffixes) are reported for most precise memos with actual accesses:
at the longest prefix and longest type suffix.
Additionally, accesses between prefix and type suffix intersecting at a node are checked.
These races are reported at the unique memo at the intersection of the prefix and the type suffix.
This requires an implementation hack to still eagerly do outer distribution, but only of empty access sets.
It ensures that corresponding trie nodes exist for traversal later. *)
(** Given C declarations:
{@c[
struct S {
int f;
};
struct T {
struct S s;
};
struct T t;
]}
Example structure of related memos for race checking:
{v
(int) (S) (T)
\ / \ / \
f s t
\ / \ /
f s
\ /
f
v}
where:
- [(int)] is a type-based memo root for the primitive [int] type;
- [(S)] and [(T)] are short for [(struct S)] and [(struct T)], which are type-based memo roots;
- prefix relations are indicated by [/], so access paths run diagonally from top-right to bottom-left;
- type suffix relations are indicated by [\ ].
All same-node races:
- Race between [t.s.f] and [t.s.f] is checked/reported at [t.s.f].
- Race between [t.s] and [t.s] is checked/reported at [t.s].
- Race between [t] and [t] is checked/reported at [t].
- Race between [(T).s.f] and [(T).s.f] is checked/reported at [(T).s.f].
- Race between [(T).s] and [(T).s] is checked/reported at [(T).s].
- Race between [(T)] and [(T)] is checked/reported at [(T)].
- Race between [(S).f] and [(S).f] is checked/reported at [(S).f].
- Race between [(S)] and [(S)] is checked/reported at [(S)].
- Race between [(int)] and [(int)] is checked/reported at [(int)].
All prefix races:
- Race between [t.s.f] and [t.s] is checked/reported at [t.s.f].
- Race between [t.s.f] and [t] is checked/reported at [t.s.f].
- Race between [t.s] and [t] is checked/reported at [t.s].
- Race between [(T).s.f] and [(T).s] is checked/reported at [(T).s.f].
- Race between [(T).s.f] and [(T)] is checked/reported at [(T).s.f].
- Race between [(T).s] and [(T)] is checked/reported at [(T).s].
- Race between [(S).f] and [(S)] is checked/reported at [(S).f].
All type suffix races:
- Race between [t.s.f] and [(T).s.f] is checked/reported at [t.s.f].
- Race between [t.s.f] and [(S).f] is checked/reported at [t.s.f].
- Race between [t.s.f] and [(int)] is checked/reported at [t.s.f].
- Race between [(T).s.f] and [(S).f] is checked/reported at [(T).s.f].
- Race between [(T).s.f] and [(int)] is checked/reported at [(T).s.f].
- Race between [(S).f] and [(int)] is checked/reported at [(S).f].
- Race between [t.s] and [(T).s] is checked/reported at [t.s].
- Race between [t.s] and [(S)] is checked/reported at [t.s].
- Race between [(T).s] and [(S)] is checked/reported at [(T).s].
- Race between [t] and [(T)] is checked/reported at [t].
All type suffix prefix races:
- Race between [t.s.f] and [(T).s] is checked/reported at [t.s.f].
- Race between [t.s.f] and [(T)] is checked/reported at [t.s.f].
- Race between [t.s.f] and [(S)] is checked/reported at [t.s.f].
- Race between [(T).s.f] and [(S)] is checked/reported at [(T).s.f].
- Race between [t.s] and [(T)] is checked/reported at [t.s].
All prefix-type suffix races:
- Race between [t.s] and [(T).s.f] is checked/reported at [t.s.f].
- Race between [t.s] and [(S).f] is checked/reported at [t.s.f].
- Race between [t.s] and [(int)] is checked/reported at [t.s.f].
- Race between [t] and [(T).s.f] is checked/reported at [t.s.f].
- Race between [t] and [(S).f] is checked/reported at [t.s.f].
- Race between [t] and [(int)] is checked/reported at [t.s.f].
- Race between [t] and [(T).s] is checked/reported at [t.s].
- Race between [t] and [(S)] is checked/reported at [t.s].
- Race between [(T).s] and [(S).f] is checked/reported at [(T).s.f].
- Race between [(T).s] and [(int)] is checked/reported at [(T).s.f].
- Race between [(T)] and [(S).f] is checked/reported at [(T).s.f].
- Race between [(T)] and [(int)] is checked/reported at [(T).s.f].
- Race between [(T)] and [(S)] is checked/reported at [(T).s].
- Race between [(S)] and [(int)] is checked/reported at [(S).f]. *)
(** Data race analyzer without base --- this is the new standard *)
module Spec =
struct
include UnitAnalysis.Spec
let name () = "race"
(* Two global invariants:
1. memoroot -> (offset --trie--> accesses) -- used for warnings
2. varinfo -> set of memo -- used for IterSysVars Global *)
module V =
struct
include Printable.Either (Access.MemoRoot) (CilType.Varinfo)
let name () = "race"
let access x = `Left x
let vars x = `Right x
let is_write_only _ = true
end
module MemoSet = SetDomain.Make (Access.Memo)
module OneOffset =
struct
include Printable.StdLeaf
type t =
| Field of CilType.Fieldinfo.t
| Index
[@@deriving eq, ord, hash, to_yojson]
let name () = "oneoffset"
let show = function
| Field f -> CilType.Fieldinfo.show f
| Index -> "?"
include Printable.SimpleShow (struct
type nonrec t = t
let show = show
end)
let to_offset : t -> Offset.Unit.t = function
| Field f -> `Field (f, `NoOffset)
| Index -> `Index ((), `NoOffset)
end
module OffsetTrie =
struct
(* LiftBot such that add_distribute_outer can side-effect empty set to indicate
all offsets that exist for prefix-type_suffix race checking.
Otherwise, there are no trie nodes to traverse to where this check must happen. *)
include TrieDomain.Make (OneOffset) (Lattice.LiftBot (Access.AS))
let rec find (offset : Offset.Unit.t) ((accs, children) : t) : value =
match offset with
| `NoOffset -> accs
| `Field (f, offset') -> find offset' (ChildMap.find (Field f) children)
| `Index ((), offset') -> find offset' (ChildMap.find Index children)
let rec singleton (offset : Offset.Unit.t) (value : value) : t =
match offset with
| `NoOffset -> (value, ChildMap.empty ())
| `Field (f, offset') -> (`Bot, ChildMap.singleton (Field f) (singleton offset' value))
| `Index ((), offset') -> (`Bot, ChildMap.singleton Index (singleton offset' value))
end
module G =
struct
include Lattice.Lift2Conf (struct include Printable.DefaultConf let expand1 = false let expand2 = false end) (OffsetTrie) (MemoSet)
let access = function
| `Bot -> OffsetTrie.bot ()
| `Lifted1 x -> x
| _ -> failwith "Race.access"
let vars = function
| `Bot -> MemoSet.bot ()
| `Lifted2 x -> x
| _ -> failwith "Race.vars"
let create_access access = `Lifted1 access
let create_vars vars = `Lifted2 vars
end
let safe = ref 0
let vulnerable = ref 0
let unsafe = ref 0
let init marshal =
safe := 0;
vulnerable := 0;
unsafe := 0
let side_vars man memo =
match memo with
| (`Var v, _) ->
if !AnalysisState.should_warn then
man.sideg (V.vars v) (G.create_vars (MemoSet.singleton memo))
| _ ->
()
let side_access man acc ((memoroot, offset) as memo) =
if !AnalysisState.should_warn then
man.sideg (V.access memoroot) (G.create_access (OffsetTrie.singleton offset (`Lifted (Access.AS.singleton acc))));
side_vars man memo
(** Side-effect empty access set for prefix-type_suffix race checking. *)
let side_access_empty man ((memoroot, offset) as memo) =
if !AnalysisState.should_warn then
man.sideg (V.access memoroot) (G.create_access (OffsetTrie.singleton offset (`Lifted (Access.AS.empty ()))));
side_vars man memo
(** Get immediate type_suffix memo. *)
let type_suffix_memo ((root, offset) : Access.Memo.t) : Access.Memo.t option =
(* No need to make ana.race.direct-arithmetic return None here,
because (int) is empty anyway since Access.add_distribute_outer isn't called. *)
match root, offset with
| `Var v, _ -> Some (`Type (Cil.typeSig v.vtype), offset) (* global.foo.bar -> (struct S).foo.bar *) (* TODO: Alloc variables void type *)
| _, `NoOffset -> None (* primitive type *)
| _, `Field (f, offset') -> Some (`Type (Cil.typeSig f.ftype), offset') (* (struct S).foo.bar -> (struct T).bar *)
| `Type (TSArray (ts, _, _)), `Index ((), offset') -> Some (`Type ts, offset') (* (int[])[*] -> int *)
| _, `Index ((), offset') -> None (* TODO: why indexing on non-array? *)
let rec find_type_suffix' man ((root, offset) as memo : Access.Memo.t) : Access.AS.t =
let trie = G.access (man.global (V.access root)) in
let accs =
match OffsetTrie.find offset trie with
| `Lifted accs -> accs
| `Bot -> Access.AS.empty ()
in
let type_suffix = find_type_suffix man memo in
Access.AS.union accs type_suffix
(** Find accesses from all type_suffixes transitively. *)
and find_type_suffix man (memo : Access.Memo.t) : Access.AS.t =
match type_suffix_memo memo with
| Some type_suffix_memo -> find_type_suffix' man type_suffix_memo
| None -> Access.AS.empty ()
let query man (type a) (q: a Queries.t): a Queries.result =
match q with
| WarnGlobal g ->
let g: V.t = Obj.obj g in
begin match g with
| `Left g' -> (* accesses *)
(* Logs.debug "WarnGlobal %a" Access.MemoRoot.pretty g'; *)
let trie = G.access (man.global g) in
(** Distribute access to contained fields. *)
let rec distribute_inner offset (accs, children) ~prefix ~type_suffix_prefix =
let accs =
match accs with
| `Lifted accs -> accs
| `Bot -> Access.AS.empty ()
in
let type_suffix = find_type_suffix man (g', offset) in
if not (Access.AS.is_empty accs) || (not (Access.AS.is_empty prefix) && not (Access.AS.is_empty type_suffix)) then (
let memo = (g', offset) in
let mem_loc_str = GobPretty.sprint Access.Memo.pretty memo in
Timing.wrap ~args:[("memory location", `String mem_loc_str)] "race" (Access.warn_global ~safe ~vulnerable ~unsafe {node=accs; prefix; type_suffix; type_suffix_prefix}) memo
);
(* Recurse to children. *)
let prefix' = Access.AS.union prefix accs in
let type_suffix_prefix' = Access.AS.union type_suffix_prefix type_suffix in
OffsetTrie.ChildMap.iter (fun child_key child_trie ->
distribute_inner (Offset.Unit.add_offset offset (OneOffset.to_offset child_key)) child_trie ~prefix:prefix' ~type_suffix_prefix:type_suffix_prefix'
) children;
in
distribute_inner `NoOffset trie ~prefix:(Access.AS.empty ()) ~type_suffix_prefix:(Access.AS.empty ())
| `Right _ -> (* vars *)
()
end
| IterSysVars (Global g, vf) ->
MemoSet.iter (fun v ->
vf (Obj.repr (V.access v))
) (G.vars (man.global (V.vars g)))
| _ -> Queries.Result.top q
let event man e oman =
match e with
| Events.Access {exp; ad; kind; reach} when ThreadFlag.is_currently_multi (Analyses.ask_of_man man) -> (* threadflag query in post-threadspawn man *)
(* must use original (pre-assign, etc) man queries *)
let conf = 110 in
let module AD = Queries.AD in
let part_access (vo:varinfo option): MCPAccess.A.t =
(*partitions & locks*)
Obj.obj (oman.ask (PartAccess (Memory {exp; var_opt=vo; kind})))
in
let node = man.prev_node in
let add_access conf voffs =
let acc = part_access (Option.map fst voffs) in
Access.add ~side:(side_access oman {conf; kind; node; exp; acc}) ~side_empty:(side_access_empty oman) exp voffs;
in
let add_access_struct conf ci =
let acc = part_access None in
Access.add_one ~side:(side_access oman {conf; kind; node; exp; acc}) (`Type (TSComp (ci.cstruct, ci.cname, [])), `NoOffset)
in
let oask = Analyses.ask_of_man oman in
(* The following function adds accesses to the lval-set ls
-- this is the common case if we have a sound points-to set. *)
let on_ad ad includes_uk =
let conf = if reach then conf - 20 else conf in
let conf = if includes_uk then conf - 10 else conf in
let f addr =
match addr with
| AD.Addr.Addr (g,o) when g.vglob || ThreadEscape.has_escaped oask g ->
let coffs = ValueDomain.Offs.to_cil o in
add_access conf (Some (g, coffs))
| UnknownPtr -> add_access conf None
| _ -> ()
in
AD.iter f ad
in
begin match ad with
| ad when not (AD.is_top ad) ->
(* the case where the points-to set is non top and does not contain unknown values *)
on_ad ad false
| ad ->
(* the case where the points-to set is non top and contains unknown values *)
let includes_uk = ref false in
(* now we need to access all fields that might be pointed to: is this correct? *)
begin match oman.ask (ReachableUkTypes exp) with
| ts when Queries.TS.is_top ts ->
includes_uk := true
| ts ->
if not (Queries.TS.is_empty ts) then
includes_uk := true;
let f t =
match Cil.unrollType t with
| TComp (ci, _) ->
add_access_struct (conf - 50) ci
| _ -> ()
in
Queries.TS.iter f ts
end;
on_ad ad !includes_uk
(* | _ ->
add_access (conf - 60) None *) (* TODO: what about this case? *)
end;
man.local
| _ ->
man.local
let special man (lvalOpt: lval option) (f:varinfo) (arglist:exp list) : D.t =
(* perform shallow and deep invalidate according to Library descriptors *)
let desc = LibraryFunctions.find f in
if List.mem LibraryDesc.ThreadUnsafe desc.attrs && ThreadFlag.is_currently_multi (Analyses.ask_of_man man) then (
let exp = Lval (Var f, NoOffset) in
let conf = 110 in
let kind = AccessKind.Call in
let node = man.prev_node in
let vo = Some f in
let acc = Obj.obj (man.ask (PartAccess (Memory {exp; var_opt=vo; kind}))) in
side_access man {conf; kind; node; exp; acc} ((`Var f), `NoOffset) ;
);
man.local
let finalize () =
let total = !safe + !unsafe + !vulnerable in
if total > 0 then (
M.msg_group Info ~category:Race "Memory locations race summary" [
(Pretty.dprintf "safe: %d" !safe, None);
(Pretty.dprintf "vulnerable: %d" !vulnerable, None);
(Pretty.dprintf "unsafe: %d" !unsafe, None);
(Pretty.dprintf "total memory locations: %d" total, None);
];
)
end
let _ =
MCP.register_analysis ~dep:["access"] (module Spec : MCPSpec)