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

Memory Out-Of-Bounds Access Analysis #1094

Merged
merged 41 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7bb5697
Add first rough (semi-working) memory OOB analysis
mrstanb Jun 27, 2023
a5c693e
Add 2 simple regression tests for memory OOB
mrstanb Jun 27, 2023
f350745
Stick with Lattice.Unit
mrstanb Jul 1, 2023
4d5e0dc
Rewrite the majority of the logic and checks performed in memOutOfBounds
mrstanb Jul 8, 2023
098501b
Remove printf calls
mrstanb Jul 8, 2023
6496c61
Improve warning messages a bit
mrstanb Jul 8, 2023
a796a0b
Add message category for memory OOB access
mrstanb Jul 8, 2023
84e80b1
Add CWE number 823 for memory OOB access warnings
mrstanb Jul 8, 2023
a07b690
Add a global variable that indicates whether an invalid pointer deref…
mrstanb Jul 8, 2023
4c09372
Add OOB memory access regression case with a loop
mrstanb Jul 26, 2023
f90770f
Merge branch 'master' into mem-oob-analysis
mrstanb Aug 12, 2023
503fb24
Remove wrongly overtaken goblintutil.ml
mrstanb Aug 12, 2023
0c53230
Move may_invalid_deref to analysisState
mrstanb Aug 12, 2023
c07d867
Use AnalysisState in place of Goblintutil
mrstanb Aug 12, 2023
434a4c1
Enable intervals for regression tests
mrstanb Aug 20, 2023
3db3d8c
Fix memOutOfBounds analysis and make it work
mrstanb Aug 20, 2023
6e7664d
Remove unused transfer funs
mrstanb Aug 20, 2023
50777a2
Merge branch 'master' into mem-oob-analysis
mrstanb Aug 20, 2023
79d4319
Move regression tests to a correctly numbered folder
mrstanb Aug 20, 2023
84bd2cc
Add comments to 77/03 test case
mrstanb Aug 20, 2023
6ed476e
Warn for function args in enter and not in combine_assign
mrstanb Aug 21, 2023
4e7b21b
Try to check and warn only upon dereferences
mrstanb Aug 24, 2023
86b7a54
Merge branch 'master' into mem-oob-analysis
mrstanb Aug 24, 2023
291d60c
Check for OOB mem access on the address level and only warn on deref
mrstanb Aug 25, 2023
2a3aaae
Adjust the OOB mem access test case with a loop
mrstanb Aug 25, 2023
46bd81f
Add test case with pointer arithmetic and subsequent derefs
mrstanb Aug 25, 2023
37fb4e8
Refactor to_int_dom_offs
mrstanb Aug 25, 2023
ff3e644
Cover bot and top cases in to_int_dom_offs
mrstanb Aug 25, 2023
aceffa8
Allow Queries.BlobSize to be asked for the size from the start address
mrstanb Aug 31, 2023
7fb6719
Use Queries.BlobSize for getting blob sizes without address offsets
mrstanb Aug 31, 2023
0e126df
Clean up unnecessary comments
mrstanb Aug 31, 2023
293d3e7
Add check for implicit pointer dereferences in special functions
mrstanb Aug 31, 2023
e4b349a
Add a test case with implicit dereferences
mrstanb Aug 31, 2023
4746eac
Merge branch 'master' into mem-oob-analysis
michael-schwarz Sep 6, 2023
2b45499
Disable Info warnings for test case 77/05 for now
mrstanb Sep 6, 2023
9241301
Remove TODO comment and add a few other explaining comments
mrstanb Sep 6, 2023
0bae455
Use a record instead of a tuple for Queries.BlobSize
mrstanb Sep 6, 2023
f06a0f5
Add check for bot and top address offsets
mrstanb Sep 26, 2023
44a3f4d
Merge branch 'master' into mem-oob-analysis
mrstanb Sep 26, 2023
a2bea7b
Fix issues after merge with master
mrstanb Sep 27, 2023
d48f232
Merge branch 'master' into mem-oob-analysis
michael-schwarz Sep 28, 2023
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
187 changes: 187 additions & 0 deletions src/analyses/memOutOfBounds.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
open GoblintCil
open Analyses
open MessageCategory

module AS = AnalysisState
module VDQ = ValueDomainQueries

module Spec =
mrstanb marked this conversation as resolved.
Show resolved Hide resolved
struct
include Analyses.IdentitySpec

module D = Lattice.Unit
module C = D

(* TODO: Check out later for benchmarking *)
let context _ _ = ()
mrstanb marked this conversation as resolved.
Show resolved Hide resolved

let name () = "memOutOfBounds"

(* HELPER FUNCTIONS *)

let rec exp_contains_a_ptr (exp:exp) =
match exp with
| Const _
| SizeOf _
| SizeOfStr _
| AlignOf _
| AddrOfLabel _ -> false
| Real e
| Imag e
| SizeOfE e
| AlignOfE e
| UnOp (_, e, _)
| CastE (_, e) -> exp_contains_a_ptr e
| BinOp (_, e1, e2, _) ->
exp_contains_a_ptr e1 || exp_contains_a_ptr e2
| Question (e1, e2, e3, _) ->
exp_contains_a_ptr e1 || exp_contains_a_ptr e2 || exp_contains_a_ptr e3
| Lval lval
| AddrOf lval
| StartOf lval -> lval_contains_a_ptr lval

and lval_contains_a_ptr (lval:lval) =
let (host, offset) = lval in
let host_contains_a_ptr = function
| Var v -> isPointerType v.vtype
| Mem e -> exp_contains_a_ptr e
in
let rec offset_contains_a_ptr = function
| NoOffset -> false
| Index (e, o) -> exp_contains_a_ptr e || offset_contains_a_ptr o
| Field (f, o) -> isPointerType f.ftype || offset_contains_a_ptr o
in
host_contains_a_ptr host || offset_contains_a_ptr offset

let points_to_heap_only ctx ptr =
match ctx.ask (Queries.MayPointTo ptr) with
| a when not (Queries.LS.is_top a) && not (Queries.LS.mem (dummyFunDec.svar, `NoOffset) a) ->
Queries.LS.for_all (fun (v, _) -> ctx.ask (Queries.IsHeapVar v)) a
| _ -> false

let get_size_of_ptr_target ctx ptr =
(* Call Queries.BlobSize only if ptr points solely to the heap. Otherwise we get bot *)
if points_to_heap_only ctx ptr then
ctx.ask (Queries.BlobSize ptr)
else
match ctx.ask (Queries.MayPointTo ptr) with
| a when not (Queries.LS.is_top a) ->
let pts_list = Queries.LS.elements a in
let pts_elems_to_sizes (v, _) =
if isArrayType v.vtype then
ctx.ask (Queries.EvalLength ptr)
else
let var_type_size = match Cil.getInteger (sizeOf v.vtype) with
| Some z ->
let ikindOfTypeSize = intKindForValue z true in
VDQ.ID.of_int ikindOfTypeSize z
| None -> VDQ.ID.bot ()
in
var_type_size
in
(* Map each points-to-set element to its size *)
let pts_sizes = List.map pts_elems_to_sizes pts_list in
(* Take the smallest of all sizes that ptr's contents may have *)
begin match pts_sizes with
| [] -> VDQ.ID.bot ()
| [x] -> x
| x::xs -> List.fold_left (fun acc elem ->
if VDQ.ID.compare acc elem >= 0 then elem else acc
) x xs
end
| _ ->
M.warn "Pointer %a has a points-to-set of top. An invalid memory access might occur" d_exp ptr;
VDQ.ID.top ()

let eval_ptr_offset_in_binop ctx exp =
ctx.ask (Queries.EvalInt exp)

let rec check_lval_for_oob_access ctx lval =
if not @@ lval_contains_a_ptr lval then ()
else
match lval with
| Var _, _ -> ()
| Mem e, _ -> check_exp_for_oob_access ctx e

and check_exp_for_oob_access ctx exp =
match exp with
| Const _
| SizeOf _
| SizeOfStr _
| AlignOf _
| AddrOfLabel _ -> ()
| Real e
| Imag e
| SizeOfE e
| AlignOfE e
| UnOp (_, e, _)
| CastE (_, e) -> check_exp_for_oob_access ctx e
| BinOp (bop, e1, e2, t) ->
check_binop_exp ctx bop e1 e2 t;
check_exp_for_oob_access ctx e1;
check_exp_for_oob_access ctx e2
| Question (e1, e2, e3, _) ->
check_exp_for_oob_access ctx e1;
check_exp_for_oob_access ctx e2;
check_exp_for_oob_access ctx e3
| Lval lval
| StartOf lval
| AddrOf lval -> check_lval_for_oob_access ctx lval

and check_binop_exp ctx binop e1 e2 t =
let binopexp = BinOp (binop, e1, e2, t) in
let behavior = Undefined MemoryOutOfBoundsAccess in
let cwe_number = 823 in
match binop with
| PlusPI
| IndexPI
| MinusPI ->
let ptr_size = get_size_of_ptr_target ctx e1 in
let offset_size = eval_ptr_offset_in_binop ctx e2 in
begin match VDQ.ID.is_top ptr_size, VDQ.ID.is_top offset_size with
| true, _ ->
AS.svcomp_may_invalid_deref := true;
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Pointer (%a) size in expression %a not known. Memory out-of-bounds access might occur" d_exp e1 d_exp binopexp
| _, true ->
AS.svcomp_may_invalid_deref := true;
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Operand value for pointer arithmetic in expression %a not known. Memory out-of-bounds access might occur" d_exp binopexp
| false, false ->
if ptr_size < offset_size then begin
AS.svcomp_may_invalid_deref := true;
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "Pointer size (%a) in expression %a is smaller than offset (%a) for pointer arithmetic. Memory out-of-bounds access must occur" VDQ.ID.pretty ptr_size d_exp binopexp VDQ.ID.pretty offset_size
end
end
| _ -> ()


(* TRANSFER FUNCTIONS *)

let assign ctx (lval:lval) (rval:exp) : D.t =
check_lval_for_oob_access ctx lval;
check_exp_for_oob_access ctx rval;
ctx.local

let branch ctx (exp:exp) (tv:bool) : D.t =
check_exp_for_oob_access ctx exp;
ctx.local

let return ctx (exp:exp option) (f:fundec) : D.t =
Option.iter (fun x -> check_exp_for_oob_access ctx x) exp;
ctx.local

let special ctx (lval:lval option) (f:varinfo) (arglist:exp list) : D.t =
Option.iter (fun x -> check_lval_for_oob_access ctx x) lval;
List.iter (fun arg -> check_exp_for_oob_access ctx arg) arglist;
ctx.local

let combine_assign ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (callee_local:D.t) (f_ask:Queries.ask) : D.t =
Option.iter (fun x -> check_lval_for_oob_access ctx x) lval;
List.iter (fun arg -> check_exp_for_oob_access ctx arg) args;
mrstanb marked this conversation as resolved.
Show resolved Hide resolved
ctx.local

let startstate v = ()
let exitstate v = ()
end

let _ =
MCP.register_analysis (module Spec : MCPSpec)
3 changes: 3 additions & 0 deletions src/framework/analysisState.ml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ let should_warn = ref false
(** Whether signed overflow or underflow happened *)
let svcomp_may_overflow = ref false

(** Whether an invalid pointer dereference happened *)
let svcomp_may_invalid_deref = ref false

(** A hack to see if we are currently doing global inits *)
let global_initialization = ref false

Expand Down
5 changes: 5 additions & 0 deletions src/util/messageCategory.ml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type undefined_behavior =
| ArrayOutOfBounds of array_oob
| NullPointerDereference
| UseAfterFree
| MemoryOutOfBoundsAccess
| DoubleFree
| InvalidMemoryDeallocation
| MemoryLeak
Expand Down Expand Up @@ -66,6 +67,7 @@ struct
let array_out_of_bounds e: category = create @@ ArrayOutOfBounds e
let nullpointer_dereference: category = create @@ NullPointerDereference
let use_after_free: category = create @@ UseAfterFree
let memory_out_of_bounds_access: category = create @@ MemoryOutOfBoundsAccess
let double_free: category = create @@ DoubleFree
let invalid_memory_deallocation: category = create @@ InvalidMemoryDeallocation
let memory_leak: category = create @@ MemoryLeak
Expand Down Expand Up @@ -105,6 +107,7 @@ struct
| "array_out_of_bounds" -> ArrayOutOfBounds.from_string_list t
| "nullpointer_dereference" -> nullpointer_dereference
| "use_after_free" -> use_after_free
| "memory_out_of_bounds_access" -> memory_out_of_bounds_access
| "double_free" -> double_free
| "invalid_memory_deallocation" -> invalid_memory_deallocation
| "uninitialized" -> uninitialized
Expand All @@ -117,6 +120,7 @@ struct
| ArrayOutOfBounds e -> "ArrayOutOfBounds" :: ArrayOutOfBounds.path_show e
| NullPointerDereference -> ["NullPointerDereference"]
| UseAfterFree -> ["UseAfterFree"]
| MemoryOutOfBoundsAccess -> ["MemoryOutOfBoundsAccess"]
| DoubleFree -> ["DoubleFree"]
| InvalidMemoryDeallocation -> ["InvalidMemoryDeallocation"]
| MemoryLeak -> ["MemoryLeak"]
Expand Down Expand Up @@ -229,6 +233,7 @@ let behaviorName = function
|Undefined u -> match u with
|NullPointerDereference -> "NullPointerDereference"
|UseAfterFree -> "UseAfterFree"
|MemoryOutOfBoundsAccess -> "MemoryOutOfBoundsAccess"
|DoubleFree -> "DoubleFree"
|InvalidMemoryDeallocation -> "InvalidMemoryDeallocation"
|MemoryLeak -> "MemoryLeak"
Expand Down
14 changes: 14 additions & 0 deletions tests/regression/77-mem-oob/01-oob-heap-simple.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval
#include <stdlib.h>

int main(int argc, char const *argv[]) {
char *ptr = malloc(5 * sizeof(char));

*ptr = 'a';//NOWARN
*(ptr + 1) = 'b';//NOWARN
*(ptr + 10) = 'c';//WARN

free(ptr);

return 0;
}
12 changes: 12 additions & 0 deletions tests/regression/77-mem-oob/02-oob-stack-simple.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval
#include <stdlib.h>

int main(int argc, char const *argv[]) {
int i = 42;
int *ptr = &i;

*ptr = 5;//NOWARN
*(ptr + 10) = 55;//WARN

return 0;
}
18 changes: 18 additions & 0 deletions tests/regression/77-mem-oob/03-oob-loop.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// PARAM: --set ana.activated[+] memOutOfBounds --enable ana.int.interval
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char const *argv[]) {
char *ptr = malloc(5 * sizeof(char));

for (int i = 0; i < 10; i++) {
ptr++;
}

//TODO: We cannot currently detect OOB memory accesses happening due to loops like the one above
// => Both lines below can't have WARNs for now
printf("%s", *ptr); //NOWARN
free(ptr); //NOWARN

return 0;
}