Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect calls of thread-unsafe functions as races #1082

Merged
merged 8 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions src/analyses/libraryFunctions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ let c_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[
("getchar", unknown []);
("putchar", unknown [drop "ch" []]);
("puts", unknown [drop "s" [r]]);
("rand", unknown ~attrs:[ThreadUnsafe] []);
("rand", special ~attrs:[ThreadUnsafe] [] Rand);
("strerror", unknown ~attrs:[ThreadUnsafe] [drop "errnum" []]);
("strspn", unknown [drop "s" [r]; drop "accept" [r]]);
("strcspn", unknown [drop "s" [r]; drop "accept" [r]]);
Expand Down Expand Up @@ -118,7 +118,6 @@ let c_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[
("_setjmp", special [__ "env" [w]] @@ fun env -> Setjmp { env }); (* only has one underscore *)
("setjmp", special [__ "env" [w]] @@ fun env -> Setjmp { env });
("longjmp", special [__ "env" [r]; __ "value" []] @@ fun env value -> Longjmp { env; value });
("rand", special [] Rand);
]

(** C POSIX library functions.
Expand Down Expand Up @@ -890,70 +889,70 @@ struct

let writesAllButFirst n f a x =
match a with
| Write | Spawn -> f a x @ drop n x
| Write | Call | Spawn -> f a x @ drop n x
| Read -> f a x
| Free -> []

let readsAllButFirst n f a x =
match a with
| Write | Spawn -> f a x
| Write | Call | Spawn -> f a x
| Read -> f a x @ drop n x
| Free -> []

let reads ns a x =
let i, o = partition ns x in
match a with
| Write | Spawn -> o
| Write | Call | Spawn -> o
| Read -> i
| Free -> []

let writes ns a x =
let i, o = partition ns x in
match a with
| Write | Spawn -> i
| Write | Call | Spawn -> i
| Read -> o
| Free -> []

let frees ns a x =
let i, o = partition ns x in
match a with
| Write | Spawn -> []
| Write | Call | Spawn -> []
| Read -> o
| Free -> i

let readsFrees rs fs a x =
match a with
| Write | Spawn -> []
| Write | Call | Spawn -> []
| Read -> keep rs x
| Free -> keep fs x

let onlyReads ns a x =
match a with
| Write | Spawn -> []
| Write | Call | Spawn -> []
| Read -> keep ns x
| Free -> []

let onlyWrites ns a x =
match a with
| Write | Spawn -> keep ns x
| Write | Call | Spawn -> keep ns x
| Read -> []
| Free -> []

let readsWrites rs ws a x =
match a with
| Write | Spawn -> keep ws x
| Write | Call | Spawn -> keep ws x
| Read -> keep rs x
| Free -> []

let readsAll a x =
match a with
| Write | Spawn -> []
| Write | Call | Spawn -> []
| Read -> x
| Free -> []

let writesAll a x =
match a with
| Write | Spawn -> x
| Write | Call | Spawn -> x
| Read -> []
| Free -> []
end
Expand Down Expand Up @@ -1165,6 +1164,8 @@ let unknown_desc ~f name = (* TODO: remove name argument, unknown function shoul
| Read when GobConfig.get_bool "sem.unknown_function.read.args" -> args
| Read -> []
| Free -> []
| Call when get_bool "sem.unknown_function.call.args" -> args
| Call -> []
| Spawn when get_bool "sem.unknown_function.spawn" -> args
| Spawn -> []
in
Expand Down
1 change: 1 addition & 0 deletions src/analyses/mutexAnalysis.ml
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ struct
let write = match kind with
| Write | Free -> true
| Read -> false
| Call
| Spawn -> false (* TODO: nonsense? *)
in
let s = GProtecting.make ~write ~recovered:is_recovered_to_st locks in
Expand Down
15 changes: 14 additions & 1 deletion src/analyses/raceAnalysis.ml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ struct
let g: V.t = Obj.obj g in
begin match g with
| `Left g' -> (* accesses *)
(* ignore (Pretty.printf "WarnGlobal %a\n" CilType.Varinfo.pretty g); *)
(* ignore (Pretty.printf "WarnGlobal %a\n" Access.MemoRoot.pretty g'); *)
let trie = G.access (ctx.global g) in
(** Distribute access to contained fields. *)
let rec distribute_inner offset (accs, children) ancestor_accs =
Expand Down Expand Up @@ -193,6 +193,19 @@ struct
| _ ->
ctx.local

let special ctx (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 then (
let e = Lval (Var f, NoOffset) in
let conf = 110 in
let loc = Option.get !Node.current_node in
let vo = Some f in
let a = Obj.obj (ctx.ask (PartAccess (Memory {exp=e; var_opt=vo; kind=Call}))) in
side_access ctx (conf, Call, loc, e, a) ((`Var f), `NoOffset) ;
);
ctx.local

let finalize () =
let total = !safe + !unsafe + !vulnerable in
if total > 0 then (
Expand Down
4 changes: 3 additions & 1 deletion src/domains/accessKind.ml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
(** Kinds of memory accesses. *)

type t =
| Write (** argument may be read or written to *)
| Write (** argument may be written to *)
| Read (** argument may be read *)
| Free (** argument may be freed *)
| Call (** argument may be called *)
| Spawn (** argument may be spawned *)
[@@deriving eq, ord, hash]
(** Specifies what is known about an argument. *)
Expand All @@ -12,6 +13,7 @@ let show: t -> string = function
| Write -> "write"
| Read -> "read"
| Free -> "free"
| Call -> "call"
| Spawn -> "spawn"

include Printable.SimpleShow (
Expand Down
7 changes: 7 additions & 0 deletions src/util/options.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,13 @@
"type": "boolean",
"default": true
},
"call": {
"title": "sem.unknown_function.call",
"description":
"Unknown function call calls reachable functions",
"type": "boolean",
"default": true
},
"invalidate": {
"title": "sem.unknown_function.invalidate",
"type": "object",
Expand Down
22 changes: 22 additions & 0 deletions tests/regression/04-mutex/90-thread-unsafe_fun_rc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;

void *t_fun(void *arg) {
pthread_mutex_lock(&mutex1);
rand(); // RACE!
pthread_mutex_unlock(&mutex1);
return NULL;
}

int main(void) {
pthread_t id;
pthread_create(&id, NULL, t_fun, NULL);
pthread_mutex_lock(&mutex2);
rand(); // RACE!
pthread_mutex_unlock(&mutex2);
pthread_join (id, NULL);
return 0;
}
21 changes: 21 additions & 0 deletions tests/regression/04-mutex/91-thread-unsafe_fun_nr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

void *t_fun(void *arg) {
pthread_mutex_lock(&mutex1);
rand(); // NORACE
pthread_mutex_unlock(&mutex1);
return NULL;
}

int main(void) {
pthread_t id;
pthread_create(&id, NULL, t_fun, NULL);
pthread_mutex_lock(&mutex1);
rand(); // NORACE
pthread_mutex_unlock(&mutex1);
pthread_join (id, NULL);
return 0;
}