Skip to content

Commit

Permalink
Merge pull request #1179 from mrstanb/stack-allocating-functions-prec…
Browse files Browse the repository at this point in the history
…ision

Support stack-allocating functions when allocating heap vars
  • Loading branch information
michael-schwarz authored Oct 1, 2023
2 parents 67160fe + b1f2f2d commit 55594a7
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 34 deletions.
33 changes: 23 additions & 10 deletions src/analyses/base.ml
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ struct

let longjmp_return = ref dummyFunDec.svar

let heap_var ctx =
let info = match (ctx.ask Q.HeapVar) with
let heap_var on_stack ctx =
let info = match (ctx.ask (Q.AllocVar {on_stack})) with
| `Lifted vinfo -> vinfo
| _ -> failwith("Ran without a malloc analysis.") in
info
Expand Down Expand Up @@ -1122,6 +1122,9 @@ struct

(* interpreter end *)

let is_not_heap_alloc_var ctx v =
(not (ctx.ask (Queries.IsAllocVar v))) || (ctx.ask (Queries.IsAllocVar v) && not (ctx.ask (Queries.IsHeapVar v)))

let query_invariant ctx context =
let cpa = ctx.local.BaseDomain.cpa in
let ask = Analyses.ask_of_ctx ctx in
Expand Down Expand Up @@ -1249,7 +1252,7 @@ struct
(* If there's a non-heap var or an offset in the lval set, we answer with bottom *)
(* If we're asking for the BlobSize from the base address, then don't check for offsets => we want to avoid getting bot *)
if AD.exists (function
| Addr (v,o) -> (not @@ ctx.ask (Queries.IsHeapVar v)) || (if not from_base_addr then o <> `NoOffset else false)
| Addr (v,o) -> is_not_heap_alloc_var ctx v || (if not from_base_addr then o <> `NoOffset else false)
| _ -> false) a then
Queries.Result.bot q
else (
Expand Down Expand Up @@ -1397,7 +1400,7 @@ struct
let t = match t_override with
| Some t -> t
| None ->
if a.f (Q.IsHeapVar x) then
if a.f (Q.IsAllocVar x) then
(* the vtype of heap vars will be TVoid, so we need to trust the pointer we got to this to be of the right type *)
(* i.e. use the static type of the pointer here *)
lval_type
Expand Down Expand Up @@ -1443,7 +1446,7 @@ struct
(* Optimization to avoid evaluating integer values when setting them.
The case when invariant = true requires the old_value to be sound for the meet.
Allocated blocks are representend by Blobs with additional information, so they need to be looked-up. *)
let old_value = if not invariant && Cil.isIntegralType x.vtype && not (a.f (IsHeapVar x)) && offs = `NoOffset then begin
let old_value = if not invariant && Cil.isIntegralType x.vtype && not (a.f (IsAllocVar x)) && offs = `NoOffset then begin
VD.bot_value ~varAttr:x.vattr lval_type
end else
Priv.read_global a priv_getg st x
Expand Down Expand Up @@ -2009,7 +2012,7 @@ struct

let check_invalid_mem_dealloc ctx special_fn ptr =
let has_non_heap_var = AD.exists (function
| Addr (v,_) -> not (ctx.ask (Q.IsHeapVar v))
| Addr (v,_) -> is_not_heap_alloc_var ctx v
| _ -> false)
in
let has_non_zero_offset = AD.exists (function
Expand Down Expand Up @@ -2277,13 +2280,22 @@ struct
| Unknown, "__goblint_assume_join" ->
let id = List.hd args in
Priv.thread_join ~force:true (Analyses.ask_of_ctx ctx) (priv_getg ctx.global) id st
| Alloca size, _ -> begin
match lv with
| Some lv ->
let heap_var = AD.of_var (heap_var true ctx) in
(* ignore @@ printf "alloca will allocate %a bytes\n" ID.pretty (eval_int ctx.ask gs st size); *)
set_many ~ctx (Analyses.ask_of_ctx ctx) gs st [(heap_var, TVoid [], Blob (VD.bot (), eval_int (Analyses.ask_of_ctx ctx) gs st size, true));
(eval_lv (Analyses.ask_of_ctx ctx) gs st lv, (Cilfacade.typeOfLval lv), Address heap_var)]
| _ -> st
end
| Malloc size, _ -> begin
match lv with
| Some lv ->
let heap_var =
if (get_bool "sem.malloc.fail")
then AD.join (AD.of_var (heap_var ctx)) AD.null_ptr
else AD.of_var (heap_var ctx)
then AD.join (AD.of_var (heap_var false ctx)) AD.null_ptr
else AD.of_var (heap_var false ctx)
in
(* ignore @@ printf "malloc will allocate %a bytes\n" ID.pretty (eval_int ctx.ask gs st size); *)
set_many ~ctx (Analyses.ask_of_ctx ctx) gs st [(heap_var, TVoid [], Blob (VD.bot (), eval_int (Analyses.ask_of_ctx ctx) gs st size, true));
Expand All @@ -2293,7 +2305,7 @@ struct
| Calloc { count = n; size }, _ ->
begin match lv with
| Some lv -> (* array length is set to one, as num*size is done when turning into `Calloc *)
let heap_var = heap_var ctx in
let heap_var = heap_var false ctx in
let add_null addr =
if get_bool "sem.malloc.fail"
then AD.join addr AD.null_ptr (* calloc can fail and return NULL *)
Expand Down Expand Up @@ -2336,7 +2348,7 @@ struct
let p_addr_get = get ask gs st p_addr' None in (* implicitly includes join of malloc value (VD.bot) *)
let size_int = eval_int ask gs st size in
let heap_val:value = Blob (p_addr_get, size_int, true) in (* copy old contents with new size *)
let heap_addr = AD.of_var (heap_var ctx) in
let heap_addr = AD.of_var (heap_var false ctx) in
let heap_addr' =
if get_bool "sem.malloc.fail" then
AD.join heap_addr AD.null_ptr
Expand Down Expand Up @@ -2584,6 +2596,7 @@ struct
| MayBeThreadReturn
| PartAccess _
| IsHeapVar _
| IsAllocVar _
| IsMultiple _
| CreatedThreads
| MustJoinedThreads ->
Expand Down
1 change: 1 addition & 0 deletions src/analyses/libraryDesc.ml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type math =
(** Type of special function, or {!Unknown}. *)
(* Use inline record if not single {!Cil.exp} argument. *)
type special =
| Alloca of Cil.exp
| Malloc of Cil.exp
| Calloc of { count: Cil.exp; size: Cil.exp; }
| Realloc of { ptr: Cil.exp; size: Cil.exp; }
Expand Down
3 changes: 2 additions & 1 deletion src/analyses/libraryFunctions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,8 @@ let gcc_descs_list: (string * LibraryDesc.t) list = LibraryDsl.[
("__sync_fetch_and_add", unknown (drop "ptr" [r; w] :: drop "value" [] :: VarArgs (drop' [])));
("__sync_fetch_and_sub", unknown (drop "ptr" [r; w] :: drop "value" [] :: VarArgs (drop' [])));
("__builtin_va_copy", unknown [drop "dest" [w]; drop "src" [r]]);
("__builtin_alloca", special [__ "size" []] @@ fun size -> Malloc size);
("alloca", special [__ "size" []] @@ fun size -> Alloca size);
("__builtin_alloca", special [__ "size" []] @@ fun size -> Alloca size);
]

let glibc_desc_list: (string * LibraryDesc.t) list = LibraryDsl.[
Expand Down
2 changes: 1 addition & 1 deletion src/analyses/mallocFresh.ml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct
| Malloc _
| Calloc _
| Realloc _ ->
begin match ctx.ask HeapVar with
begin match ctx.ask (AllocVar {on_stack = false}) with
| `Lifted var -> D.add var ctx.local
| _ -> ctx.local
end
Expand Down
4 changes: 2 additions & 2 deletions src/analyses/memLeak.ml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct
| Realloc _ ->
(* Warn about multi-threaded programs as soon as we encounter a dynamic memory allocation function *)
warn_for_multi_threaded ctx;
begin match ctx.ask Queries.HeapVar with
begin match ctx.ask (Queries.AllocVar {on_stack = false}) with
| `Lifted var -> D.add var state
| _ -> state
end
Expand All @@ -53,7 +53,7 @@ struct
| ad when not (Queries.AD.is_top ad) && Queries.AD.cardinal ad = 1 ->
(* Note: Need to always set "ana.malloc.unique_address_count" to a value > 0 *)
begin match Queries.AD.choose ad with
| Queries.AD.Addr.Addr (v,_) when ctx.ask (Queries.IsHeapVar v) && not @@ ctx.ask (Queries.IsMultiple v) -> D.remove v state (* Unique pointed to heap vars *)
| Queries.AD.Addr.Addr (v,_) when ctx.ask (Queries.IsAllocVar v) && ctx.ask (Queries.IsHeapVar v) && not @@ ctx.ask (Queries.IsMultiple v) -> D.remove v state (* Unique pointed to heap vars *)
| _ -> state
end
| _ -> state
Expand Down
43 changes: 29 additions & 14 deletions src/analyses/useAfterFree.ml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ open GoblintCil
open Analyses
open MessageCategory

module ToppedVarInfoSet = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All Heap Variables" end)
module AllocaVars = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All alloca() Variables" end)
module HeapVars = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All Heap Variables" end)

(* Heap vars created by alloca() and deallocated at function exit * Heap vars deallocated by free() *)
module StackAndHeapVars = Lattice.Prod(AllocaVars)(HeapVars)

module ThreadIdToJoinedThreadsMap = MapDomain.MapBot(ThreadIdDomain.ThreadLifted)(ConcDomain.MustThreadSet)

module Spec : Analyses.MCPSpec =
Expand All @@ -13,7 +18,7 @@ struct

let name () = "useAfterFree"

module D = ToppedVarInfoSet
module D = StackAndHeapVars
module C = Lattice.Unit
module G = ThreadIdToJoinedThreadsMap
module V = VarinfoV
Expand Down Expand Up @@ -75,7 +80,7 @@ struct
set_global_svcomp_var is_double_free;
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
end
else if D.mem heap_var ctx.local then begin
else if HeapVars.mem heap_var (snd ctx.local) then begin
set_global_svcomp_var is_double_free;
M.warn ~category:(Behavior behavior) ~tags:[CWE cwe_number] "%s might occur in current unique thread %a for heap variable %a" bug_name ThreadIdDomain.FlagConfiguredTID.pretty current CilType.Varinfo.pretty heap_var
end
Expand Down Expand Up @@ -110,13 +115,13 @@ struct
begin match ctx.ask (Queries.MayPointTo lval_to_query) with
| ad when not (Queries.AD.is_top ad) ->
let warn_for_heap_var v =
if D.mem v state then
if HeapVars.mem v (snd state) then
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
in
let pointed_to_heap_vars =
Queries.AD.fold (fun addr vars ->
match addr with
| Queries.AD.Addr.Addr (v,_) when ctx.ask (Queries.IsHeapVar v) -> v :: vars
| Queries.AD.Addr.Addr (v,_) when ctx.ask (Queries.IsAllocVar v) -> v :: vars
| _ -> vars
) ad []
in
Expand Down Expand Up @@ -160,8 +165,7 @@ struct
let globals_to_side_effect = G.add threadid joined_threads current_globals in
ctx.sideg heap_var globals_to_side_effect
in
D.iter side_effect_globals_to_heap_var freed_heap_vars

HeapVars.iter side_effect_globals_to_heap_var freed_heap_vars


(* TRANSFER FUNCTIONS *)
Expand All @@ -185,21 +189,26 @@ struct
let enter ctx (lval:lval option) (f:fundec) (args:exp list) : (D.t * D.t) list =
let caller_state = ctx.local in
List.iter (fun arg -> warn_exp_might_contain_freed "enter" ctx arg) args;
if D.is_empty caller_state then
if AllocaVars.is_empty (fst caller_state) && HeapVars.is_empty (snd caller_state) then
[caller_state, caller_state]
else (
let reachable_from_args = List.fold_left (fun ad arg -> Queries.AD.join ad (ctx.ask (ReachableFrom arg))) (Queries.AD.empty ()) args in
if Queries.AD.is_top reachable_from_args || D.is_top caller_state then
[caller_state, caller_state]
else
let reachable_vars = Queries.AD.to_var_may reachable_from_args in
let callee_state = D.filter (fun var -> List.mem var reachable_vars) caller_state in (* TODO: use AD.mem directly *)
let callee_state = (AllocaVars.empty (), HeapVars.filter (fun var -> List.mem var reachable_vars) (snd caller_state)) in (* TODO: use AD.mem directly *)
[caller_state, callee_state]
)

let combine_env ctx (lval:lval option) fexp (f:fundec) (args:exp list) fc (callee_local:D.t) (f_ask:Queries.ask) : D.t =
let caller_state = ctx.local in
D.join caller_state callee_local
let (caller_stack_state, caller_heap_state) = ctx.local in
let callee_stack_state = fst callee_local in
let callee_heap_state = snd callee_local in
(* Put all alloca()-vars together with all freed() vars in the caller's second component *)
(* Don't change caller's first component => caller hasn't exited yet *)
let callee_combined_state = HeapVars.join callee_stack_state callee_heap_state in
(caller_stack_state, HeapVars.join caller_heap_state callee_combined_state)

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 -> warn_lval_might_contain_freed "enter" ctx x) lval;
Expand All @@ -224,14 +233,20 @@ struct
let pointed_to_heap_vars =
Queries.AD.fold (fun addr state ->
match addr with
| Queries.AD.Addr.Addr (var,_) when ctx.ask (Queries.IsHeapVar var) -> D.add var state
| Queries.AD.Addr.Addr (var,_) when ctx.ask (Queries.IsAllocVar var) && ctx.ask (Queries.IsHeapVar var) -> HeapVars.add var state
| _ -> state
) ad (D.empty ())
) ad (HeapVars.empty ())
in
(* Side-effect the tid that's freeing all the heap vars collected here *)
side_effect_mem_free ctx pointed_to_heap_vars (get_current_threadid ctx) (get_joined_threads ctx);
(* Add all heap vars, which ptr points to, to the state *)
D.join state (pointed_to_heap_vars)
(fst state, HeapVars.join (snd state) pointed_to_heap_vars)
| _ -> state
end
| Alloca _ ->
(* Create fresh heap var for the alloca() call *)
begin match ctx.ask (Queries.AllocVar {on_stack = true}) with
| `Lifted v -> (AllocaVars.add v (fst state), snd state)
| _ -> state
end
| _ -> state
Expand Down
5 changes: 4 additions & 1 deletion src/analyses/wrapperFunctionAnalysis.ml
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,19 @@ module MallocWrapper : MCPSpec = struct
let query (ctx: (D.t, G.t, C.t, V.t) ctx) (type a) (q: a Q.t): a Q.result =
let wrapper_node, counter = ctx.local in
match q with
| Q.HeapVar ->
| Q.AllocVar {on_stack = on_stack} ->
let node = match wrapper_node with
| `Lifted wrapper_node -> wrapper_node
| _ -> node_for_ctx ctx
in
let count = UniqueCallCounter.find (`Lifted node) counter in
let var = NodeVarinfoMap.to_varinfo (ctx.ask Q.CurrentThreadId, node, count) in
var.vdecl <- UpdateCil.getLoc node; (* TODO: does this do anything bad for incremental? *)
if on_stack then var.vattr <- addAttribute (Attr ("stack_alloca", [])) var.vattr; (* If the call was for stack allocation, add an attr to mark the heap var *)
`Lifted var
| Q.IsHeapVar v ->
NodeVarinfoMap.mem_varinfo v && not @@ hasAttribute "stack_alloca" v.vattr
| Q.IsAllocVar v ->
NodeVarinfoMap.mem_varinfo v
| Q.IsMultiple v ->
begin match NodeVarinfoMap.from_varinfo v with
Expand Down
19 changes: 14 additions & 5 deletions src/domains/queries.ml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ type _ t =
| IterVars: itervar -> Unit.t t
| PathQuery: int * 'a t -> 'a t (** Query only one path under witness lifter. *)
| DYojson: FlatYojson.t t (** Get local state Yojson of one path under [PathQuery]. *)
| HeapVar: VI.t t
| AllocVar: {on_stack: bool} -> VI.t t
(* Create a variable representing a dynamic allocation-site *)
(* If on_stack is [true], then the dynamic allocation is on the stack (i.e., alloca() or a similar function was called). Otherwise, allocation is on the heap *)
| IsAllocVar: varinfo -> MayBool.t t (* [true] if variable represents dynamically allocated memory *)
| IsHeapVar: varinfo -> MayBool.t t (* TODO: is may or must? *)
| IsMultiple: varinfo -> MustBool.t t
(* For locals: Is another copy of this local variable reachable via pointers? *)
Expand Down Expand Up @@ -154,6 +157,7 @@ struct
| MayBePublicWithout _ -> (module MayBool)
| MayBeThreadReturn -> (module MayBool)
| IsHeapVar _ -> (module MayBool)
| IsAllocVar _ -> (module MayBool)
| MustBeProtectedBy _ -> (module MustBool)
| MustBeAtomic -> (module MustBool)
| MustBeSingleThreaded _ -> (module MustBool)
Expand All @@ -165,7 +169,7 @@ struct
| BlobSize _ -> (module ID)
| CurrentThreadId -> (module ThreadIdDomain.ThreadLifted)
| ThreadCreateIndexedNode -> (module ThreadNodeLattice)
| HeapVar -> (module VI)
| AllocVar _ -> (module VI)
| EvalStr _ -> (module SD)
| IterPrevVars _ -> (module Unit)
| IterVars _ -> (module Unit)
Expand Down Expand Up @@ -218,6 +222,7 @@ struct
| MayBePublicWithout _ -> MayBool.top ()
| MayBeThreadReturn -> MayBool.top ()
| IsHeapVar _ -> MayBool.top ()
| IsAllocVar _ -> MayBool.top ()
| MutexType _ -> MutexAttrDomain.top ()
| MustBeProtectedBy _ -> MustBool.top ()
| MustBeAtomic -> MustBool.top ()
Expand All @@ -230,7 +235,7 @@ struct
| BlobSize _ -> ID.top ()
| CurrentThreadId -> ThreadIdDomain.ThreadLifted.top ()
| ThreadCreateIndexedNode -> ThreadNodeLattice.top ()
| HeapVar -> VI.top ()
| AllocVar _ -> VI.top ()
| EvalStr _ -> SD.top ()
| IterPrevVars _ -> Unit.top ()
| IterVars _ -> Unit.top ()
Expand Down Expand Up @@ -290,7 +295,7 @@ struct
| Any (PartAccess _) -> 23
| Any (IterPrevVars _) -> 24
| Any (IterVars _) -> 25
| Any HeapVar -> 29
| Any (AllocVar _) -> 29
| Any (IsHeapVar _) -> 30
| Any (IsMultiple _) -> 31
| Any (EvalThread _) -> 32
Expand All @@ -315,6 +320,7 @@ struct
| Any ThreadCreateIndexedNode -> 51
| Any ThreadsJoinedCleanly -> 52
| Any (TmpSpecial _) -> 53
| Any (IsAllocVar _) -> 54

let rec compare a b =
let r = Stdlib.compare (order a) (order b) in
Expand Down Expand Up @@ -354,6 +360,7 @@ struct
else
compare (Any q1) (Any q2)
| Any (IsHeapVar v1), Any (IsHeapVar v2) -> CilType.Varinfo.compare v1 v2
| Any (IsAllocVar v1), Any (IsAllocVar v2) -> CilType.Varinfo.compare v1 v2
| Any (IsMultiple v1), Any (IsMultiple v2) -> CilType.Varinfo.compare v1 v2
| Any (EvalThread e1), Any (EvalThread e2) -> CilType.Exp.compare e1 e2
| Any (EvalJumpBuf e1), Any (EvalJumpBuf e2) -> CilType.Exp.compare e1 e2
Expand Down Expand Up @@ -394,6 +401,7 @@ struct
| Any (IterVars i) -> 0
| Any (PathQuery (i, q)) -> 31 * i + hash (Any q)
| Any (IsHeapVar v) -> CilType.Varinfo.hash v
| Any (IsAllocVar v) -> CilType.Varinfo.hash v
| Any (IsMultiple v) -> CilType.Varinfo.hash v
| Any (EvalThread e) -> CilType.Exp.hash e
| Any (EvalJumpBuf e) -> CilType.Exp.hash e
Expand Down Expand Up @@ -441,8 +449,9 @@ struct
| Any (IterPrevVars i) -> Pretty.dprintf "IterPrevVars _"
| Any (IterVars i) -> Pretty.dprintf "IterVars _"
| Any (PathQuery (i, q)) -> Pretty.dprintf "PathQuery (%d, %a)" i pretty (Any q)
| Any HeapVar -> Pretty.dprintf "HeapVar"
| Any (AllocVar {on_stack = on_stack}) -> Pretty.dprintf "AllocVar %b" on_stack
| Any (IsHeapVar v) -> Pretty.dprintf "IsHeapVar %a" CilType.Varinfo.pretty v
| Any (IsAllocVar v) -> Pretty.dprintf "IsAllocVar %a" CilType.Varinfo.pretty v
| Any (IsMultiple v) -> Pretty.dprintf "IsMultiple %a" CilType.Varinfo.pretty v
| Any (EvalThread e) -> Pretty.dprintf "EvalThread %a" CilType.Exp.pretty e
| Any (EvalJumpBuf e) -> Pretty.dprintf "EvalJumpBuf %a" CilType.Exp.pretty e
Expand Down
16 changes: 16 additions & 0 deletions tests/regression/74-use_after_free/14-alloca-uaf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//PARAM: --set ana.activated[+] useAfterFree
#include <stdlib.h>
#include <alloca.h>

int *f() {
int *c = alloca(sizeof(int));
return c;
}

int main(int argc, char const *argv[]) {
int *ps = alloca(sizeof(int));
int *c = f();
int a = *ps; //NOWARN
int b = *c; //WARN
return 0;
}
Loading

0 comments on commit 55594a7

Please sign in to comment.