Skip to content

Commit d371ccc

Browse files
Merge pull request #1114 from mrstanb/multithreaded-uaf-analysis
Multi-threaded UAF Analysis
2 parents ed2b149 + 8f295ee commit d371ccc

File tree

1 file changed

+56
-7
lines changed

1 file changed

+56
-7
lines changed

src/analyses/useAfterFree.ml

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
(** An analysis for the detection of use-after-free vulnerabilities. *)
1+
(** An analysis for the detection of use-after-free vulnerabilities ([useAfterFree]). *)
22

33
open GoblintCil
44
open Analyses
55
open MessageCategory
66

77
module ToppedVarInfoSet = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All Heap Variables" end)
8+
module ThreadIdSet = SetDomain.Make(ThreadIdDomain.ThreadLifted)
89

910
module Spec : Analyses.MCPSpec =
1011
struct
@@ -14,22 +15,61 @@ struct
1415

1516
module D = ToppedVarInfoSet
1617
module C = Lattice.Unit
18+
module G = ThreadIdSet
19+
module V = VarinfoV
1720

1821
(** TODO: Try out later in benchmarks to see how we perform with and without context-sensititivty *)
1922
let context _ _ = ()
2023

2124

2225
(* HELPER FUNCTIONS *)
2326

24-
let warn_for_multi_threaded ctx behavior cwe_number =
25-
if not (ctx.ask (Queries.MustBeSingleThreaded { since_start = true })) then
26-
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Program isn't running in single-threaded mode. Use-After-Free might occur due to multi-threading"
27+
let get_current_threadid ctx =
28+
ctx.ask Queries.CurrentThreadId
29+
30+
let warn_for_multi_threaded_access ctx (heap_var:varinfo) behavior cwe_number =
31+
let freeing_threads = ctx.global heap_var in
32+
(* If we're single-threaded or there are no threads freeing the memory, we have nothing to WARN about *)
33+
if ctx.ask (Queries.MustBeSingleThreaded { since_start = true }) || ThreadIdSet.is_empty freeing_threads then ()
34+
else begin
35+
let possibly_started current = function
36+
| `Lifted tid ->
37+
let threads = ctx.ask Queries.CreatedThreads in
38+
let not_started = MHP.definitely_not_started (current, threads) tid in
39+
let possibly_started = not not_started in
40+
possibly_started
41+
| `Top -> true
42+
| `Bot -> false
43+
in
44+
let equal_current current = function
45+
| `Lifted tid ->
46+
ThreadId.Thread.equal current tid
47+
| `Top -> true
48+
| `Bot -> false
49+
in
50+
match get_current_threadid ctx with
51+
| `Lifted current ->
52+
let possibly_started = ThreadIdSet.exists (possibly_started current) freeing_threads in
53+
if possibly_started then
54+
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "There's a thread that's been started in parallel with the memory-freeing threads for heap variable %a. Use-After-Free might occur" CilType.Varinfo.pretty heap_var
55+
else begin
56+
let current_is_unique = ThreadId.Thread.is_unique current in
57+
let any_equal_current threads = ThreadIdSet.exists (equal_current current) threads in
58+
if not current_is_unique && any_equal_current freeing_threads then
59+
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Current thread is not unique and a Use-After-Free might occur for heap variable %a" CilType.Varinfo.pretty heap_var
60+
else if D.mem heap_var ctx.local then
61+
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Use-After-Free might occur in current unique thread %a for heap variable %a" ThreadIdDomain.FlagConfiguredTID.pretty current CilType.Varinfo.pretty heap_var
62+
end
63+
| `Top ->
64+
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "CurrentThreadId is top. A Use-After-Free might occur for heap variable %a" CilType.Varinfo.pretty heap_var
65+
| `Bot ->
66+
M.warn ~category:MessageCategory.Analyzer "CurrentThreadId is bottom"
67+
end
2768

2869
let rec warn_lval_might_contain_freed ?(is_double_free = false) (transfer_fn_name:string) ctx (lval:lval) =
2970
let state = ctx.local in
3071
let undefined_behavior = if is_double_free then Undefined DoubleFree else Undefined UseAfterFree in
3172
let cwe_number = if is_double_free then 415 else 416 in
32-
warn_for_multi_threaded ctx undefined_behavior cwe_number; (* Simple solution to warn when multi-threaded *)
3373
let rec offset_might_contain_freed offset =
3474
match offset with
3575
| NoOffset -> ()
@@ -53,7 +93,9 @@ struct
5393
|> List.map fst
5494
|> List.filter (fun var -> ctx.ask (Queries.IsHeapVar var))
5595
in
56-
List.iter warn_for_heap_var pointed_to_heap_vars (* Warn for all heap vars that the lval possibly points to *)
96+
List.iter warn_for_heap_var pointed_to_heap_vars; (* Warn for all heap vars that the lval possibly points to *)
97+
(* Warn for a potential multi-threaded UAF for all heap vars that the lval possibly points to *)
98+
List.iter (fun heap_var -> warn_for_multi_threaded_access ctx heap_var undefined_behavior cwe_number) pointed_to_heap_vars
5799
| _ -> ()
58100

59101
and warn_exp_might_contain_freed ?(is_double_free = false) (transfer_fn_name:string) ctx (exp:exp) =
@@ -83,6 +125,10 @@ struct
83125
| StartOf lval
84126
| AddrOf lval -> warn_lval_might_contain_freed ~is_double_free transfer_fn_name ctx lval
85127

128+
let side_effect_mem_free ctx freed_heap_vars threadid =
129+
let threadid = G.singleton threadid in
130+
D.iter (fun var -> ctx.sideg var threadid) freed_heap_vars
131+
86132

87133
(* TRANSFER FUNCTIONS *)
88134

@@ -138,8 +184,11 @@ struct
138184
Queries.LS.elements a
139185
|> List.map fst
140186
|> List.filter (fun var -> ctx.ask (Queries.IsHeapVar var))
187+
|> D.of_list
141188
in
142-
D.join state (D.of_list pointed_to_heap_vars) (* Add all heap vars, which ptr points to, to the state *)
189+
(* Side-effect the tid that's freeing all the heap vars collected here *)
190+
side_effect_mem_free ctx pointed_to_heap_vars (get_current_threadid ctx);
191+
D.join state (pointed_to_heap_vars) (* Add all heap vars, which ptr points to, to the state *)
143192
| _ -> state
144193
end
145194
| _ -> state

0 commit comments

Comments
 (0)