Skip to content

Commit 5ee8c42

Browse files
authored
Merge pull request #1864 from goblint/uaf-event
Use access events in useAfterFree analysis
2 parents 3ec10b4 + 6d9c2f1 commit 5ee8c42

6 files changed

Lines changed: 119 additions & 104 deletions

File tree

src/analyses/accessAnalysis.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ struct
2929
let init _ =
3030
collect_local := get_bool "witness.yaml.enabled" && get_bool "witness.invariant.accessed";
3131
let activated = get_string_list "ana.activated" in
32-
emit_single_threaded := List.mem (ModifiedSinceSetjmp.Spec.name ()) activated || List.mem (PoisonVariables.Spec.name ()) activated
32+
emit_single_threaded := List.mem (ModifiedSinceSetjmp.Spec.name ()) activated || List.mem (PoisonVariables.Spec.name ()) activated || List.mem (UseAfterFree.Spec.name ()) activated (* TODO: some of these don't have access as dependency *)
3333

3434
let do_access (man: (D.t, G.t, C.t, V.t) man) (kind:AccessKind.t) (reach:bool) (e:exp) =
3535
if M.tracing then M.trace "access" "do_access %a %a %B" d_exp e AccessKind.pretty kind reach;

src/analyses/useAfterFree.ml

Lines changed: 38 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ struct
3131
let get_joined_threads man =
3232
man.ask Queries.MustJoinedThreads
3333

34-
let warn_for_multi_threaded_access man ?(is_double_free = false) (heap_var:varinfo) behavior cwe_number =
34+
let warn_for_multi_threaded_access man ?(is_free = false) (heap_var:varinfo) behavior cwe_number =
3535
let freeing_threads = man.global heap_var in
3636
(* If we're single-threaded or there are no threads freeing the memory, we have nothing to WARN about *)
3737
if man.ask (Queries.MustBeSingleThreaded { since_start = true }) || G.is_empty freeing_threads then ()
@@ -58,102 +58,33 @@ struct
5858
| `Top -> true
5959
| `Bot -> false
6060
in
61-
let bug_name = if is_double_free then "Double Free" else "Use After Free" in
61+
let bug_name = if is_free then "Double Free" else "Use After Free" in
6262
match get_current_threadid man with
6363
| `Lifted current ->
6464
let possibly_started = G.exists (other_possibly_started current) freeing_threads in
6565
if possibly_started then begin
66-
if is_double_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref;
66+
if is_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref;
6767
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. %s might occur" CilType.Varinfo.pretty heap_var bug_name
6868
end
6969
else begin
7070
let current_is_unique = ThreadId.Thread.is_unique current in
7171
let any_equal_current threads = G.exists (equal_current current) threads in
7272
if not current_is_unique && any_equal_current freeing_threads then begin
73-
if is_double_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref;
73+
if is_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref;
7474
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Current thread is not unique and a %s might occur for heap variable %a" bug_name CilType.Varinfo.pretty heap_var
7575
end
7676
else if HeapVars.mem heap_var (snd man.local) then begin
77-
if is_double_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref;
77+
if is_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref;
7878
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "%s might occur in current unique thread %a for heap variable %a" bug_name ThreadIdDomain.Thread.pretty current CilType.Varinfo.pretty heap_var
7979
end
8080
end
8181
| `Top ->
82-
if is_double_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref;
82+
if is_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref;
8383
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "CurrentThreadId is top. %s might occur for heap variable %a" bug_name CilType.Varinfo.pretty heap_var
8484
| `Bot ->
8585
M.warn ~category:MessageCategory.Analyzer "CurrentThreadId is bottom"
8686
end
8787

88-
let rec warn_lval_might_contain_freed ?(is_implicitly_derefed = false) ?(is_double_free = false) (transfer_fn_name:string) man (lval:lval) =
89-
match is_implicitly_derefed, is_double_free, lval with
90-
(* If we're not checking for a double-free and there's no deref happening, then there's no need to check for an invalid deref or an invalid free *)
91-
| false, false, (Var _, NoOffset) -> ()
92-
| _ ->
93-
let state = man.local in
94-
let undefined_behavior = if is_double_free then Undefined DoubleFree else Undefined UseAfterFree in
95-
let cwe_number = if is_double_free then 415 else 416 in
96-
let rec offset_might_contain_freed offset =
97-
match offset with
98-
| NoOffset -> ()
99-
| Field (f, o) -> offset_might_contain_freed o
100-
| Index (e, o) -> warn_exp_might_contain_freed transfer_fn_name man e; offset_might_contain_freed o
101-
in
102-
let (lval_host, o) = lval in offset_might_contain_freed o; (* Check the lval's offset *)
103-
let lval_to_query =
104-
match lval_host with
105-
| Var _ -> Lval lval
106-
| Mem _ -> mkAddrOf lval (* Take the lval's address if its lhost is of the form *p, where p is a ptr *)
107-
in
108-
begin match man.ask (Queries.MayPointTo lval_to_query) with
109-
| ad when not (Queries.AD.is_top ad) ->
110-
let warn_for_heap_var v =
111-
if HeapVars.mem v (snd state) then begin
112-
if is_double_free then set_mem_safety_flag InvalidFree else set_mem_safety_flag InvalidDeref;
113-
M.warn ~category:(Behavior undefined_behavior) ~tags:[CWE cwe_number] "lval (%s) in \"%s\" points to a maybe freed memory region" v.vname transfer_fn_name
114-
end
115-
in
116-
let pointed_to_heap_vars =
117-
Queries.AD.fold (fun addr vars ->
118-
match addr with
119-
| Queries.AD.Addr.Addr (v,_) when man.ask (Queries.IsAllocVar v) -> v :: vars
120-
| _ -> vars
121-
) ad []
122-
in
123-
(* Warn for all heap vars that the lval possibly points to *)
124-
List.iter warn_for_heap_var pointed_to_heap_vars;
125-
(* Warn for a potential multi-threaded UAF for all heap vars that the lval possibly points to *)
126-
List.iter (fun heap_var -> warn_for_multi_threaded_access man ~is_double_free heap_var undefined_behavior cwe_number) pointed_to_heap_vars
127-
| _ -> ()
128-
end
129-
130-
and warn_exp_might_contain_freed ?(is_implicitly_derefed = false) ?(is_double_free = false) (transfer_fn_name:string) man (exp:exp) =
131-
match exp with
132-
(* Base recursion cases *)
133-
| Const _
134-
| SizeOf _
135-
| SizeOfStr _
136-
| AlignOf _
137-
| AddrOfLabel _ -> ()
138-
(* Non-base cases *)
139-
| Real e
140-
| Imag e
141-
| SizeOfE e
142-
| AlignOfE e
143-
| UnOp (_, e, _)
144-
| CastE (_, e) -> warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name man e
145-
| BinOp (_, e1, e2, _) ->
146-
warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name man e1;
147-
warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name man e2
148-
| Question (e1, e2, e3, _) ->
149-
warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name man e1;
150-
warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name man e2;
151-
warn_exp_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name man e3
152-
(* Lval cases (need [warn_lval_might_contain_freed] for them) *)
153-
| Lval lval
154-
| StartOf lval
155-
| AddrOf lval -> warn_lval_might_contain_freed ~is_implicitly_derefed ~is_double_free transfer_fn_name man lval
156-
15788
let side_effect_mem_free man freed_heap_vars threadid joined_threads =
15889
let side_effect_globals_to_heap_var heap_var =
15990
let current_globals = man.global heap_var in
@@ -165,22 +96,8 @@ struct
16596

16697
(* TRANSFER FUNCTIONS *)
16798

168-
let assign man (lval:lval) (rval:exp) : D.t =
169-
warn_lval_might_contain_freed "assign" man lval;
170-
warn_exp_might_contain_freed "assign" man rval;
171-
man.local
172-
173-
let branch man (exp:exp) (tv:bool) : D.t =
174-
warn_exp_might_contain_freed "branch" man exp;
175-
man.local
176-
177-
let return man (exp:exp option) (f:fundec) : D.t =
178-
Option.iter (fun x -> warn_exp_might_contain_freed "return" man x) exp;
179-
man.local
180-
18199
let enter man (lval:lval option) (f:fundec) (args:exp list) : (D.t * D.t) list =
182100
let caller_state = man.local in
183-
List.iter (fun arg -> warn_exp_might_contain_freed "enter" man arg) args;
184101
(* TODO: The 2nd component of the callee state needs to contain only the heap vars from the caller state which are reachable from: *)
185102
(* * Global program variables *)
186103
(* * The callee arguments *)
@@ -195,24 +112,12 @@ struct
195112
let callee_combined_state = HeapVars.join callee_stack_state callee_heap_state in
196113
(caller_stack_state, HeapVars.join caller_heap_state callee_combined_state)
197114

198-
let combine_assign man (lval:lval option) fexp (f:fundec) (args:exp list) fc (callee_local:D.t) (f_ask: Queries.ask): D.t =
199-
Option.iter (fun x -> warn_lval_might_contain_freed "enter" man x) lval;
200-
man.local
201-
202115
let special man (lval:lval option) (f:varinfo) (arglist:exp list) : D.t =
203116
let state = man.local in
204117
let desc = LibraryFunctions.find f in
205-
let is_arg_implicitly_derefed arg =
206-
let read_shallow_args = LibraryDesc.Accesses.find desc.accs { kind = Read; deep = false } arglist in
207-
let read_deep_args = LibraryDesc.Accesses.find desc.accs { kind = Read; deep = true } arglist in
208-
let write_shallow_args = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = false } arglist in
209-
let write_deep_args = LibraryDesc.Accesses.find desc.accs { kind = Write; deep = true } arglist in
210-
List.mem arg read_shallow_args || List.mem arg read_deep_args || List.mem arg write_shallow_args || List.mem arg write_deep_args
211-
in
212-
Option.iter (fun x -> warn_lval_might_contain_freed ("special: " ^ f.vname) man x) lval;
213-
List.iter (fun arg -> warn_exp_might_contain_freed ~is_implicitly_derefed:(is_arg_implicitly_derefed arg) ~is_double_free:(match desc.special arglist with Free _ -> true | _ -> false) ("special: " ^ f.vname) man arg) arglist;
214118
match desc.special arglist with
215119
| Free ptr ->
120+
(* TODO: do this using Free Access events? *)
216121
begin match man.ask (Queries.MayPointTo ptr) with
217122
| ad when not (Queries.AD.is_top ad) ->
218123
let pointed_to_heap_vars =
@@ -239,7 +144,37 @@ struct
239144
let startstate v = D.bot ()
240145
let exitstate v = D.top ()
241146

147+
let event man e oman =
148+
match e with
149+
| Events.Access {exp; ad; kind; reach} ->
150+
(* must use original (pre-assign, etc) man queries *)
151+
let is_free = kind = Free in
152+
let freed_heap_vars = snd oman.local in
153+
let undefined_behavior = if is_free then Undefined DoubleFree else Undefined UseAfterFree in
154+
let cwe_number = if is_free then 415 else 416 in
155+
let warn_for_heap_var v =
156+
if HeapVars.mem v freed_heap_vars then begin
157+
set_mem_safety_flag InvalidDeref;
158+
M.warn ~category:(Behavior undefined_behavior) ~tags:[CWE cwe_number] "lval (%s) points to a maybe freed memory region" v.vname
159+
end
160+
in
161+
if not (Queries.AD.is_top ad) then begin
162+
let pointed_to_heap_vars =
163+
Queries.AD.fold (fun addr vars ->
164+
match addr with
165+
| Queries.AD.Addr.Addr (v,_) when oman.ask (Queries.IsAllocVar v) -> v :: vars
166+
| _ -> vars
167+
) ad []
168+
in
169+
(* Warn for all heap vars that the lval possibly points to *)
170+
List.iter warn_for_heap_var pointed_to_heap_vars;
171+
(* Warn for a potential multi-threaded UAF for all heap vars that the lval possibly points to *)
172+
List.iter (fun heap_var -> warn_for_multi_threaded_access oman ~is_free heap_var undefined_behavior cwe_number) pointed_to_heap_vars
173+
end;
174+
man.local
175+
| _ ->
176+
man.local
242177
end
243178

244179
let _ =
245-
MCP.register_analysis (module Spec : MCPSpec)
180+
MCP.register_analysis ~dep:["access"] (module Spec : MCPSpec)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
$ goblint --set ana.activated[+] useAfterFree 18-simple-uaf.c 2>&1 | sed -r 's/sid:[0-9]+/sid:$SID/'
2+
[Warning][Behavior > Undefined > UseAfterFree][CWE-416] lval ((alloc@sid:$SID@tid:[main])) points to a maybe freed memory region (18-simple-uaf.c:11:5-11:14)
3+
[Warning][Behavior > Undefined > DoubleFree][CWE-415] lval ((alloc@sid:$SID@tid:[main])) points to a maybe freed memory region (18-simple-uaf.c:12:5-12:14)
4+
[Info][Deadcode] Logical lines of code (LLoC) summary:
5+
live: 7
6+
dead: 0
7+
total lines: 7
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//PARAM: --set ana.activated[+] useAfterFree
2+
#include <stdlib.h>
3+
4+
struct A19 {
5+
int *p;
6+
};
7+
8+
void init_and_free(struct A19 *a) {
9+
a->p = (int *)malloc(sizeof(int));
10+
11+
free(a->p);
12+
free(a->p); //WARN
13+
}
14+
15+
16+
int main(void) {
17+
struct A19 a19;
18+
a19.p = NULL;
19+
init_and_free(&a19);
20+
return 0;
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//PARAM: --set ana.activated[+] useAfterFree
2+
#include <stdlib.h>
3+
4+
struct A19 {
5+
int *p;
6+
};
7+
8+
9+
void init_and_free(struct A19 *a) {
10+
(*a).p = (int *)malloc(sizeof(int));
11+
12+
free(a);
13+
// a is freed; use-after-free
14+
*((*a).p) = 3; //WARN
15+
16+
// a already freed; Thus, this not a double free, but a use-after-free.
17+
free((*a).p); //WARN
18+
}
19+
20+
int main(void) {
21+
struct A19 *a19p = (struct A19 *)malloc(sizeof(struct A19));
22+
init_and_free(a19p);
23+
return 0;
24+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//PARAM: --set ana.activated[+] useAfterFree
2+
#include <stdlib.h>
3+
4+
struct A19 {
5+
int *p;
6+
};
7+
8+
void init_and_free(struct A19 **a) {
9+
(**a).p = (int *)malloc(sizeof(int));
10+
11+
free(a);
12+
// a already freed; Thus, this not a double free, but a use-after-free.
13+
*((**a).p) = 3; //WARN
14+
15+
// a already freed; Thus, this not a double free, but a use-after-free.
16+
free((**a).p); //WARN
17+
}
18+
19+
20+
21+
int main(void) {
22+
struct A19 **a19pp = (struct A19 **)malloc(sizeof(struct A19*));
23+
struct A19 *a19p2 = (struct A19 *)malloc(sizeof(struct A19));
24+
25+
*a19pp = a19p2;
26+
init_and_free(a19pp);
27+
return 0;
28+
}

0 commit comments

Comments
 (0)