Commit 41b84a41 by Enkelmann Committed by Thomas Barabosch

Cwe476 (#11)

* improved CWE476-check with dataflow analysis
parent 44cb572a
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
- Added automated test suite (run with make test) (PR #7) - Added automated test suite (run with make test) (PR #7)
- Improved cross compiling for acceptance test cases by using dockcross (PR #8) - Improved cross compiling for acceptance test cases by using dockcross (PR #8)
- Added BAP recipe for standard cwe_checker run (PR #9) - Added BAP recipe for standard cwe_checker run (PR #9)
- Improved check for CWE-476 (NULL Pointer Dereference) using data flow analysis (PR #11)
0.1 (2018-10-08) 0.1 (2018-10-08)
===== =====
......
...@@ -27,7 +27,7 @@ let check_multiplication_before_symbol proj prog sub blk jmp tid_map symbols = ...@@ -27,7 +27,7 @@ let check_multiplication_before_symbol proj prog sub blk jmp tid_map symbols =
(Address_translation.translate_tid_to_assembler_address_string (Term.tid blk) tid_map) (Address_translation.translate_tid_to_assembler_address_string (Term.tid blk) tid_map)
(Symbol_utils.get_symbol_name_from_jmp jmp symbols)) (Symbol_utils.get_symbol_name_from_jmp jmp symbols))
let check_cwe prog proj tid_map symbol_names = let check_cwe prog proj tid_map symbol_names _ =
match symbol_names with match symbol_names with
| hd::[] -> | hd::[] ->
let symbols = Symbol_utils.build_symbols hd prog in let symbols = Symbol_utils.build_symbols hd prog in
......
...@@ -5,4 +5,4 @@ https://cwe.mitre.org/data/definitions/190.html ...@@ -5,4 +5,4 @@ https://cwe.mitre.org/data/definitions/190.html
val name : string val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
...@@ -30,7 +30,7 @@ let read_lines in_chan = ...@@ -30,7 +30,7 @@ let read_lines in_chan =
List.rev !lines List.rev !lines
(* TODO: check if program contains strings like "DEBUG"*) (* TODO: check if program contains strings like "DEBUG"*)
let check_cwe _ project _ _ = let check_cwe _ project _ _ _ =
match Project.get project filename with match Project.get project filename with
| Some fname -> begin | Some fname -> begin
let cmd = Format.sprintf "readelf --debug-dump=decodedline %s | grep CU" fname in let cmd = Format.sprintf "readelf --debug-dump=decodedline %s | grep CU" fname in
...@@ -42,4 +42,3 @@ let check_cwe _ project _ _ = ...@@ -42,4 +42,3 @@ let check_cwe _ project _ _ =
Log_utils.error "[%s] {%s} %s %s %s" name version (Unix.error_message e) fm argm Log_utils.error "[%s] {%s} %s %s %s" name version (Unix.error_message e) fm argm
end end
| _ -> failwith "[CWE215] symbol_names not as expected" | _ -> failwith "[CWE215] symbol_names not as expected"
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
val name : string val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
...@@ -81,10 +81,9 @@ let check_subfunction prog tid_map sub pathes = ...@@ -81,10 +81,9 @@ let check_subfunction prog tid_map sub pathes =
(Term.name sub) (Term.name sub)
end end
let check_cwe prog proj tid_map pathes = let check_cwe prog proj tid_map pathes _ =
let chroot_symbol = find_symbol prog "chroot" in let chroot_symbol = find_symbol prog "chroot" in
match chroot_symbol with match chroot_symbol with
| Some _ -> | Some _ ->
Seq.iter (Term.enum sub_t prog) ~f:(fun sub -> check_subfunction prog tid_map sub pathes) Seq.iter (Term.enum sub_t prog) ~f:(fun sub -> check_subfunction prog tid_map sub pathes)
| _ -> () | _ -> ()
...@@ -5,4 +5,4 @@ See https://cwe.mitre.org/data/definitions/243.html for detailed description. *) ...@@ -5,4 +5,4 @@ See https://cwe.mitre.org/data/definitions/243.html for detailed description. *)
val name : string val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
...@@ -59,7 +59,7 @@ let rec find_uncaught_exceptions subfunction already_checked_functions program ~ ...@@ -59,7 +59,7 @@ let rec find_uncaught_exceptions subfunction already_checked_functions program ~
(* Search for uncatched exceptions for each entry point into the binary. (* Search for uncatched exceptions for each entry point into the binary.
TODO: Exceptions, that are catched when starting from one entry point, but not from another, are masked this TODO: Exceptions, that are catched when starting from one entry point, but not from another, are masked this
way. We should check whether this produces a lot of false negatives. *) way. We should check whether this produces a lot of false negatives. *)
let check_cwe program project tid_map symbol_pairs = let check_cwe program project tid_map symbol_pairs _ =
let entry_points = Symbol_utils.get_program_entry_points program in let entry_points = Symbol_utils.get_program_entry_points program in
let _ = Seq.fold entry_points ~init:[] ~f:(fun already_checked_functions sub -> find_uncaught_exceptions ~tid_map:tid_map sub already_checked_functions program) in let _ = Seq.fold entry_points ~init:[] ~f:(fun already_checked_functions sub -> find_uncaught_exceptions ~tid_map:tid_map sub already_checked_functions program) in
() ()
...@@ -8,4 +8,4 @@ can actually catch the thrown exceptions, thus we generate some false negatives. ...@@ -8,4 +8,4 @@ can actually catch the thrown exceptions, thus we generate some false negatives.
val name : string val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
...@@ -7,7 +7,7 @@ open Symbol_utils ...@@ -7,7 +7,7 @@ open Symbol_utils
let name = "CWE332" let name = "CWE332"
let version = "0.1" let version = "0.1"
let check_cwe program proj tid_map symbol_pairs = let check_cwe program proj tid_map symbol_pairs _ =
match Option.both (find_symbol program "srand") (find_symbol program "rand") with match Option.both (find_symbol program "srand") (find_symbol program "rand") with
| None -> begin | None -> begin
match (find_symbol program "rand") with match (find_symbol program "rand") with
...@@ -15,4 +15,3 @@ let check_cwe program proj tid_map symbol_pairs = ...@@ -15,4 +15,3 @@ let check_cwe program proj tid_map symbol_pairs =
| Some _ -> Log_utils.warn "[%s] {%s} (Insufficient Entropy in PRNG) program uses rand without calling srand before" name version | Some _ -> Log_utils.warn "[%s] {%s} (Insufficient Entropy in PRNG) program uses rand without calling srand before" name version
end end
| Some (srand_tid, rand_tid) -> () | Some (srand_tid, rand_tid) -> ()
...@@ -6,4 +6,4 @@ See https://cwe.mitre.org/data/definitions/332.html for detailed description. *) ...@@ -6,4 +6,4 @@ See https://cwe.mitre.org/data/definitions/332.html for detailed description. *)
val name : string val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> 'a -> 'b -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> 'a -> 'b -> string list list -> 'c -> unit
...@@ -57,6 +57,6 @@ let handle_sub sub program tid_map symbols source sink = ...@@ -57,6 +57,6 @@ let handle_sub sub program tid_map symbols source sink =
else else
() ()
let check_cwe program proj tid_map symbol_pairs = let check_cwe program proj tid_map symbol_pairs _ =
let symbols = Symbol_utils.build_symbols ["access"; "open";] in let symbols = Symbol_utils.build_symbols ["access"; "open";] in
Seq.iter (Term.enum sub_t program) ~f:(fun s -> handle_sub s program tid_map symbols "access" "open") Seq.iter (Term.enum sub_t program) ~f:(fun s -> handle_sub s program tid_map symbols "access" "open")
...@@ -5,4 +5,4 @@ https://en.wikipedia.org/wiki/Time_of_check_to_time_of_use ...@@ -5,4 +5,4 @@ https://en.wikipedia.org/wiki/Time_of_check_to_time_of_use
val name : string val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
...@@ -21,7 +21,7 @@ let handle_sub sub program tid_map symbols = ...@@ -21,7 +21,7 @@ let handle_sub sub program tid_map symbols =
end end
else () else ()
let check_cwe program proj tid_map symbols = let check_cwe program proj tid_map symbols _ =
match symbols with match symbols with
| hd::[] -> | hd::[] ->
Seq.iter (Term.enum sub_t program) ~f:(fun s -> handle_sub s program tid_map hd) Seq.iter (Term.enum sub_t program) ~f:(fun s -> handle_sub s program tid_map hd)
......
...@@ -11,4 +11,4 @@ drops privileges on startup. (Debian uses a modified bash which does not do thi ...@@ -11,4 +11,4 @@ drops privileges on startup. (Debian uses a modified bash which does not do thi
val name : string val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
...@@ -92,5 +92,5 @@ let check_subfunction prog proj tid_map sub = ...@@ -92,5 +92,5 @@ let check_subfunction prog proj tid_map sub =
end end
end) end)
let check_cwe prog proj tid_map symbol_names = let check_cwe prog proj tid_map symbol_names _ =
Seq.iter (Term.enum sub_t prog) ~f:(fun sub -> check_subfunction prog proj tid_map sub) Seq.iter (Term.enum sub_t prog) ~f:(fun sub -> check_subfunction prog proj tid_map sub)
...@@ -4,4 +4,4 @@ See https://cwe.mitre.org/data/definitions/457.html for detailed description. *) ...@@ -4,4 +4,4 @@ See https://cwe.mitre.org/data/definitions/457.html for detailed description. *)
val name: string val name: string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
...@@ -24,7 +24,7 @@ let check_input_is_pointer_size proj prog sub blk jmp tid_map symbols = ...@@ -24,7 +24,7 @@ let check_input_is_pointer_size proj prog sub blk jmp tid_map symbols =
| _ -> ()) | _ -> ())
let check_cwe prog proj tid_map symbol_names = let check_cwe prog proj tid_map symbol_names _ =
match symbol_names with match symbol_names with
| hd::[] -> | hd::[] ->
let symbols = Symbol_utils.build_symbols hd prog in let symbols = Symbol_utils.build_symbols hd prog in
......
...@@ -9,4 +9,4 @@ See https://cwe.mitre.org/data/definitions/467.html for detailed description. *) ...@@ -9,4 +9,4 @@ See https://cwe.mitre.org/data/definitions/467.html for detailed description. *)
val name : string val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
open Core_kernel.Std
open Bap.Std open Bap.Std
open Symbol_utils open Core_kernel.Std
let name = "CWE476" let name = "CWE476"
let version = "0.1" let version = "0.2"
let find_blk_tid_in_sub blk_tid sub = (* Access type denotes whether a variable var gets accessed or the memory stored at
Seq.find (Term.enum blk_t sub) ~f:(fun b -> (Term.tid b) = blk_tid) address var gets accessed *)
type access_type = | Access of Bil.var | MemAccess of Bil.var | NoAccess
let get_jmps blk = Seq.filter (Blk.elts blk) ~f:(fun elt -> match elt with
| `Phi phi -> false (* The union of two accesses is the higher access with MemAcces > Access > NoAccess *)
| `Def def -> false let union_access access1 access2 : access_type =
| `Jmp jmp -> true ) match (access1, access2) with
|> Seq.map ~f:(fun j -> match j with | (MemAccess(_), _) -> access1
| `Jmp jmp -> jmp | (_, MemAccess(_))
| _ -> assert(false)) | (_, Access(_)) -> access2
| _ -> access1
let jmp_cond_checks_zf jmp =
let e = Jmp.cond jmp in
(Exp.to_string e) = "~ZF" || (Exp.to_string e) = "ZF" (* union of three accesses for convenience *)
let union_access_triple access1 access2 access3 =
(* Check if next block contains when zf = 0 goto, if not then there is a chance that this yields a null pointer deref *) union_access access1 access2
let check_null_pointer proj prog sub blk jmp tid_map symbols = |> union_access access3
Seq.iter (Graphs.Tid.Node.succs (Term.tid blk) (Sub.to_graph sub)) ~f:(
fun next_blk -> match find_blk_tid_in_sub next_blk sub with (* the state contains a list of pairs of register names containing an unchecked
| Some b -> begin return value and the term identifiers of the block where the unchecked
(* ToDo: Check if there is a definition of ZF = 0 *) return value was generated. *)
let jmps = get_jmps b in module State = struct
match Seq.find jmps ~f:jmp_cond_checks_zf with type t = (Var.t * Tid.t) list
| Some _ -> ()
| None -> Log_utils.warn "[%s] {%s} (NULL Pointer Dereference) There is no check if the return value is NULL at %s (%s)." (** adds var as a tainted register (with the taint source given by tid) *)
let add state var tid =
let state = List.Assoc.remove state ~equal:Var.(=) var in
List.Assoc.add state ~equal:Var.(=) var tid
(** returns Some(tid) if var is a tainted register, None otherwise *)
let find state var =
List.Assoc.find state ~equal:Var.(=) var
(** returns the tid associated with a tainted register *)
let find_exn state var =
Option.value_exn (find state var)
(** only remove the register var from the list of tainted registers *)
let remove_var state var =
List.Assoc.remove state ~equal:Var.(=) var
(** filters out all registers from the state with the same tid *)
let remove_tid state var =
let tid = find_exn state var in
List.filter state ~f:(fun (_, state_elem_tid) -> not (state_elem_tid = tid))
(** two states are equal if they contain the same set of tainted registers*)
let equal state1 state2 =
(List.length state1) = (List.length state2) &&
not (List.exists state1 ~f:(fun (var, tid) -> Option.is_none (find state2 var) ))
(** The union of two states is the union of the tainted registers*)
let union state1 state2 =
List.fold state2 ~init:state1 ~f:(fun state (var, tid) ->
if Option.is_some (find state var) then
state
else
(var, tid) :: state
)
(** remove virtual registers from the state (useful at the end of a block) *)
let remove_virtual_registers state =
List.filter state ~f:(fun (var, tid) -> Var.is_physical var)
end
(* check whether an expression contains an unchecked value. *)
let rec contains_unchecked exp state : access_type =
match exp with
| Bil.Load(mem, addr, _, _)->
begin
let acc = contains_unchecked addr state in
match acc with
| MemAccess(_) -> acc
| Access(var) -> MemAccess(var)
| NoAccess -> NoAccess
end
| Bil.Store(mem, addr, val_expression, _,_) ->
begin
let acc = union_access (contains_unchecked addr state) (contains_unchecked val_expression state) in
match acc with
| MemAccess(_) -> acc
| Access(var) -> MemAccess(var)
| NoAccess -> NoAccess
end
| Bil.BinOp(_, exp1, exp2) -> union_access (contains_unchecked exp1 state) (contains_unchecked exp2 state)
| Bil.UnOp(_, exp) -> contains_unchecked exp state
| Bil.Var(var) ->
begin
match State.find state var with
| Some(_) -> Access(var)
| None -> NoAccess
end
| Bil.Int(_) -> NoAccess
| Bil.Cast(_, _, exp) -> contains_unchecked exp state
| Bil.Let(var, exp1, exp2) ->
union_access_triple (contains_unchecked exp1 state) (contains_unchecked exp2 state) (contains_unchecked (Bil.var var) state)
| Bil.Unknown(_) -> NoAccess
| Bil.Ite(if_, then_, else_) ->
union_access_triple (contains_unchecked if_ state) (contains_unchecked then_ state) (contains_unchecked else_ state)
| Bil.Extract(_,_, exp) -> contains_unchecked exp state
| Bil.Concat(exp1, exp2) -> union_access (contains_unchecked exp1 state) (contains_unchecked exp2 state)
(* If an formerly unchecked return value was checked then remove all registers pointing
to the source of this return value from state. *)
let checks_value exp state : State.t =
match exp with
| Bil.Ite(if_, then_, else_) -> begin
match contains_unchecked if_ state with
| Access(var) ->
(* We filter out all registers with the same generating tid, since we have checked
the return value of this source *)
State.remove_tid state var
| MemAccess(_) (* This is a memory access before checking the return value, so do nothing here. *)
| NoAccess -> state
end
| _ -> state
let append_to_hits (cwe_hits:Tid.t list ref) (tid:Tid.t) =
match List.find cwe_hits.contents ~f:(fun elem -> elem = tid) with
| Some(_) -> ()
| None -> (cwe_hits := (tid :: cwe_hits.contents))
(** flags any access (not just memory access) from an unchecked source as a cwe_hit. *)
let flag_any_access exp state ~cwe_hits =
match contains_unchecked exp state with
| MemAccess(var) | Access(var) ->
let tid = State.find_exn state var in
append_to_hits cwe_hits tid;
State.remove_tid state var
| NoAccess -> state
(** flag all unchecked registers as cwe_hits, return empty state *)
let flag_all_unchecked_registers state ~cwe_hits =
let () = List.iter state ~f:(fun (var, tid) ->
append_to_hits cwe_hits tid) in
[]
(** Updates the state depending on the def. If memory is accessed using an unchecked return value,
then the access is added to the list of cwe_hits. *)
let update_state_def def state ~cwe_hits =
let (lhs, rhs) = (Def.lhs def, Def.rhs def) in
let state = checks_value rhs state in
match contains_unchecked rhs state with
| MemAccess(var) -> begin (* we found a case of unchecked return value *)
let tid = State.find_exn state var in
append_to_hits cwe_hits tid;
State.remove_tid state var
end
| Access(var) -> (* taint the lhs as an unchecked return value *)
let tid = State.find_exn state var in
State.add state lhs tid
| NoAccess -> (* no access to an unchecked return value in rhs. Since lhs is overwritten, it cannot be an unchecked return value anymore. *)
State.remove_var state lhs
(** Taint the return registers of a function as unchecked return values. *)
let taint_return_registers func_tid state ~program ~block =
let func = Term.find_exn sub_t program func_tid in
let arguments = Term.enum arg_t func in
(* Every return register is tainted as unchecked return value. *)
Seq.fold arguments ~init:state ~f:(fun state arg ->
match Bap.Std.Arg.intent arg with
| None | Some(In) -> state
| Some(Out) | Some(Both) ->
let variable = match Bap.Std.Arg.rhs arg with
| Bil.Var(var) -> var
| _ -> failwith "[CWE476] Return register wasn't a register." in
State.add state variable (Term.tid block)
)
(** Updates the state depending on the jump. On a jump to a function from the function list
taint all return registers as unchecked return values. *)
let update_state_jmp jmp state ~cwe_hits ~function_names ~program ~block ~strict_call_policy =
(* first check the guard condition for unchecked access. Any normal access clears the access from being unchecked *)
let condition_exp = Jmp.cond jmp in
let state = begin
match contains_unchecked condition_exp state with
| Access(var) ->
State.remove_tid state var
| MemAccess(var) -> (* a memory access using an unchecked value is still an error *)
let tid = State.find_exn state var in
let () = append_to_hits cwe_hits tid in
State.remove_tid state var
| NoAccess -> state
end in
match Jmp.kind jmp with
| Goto(Indirect(exp)) -> flag_any_access exp state cwe_hits
| Goto(Direct(_)) -> state
| Ret(_) -> if strict_call_policy then
flag_all_unchecked_registers state cwe_hits
else
state
| Int(_, _) -> flag_all_unchecked_registers state cwe_hits
| Call(call) ->
let state = match Call.return call with
| Some(Indirect(exp)) -> flag_any_access exp state cwe_hits
| _ -> state in
let state = match Call.target call with
| Indirect(exp) -> flag_any_access exp state cwe_hits
| _ -> state in
let state = match strict_call_policy with
| true -> (* all unchecked registers get flagged as hits *)
flag_all_unchecked_registers state cwe_hits
| false -> (* we assume that the callee will check all remaining unchecked values *)
[] in
match Call.target call with
| Indirect(_) -> state (* already handled above *)
| Direct(tid) ->
if List.exists function_names ~f:(fun elem -> String.(=) elem (Tid.name tid)) then
taint_return_registers tid state program block
else
state
(** updates a block analysis.
The strict call policy decides the behaviour on call and return instructions:
strict: all unchecked values get flagged as cwe-hits
non-strict: the state gets cleared, it is assumed that the target of the call/return
instruction checks all remaining unchecked values. *)
let update_block_analysis block register_state ~cwe_hits ~function_names ~program ~strict_call_policy =
let elements = Blk.elts block in
let register_state = Seq.fold elements ~init:register_state ~f:(fun state element ->
match element with
| `Def def -> update_state_def def state ~cwe_hits
| `Phi phi -> state (* We ignore phi terms for this analysis. *)
| `Jmp jmp -> update_state_jmp jmp state ~cwe_hits ~function_names ~program ~block ~strict_call_policy
) in
State.remove_virtual_registers register_state (* virtual registers should not be accessed outside of the block where they are defined. *)
let print_hit tid ~sub ~function_names ~tid_map =
let block = Option.value_exn (Term.find blk_t sub tid) in
let jmps = Term.enum jmp_t block in
let _ = Seq.find_exn jmps ~f:(fun jmp ->
match Jmp.kind jmp with
| Call(call) -> begin
match Call.target call with
| Direct(call_tid) -> Option.is_some (List.find function_names ~f:(fun name ->
if name = (Tid.name call_tid) then begin
Log_utils.warn "[%s] {%s} (NULL Pointer Dereference) There is no check if the return value is NULL at %s (%s)."
name name
version version
(Address_translation.translate_tid_to_assembler_address_string (Term.tid blk) tid_map) (Address_translation.translate_tid_to_assembler_address_string tid tid_map)
(Symbol_utils.get_symbol_name_from_jmp jmp symbols) name;
true
end else
false
))
| _ -> false
end end
| _ -> assert(false)) | _ -> false
) in ()
let check_cwe prog proj tid_map symbol_names =
match symbol_names with let check_cwe prog proj tid_map symbol_names parameters =
| hd::[] -> let symbols = match symbol_names with
let symbols = Symbol_utils.build_symbols hd prog in | hd :: _ -> hd
let calls = call_finder#run prog [] in | _ -> failwith "[CWE476] symbol_names not as expected" in
let relevant_calls = filter_calls_to_symbols calls symbols in let (strict_call_policy_string, max_steps_string) = match parameters with
check_calls relevant_calls prog proj tid_map symbols check_null_pointer | par1 :: par2 :: _ -> (par1, par2)
| _ -> failwith "[CWE476] symbol_names not as expected" | _ -> failwith "[CWE476] parameters not as expected" in
let strict_call_policy = match String.split strict_call_policy_string ~on:'=' with
| "strict_call_policy" :: policy :: [] -> bool_of_string policy
| _ -> failwith "[CWE476] parameters not as expected" in
let max_steps = match String.split max_steps_string ~on:'=' with
| "max_steps" :: num :: [] -> int_of_string num
| _ -> failwith "[CWE476] parameters not as expected" in
let function_names = List.map symbols ~f:(fun symb -> "@" ^ symb) in
let subfunctions = Term.enum sub_t prog in
Seq.iter subfunctions ~f:(fun subfn ->
let cfg = Sub.to_cfg subfn in
let cwe_hits = ref [] in
let empty = Map.empty Graphs.Ir.Node.comparator in
let init = Graphlib.Std.Solution.create empty [] in
let equal = State.equal in
let merge = State.union in
let f = (fun node state ->
let block = Graphs.Ir.Node.label node in
update_block_analysis block state ~cwe_hits ~function_names ~program:prog ~strict_call_policy
) in
let _ = Graphlib.Std.Graphlib.fixpoint (module Graphs.Ir) cfg ~steps:max_steps ~rev:false ~init:init ~equal:equal ~merge:merge ~f:f in
List.iter (!cwe_hits) ~f:(fun hit -> print_hit hit ~sub:subfn ~function_names ~tid_map)
)
(** This module implements a check for CWE-476 (NULL Pointer Dereference). (** This module implements a check for CWE-476 (NULL Pointer Dereference).
It checks if the result of a function that may return a NULL value is checked immediately It checks if the result of a function that may return a NULL value is checked
for NULL. The symbols are configurable in config.json. for NULL before any memory gets accessed using the return values. The symbols
See https://cwe.mitre.org/data/definitions/476.html for detailed description. *) are configurable in config.json. See https://cwe.mitre.org/data/definitions/476.html
val name: string for detailed description.
Parameters:
- strict_call_policy={true, false}: Determines behaviour on call and return instructions.
If false, we assume that the callee, resp. the caller on a return instruction,
checks all unchecked values still contained in the registers. If true, every
unchecked value on a call or return instruction gets reported.
- max_steps=<num>: Max number of steps for the dataflow fixpoint algorithm.
Notes: The check relies on Bap-generated stubs to identify return registers of the
checked functions. Therefore it only works for functions for which Bap generates
these stubs. *)
val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
...@@ -35,7 +35,7 @@ let resolve_symbols prog symbols = ...@@ -35,7 +35,7 @@ let resolve_symbols prog symbols =
Seq.filter ~f:(fun s -> List.exists ~f:(fun x -> x = Sub.name s) symbols) Seq.filter ~f:(fun s -> List.exists ~f:(fun x -> x = Sub.name s) symbols)
let check_cwe prog proj tid_map symbol_names = let check_cwe prog proj tid_map symbol_names _ =
match symbol_names with match symbol_names with
| hd::[] -> | hd::[] ->
let subfunctions = Term.enum sub_t prog in let subfunctions = Term.enum sub_t prog in
...@@ -43,4 +43,3 @@ let check_cwe prog proj tid_map symbol_names = ...@@ -43,4 +43,3 @@ let check_cwe prog proj tid_map symbol_names =
get_calls_to_symbols cg subfunctions (resolve_symbols prog hd) get_calls_to_symbols cg subfunctions (resolve_symbols prog hd)
|> print_calls ~tid_map:tid_map |> print_calls ~tid_map:tid_map
| _ -> failwith "[CWE676] symbol_names not as expected" | _ -> failwith "[CWE676] symbol_names not as expected"
...@@ -5,4 +5,4 @@ See https://cwe.mitre.org/data/definitions/676.html for detailed description. *) ...@@ -5,4 +5,4 @@ See https://cwe.mitre.org/data/definitions/676.html for detailed description. *)
val name : string val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
...@@ -15,5 +15,5 @@ let handle_sub sub program tid_map symbols = ...@@ -15,5 +15,5 @@ let handle_sub sub program tid_map symbols =
else else
() ()
let check_cwe program proj tid_map symbols = let check_cwe program proj tid_map symbols _ =
Seq.iter (Term.enum sub_t program) ~f:(fun s -> handle_sub s program tid_map symbols) Seq.iter (Term.enum sub_t program) ~f:(fun s -> handle_sub s program tid_map symbols)
...@@ -4,4 +4,4 @@ https://cwe.mitre.org/data/definitions/782.html *) ...@@ -4,4 +4,4 @@ https://cwe.mitre.org/data/definitions/782.html *)
val name : string val name : string
val version : string val version : string
val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit val check_cwe : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit
...@@ -95,6 +95,10 @@ ...@@ -95,6 +95,10 @@
"CWE476": { "CWE476": {
"_comment": "any function that possibly returns a NULL value.", "_comment": "any function that possibly returns a NULL value.",
"_comment1": "included functions of the following libs: stdlib.h, locale.h, stdio.h, cstring.h, wchar.h", "_comment1": "included functions of the following libs: stdlib.h, locale.h, stdio.h, cstring.h, wchar.h",
"parameters": [
"strict_call_policy=true",
"max_steps=100"
],
"symbols": [ "symbols": [
"malloc", "malloc",
"calloc", "calloc",
......
...@@ -7,24 +7,25 @@ open Yojson.Basic.Util ...@@ -7,24 +7,25 @@ open Yojson.Basic.Util
include Self() include Self()
type cwe_module = { type cwe_module = {
cwe_func : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> unit; cwe_func : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit;
name : string; name : string;
version : string; version : string;
requires_pairs : bool; requires_pairs : bool;
has_parameters : bool;
} }
let known_modules = [{cwe_func = Cwe_190.check_cwe; name = Cwe_190.name; version = Cwe_190.version; requires_pairs = false}; let known_modules = [{cwe_func = Cwe_190.check_cwe; name = Cwe_190.name; version = Cwe_190.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_215.check_cwe; name = Cwe_215.name; version = Cwe_215.version; requires_pairs = false}; {cwe_func = Cwe_215.check_cwe; name = Cwe_215.name; version = Cwe_215.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_243.check_cwe; name = Cwe_243.name; version = Cwe_243.version; requires_pairs = true}; {cwe_func = Cwe_243.check_cwe; name = Cwe_243.name; version = Cwe_243.version; requires_pairs = true; has_parameters = false};
{cwe_func = Cwe_248.check_cwe; name = Cwe_248.name; version = Cwe_248.version; requires_pairs = false}; {cwe_func = Cwe_248.check_cwe; name = Cwe_248.name; version = Cwe_248.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_332.check_cwe; name = Cwe_332.name; version = Cwe_332.version; requires_pairs = true}; {cwe_func = Cwe_332.check_cwe; name = Cwe_332.name; version = Cwe_332.version; requires_pairs = true; has_parameters = false};
{cwe_func = Cwe_367.check_cwe; name = Cwe_367.name; version = Cwe_367.version; requires_pairs = true}; {cwe_func = Cwe_367.check_cwe; name = Cwe_367.name; version = Cwe_367.version; requires_pairs = true; has_parameters = false};
{cwe_func = Cwe_426.check_cwe; name = Cwe_426.name; version = Cwe_426.version; requires_pairs = false}; {cwe_func = Cwe_426.check_cwe; name = Cwe_426.name; version = Cwe_426.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_457.check_cwe; name = Cwe_457.name; version = Cwe_457.version; requires_pairs = false}; {cwe_func = Cwe_457.check_cwe; name = Cwe_457.name; version = Cwe_457.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_467.check_cwe; name = Cwe_467.name; version = Cwe_467.version; requires_pairs = false}; {cwe_func = Cwe_467.check_cwe; name = Cwe_467.name; version = Cwe_467.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_476.check_cwe; name = Cwe_476.name; version = Cwe_476.version; requires_pairs = false}; {cwe_func = Cwe_476.check_cwe; name = Cwe_476.name; version = Cwe_476.version; requires_pairs = false; has_parameters = true};
{cwe_func = Cwe_676.check_cwe; name = Cwe_676.name; version = Cwe_676.version; requires_pairs = false}; {cwe_func = Cwe_676.check_cwe; name = Cwe_676.name; version = Cwe_676.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_782.check_cwe; name = Cwe_782.name; version = Cwe_782.version; requires_pairs = false}] {cwe_func = Cwe_782.check_cwe; name = Cwe_782.name; version = Cwe_782.version; requires_pairs = false; has_parameters = false}]
let build_version_sexp () = let build_version_sexp () =
List.map known_modules ~f:(fun cwe -> Format.sprintf "(\"%s\" \"%s\")" cwe.name cwe.version) List.map known_modules ~f:(fun cwe -> Format.sprintf "(\"%s\" \"%s\")" cwe.name cwe.version)
...@@ -36,15 +37,18 @@ let print_module_versions () = ...@@ -36,15 +37,18 @@ let print_module_versions () =
(build_version_sexp ()) (build_version_sexp ())
let execute_cwe_module cwe json program project tid_address_map = let execute_cwe_module cwe json program project tid_address_map =
let parameters = match cwe.has_parameters with
| false -> []
| true -> Json_utils.get_parameter_list_from_json json cwe.name in
if cwe.requires_pairs = true then if cwe.requires_pairs = true then
begin begin
let symbol_pairs = Json_utils.get_symbol_lists_from_json json cwe.name in let symbol_pairs = Json_utils.get_symbol_lists_from_json json cwe.name in
cwe.cwe_func program project tid_address_map symbol_pairs cwe.cwe_func program project tid_address_map symbol_pairs parameters
end end
else else
begin begin
let symbols = Json_utils.get_symbols_from_json json cwe.name in let symbols = Json_utils.get_symbols_from_json json cwe.name in
cwe.cwe_func program project tid_address_map [symbols] cwe.cwe_func program project tid_address_map [symbols] parameters
end end
let partial_run project config modules = let partial_run project config modules =
......
...@@ -23,3 +23,10 @@ let get_symbol_lists_from_json json cwe = ...@@ -23,3 +23,10 @@ let get_symbol_lists_from_json json cwe =
|> filter_member "pairs" |> filter_member "pairs"
|> flatten |> flatten
|> List.map ~f:(fun l -> List.map (to_list l) ~f:to_string) |> List.map ~f:(fun l -> List.map (to_list l) ~f:to_string)
let get_parameter_list_from_json json cwe =
[json]
|> filter_member cwe
|> filter_member "parameters"
|> flatten
|> List.map ~f:to_string
...@@ -2,3 +2,4 @@ ...@@ -2,3 +2,4 @@
val get_symbol_lists_from_json : Yojson.Basic.json -> string -> string list list val get_symbol_lists_from_json : Yojson.Basic.json -> string -> string list list
val get_symbols_from_json : Yojson.Basic.json -> string -> string list val get_symbols_from_json : Yojson.Basic.json -> string -> string list
val get_parameter_list_from_json : Yojson.Basic.json -> string -> string list
#include <stdlib.h> #include <stdlib.h>
void func1(){ void func1(){
void* data = malloc(20); void* data = malloc(20000);
if (data == NULL){ if (data == NULL){
exit(42); exit(42);
} }
...@@ -9,7 +9,8 @@ void func1(){ ...@@ -9,7 +9,8 @@ void func1(){
} }
void func2(){ void func2(){
void* data = malloc(20); int* data = malloc(200000);
printf("%i", data[0]);
free(data); free(data);
} }
......
...@@ -10,114 +10,117 @@ CPP_ARM=arm-linux-gnueabi-g++-5 ...@@ -10,114 +10,117 @@ CPP_ARM=arm-linux-gnueabi-g++-5
CPP_MIPS=mips-linux-gnu-g++-5 CPP_MIPS=mips-linux-gnu-g++-5
CPP_PPC=powerpc-linux-gnu-g++-5 CPP_PPC=powerpc-linux-gnu-g++-5
CFLAGS_X64=-O0 -g -fno-stack-protector -std=c11 CFLAGS_X64=-g -fno-stack-protector -std=c11
CFLAGS_X86=-O0 -g -m32 -fno-stack-protector -std=c11 CFLAGS_X86=-g -m32 -fno-stack-protector -std=c11
CFLAGS_ARM=-O0 -g -fno-stack-protector -std=c11 CFLAGS_ARM=-g -fno-stack-protector -std=c11
CFLAGS_MIPS=-O0 -g -fno-stack-protector -std=c11 CFLAGS_MIPS=-g -fno-stack-protector -std=c11
CFLAGS_PPC=-O0 -g -fno-stack-protector -std=c11 CFLAGS_PPC=-g -fno-stack-protector -std=c11
CPPFLAGS_X64=-O0 -g -fno-stack-protector CPPFLAGS_X64=-g -fno-stack-protector
CPPFLAGS_X86=-O0 -g -m32 -fno-stack-protector CPPFLAGS_X86=-g -m32 -fno-stack-protector
CPPFLAGS_ARM=-O0 -g -fno-stack-protector CPPFLAGS_ARM=-g -fno-stack-protector
CPPFLAGS_MIPS=-O0 -g -fno-stack-protector CPPFLAGS_MIPS=-g -fno-stack-protector
CPPFLAGS_PPC=-O0 -g -fno-stack-protector CPPFLAGS_PPC=-g -fno-stack-protector
OPTIMIZE=-O3
NO_OPTIMIZE=-O0
define compile_x64 define compile_x64
@echo "Compiling x64 target:" $(1) @echo "Compiling x64 target:" $(1)
$(CC_x64) $(CFLAGS_X64) -o build/$(1)_x64.out $(1).c $(CC_x64) $(CFLAGS_X64) $(2) -o build/$(1)_x64.out $(1).c
execstack -s build/$(1)_x64.out execstack -s build/$(1)_x64.out
endef endef
define compile_x64_cpp define compile_x64_cpp
@echo "Compiling x64 target:" $(1) @echo "Compiling x64 target:" $(1)
$(CPP_x64) $(CPPFLAGS_X64) -o build/$(1)_x64.out $(1).cpp $(CPP_x64) $(CPPFLAGS_X64) $(2) -o build/$(1)_x64.out $(1).cpp
execstack -s build/$(1)_x64.out execstack -s build/$(1)_x64.out
endef endef
define compile_x86 define compile_x86
@echo "Compiling x86 target:" $(1) @echo "Compiling x86 target:" $(1)
$(CC_X86) $(CFLAGS_X86) -o build/$(1)_x86.out $(1).c $(CC_X86) $(CFLAGS_X86) $(2) -o build/$(1)_x86.out $(1).c
execstack -s build/$(1)_x86.out execstack -s build/$(1)_x86.out
endef endef
define compile_x86_cpp define compile_x86_cpp
@echo "Compiling x86 target:" $(1) @echo "Compiling x86 target:" $(1)
$(CPP_X86) $(CPPFLAGS_X86) -o build/$(1)_x86.out $(1).cpp $(CPP_X86) $(CPPFLAGS_X86) $(2) -o build/$(1)_x86.out $(1).cpp
execstack -s build/$(1)_x86.out execstack -s build/$(1)_x86.out
endef endef
define compile_mips define compile_mips
@echo "Compiling mips target:" $(1) @echo "Compiling mips target:" $(1)
$(CC_MIPS) $(CFLAGS_MIPS) -o build/$(1)_mips.out $(1).c $(CC_MIPS) $(CFLAGS_MIPS) $(2) -o build/$(1)_mips.out $(1).c
execstack -s build/$(1)_mips.out execstack -s build/$(1)_mips.out
endef endef
define compile_mips_cpp define compile_mips_cpp
@echo "Compiling mips target:" $(1) @echo "Compiling mips target:" $(1)
$(CPP_MIPS) $(CPPFLAGS_MIPS) -o build/$(1)_mips.out $(1).cpp $(CPP_MIPS) $(CPPFLAGS_MIPS) $(2) -o build/$(1)_mips.out $(1).cpp
execstack -s build/$(1)_mips.out execstack -s build/$(1)_mips.out
endef endef
define compile_arm define compile_arm
@echo "Compiling arm target:" $(1) @echo "Compiling arm target:" $(1)
$(CC_ARM) $(CFLAGS_ARM) -o build/$(1)_arm.out $(1).c $(CC_ARM) $(CFLAGS_ARM) $(2) -o build/$(1)_arm.out $(1).c
execstack -s build/$(1)_arm.out execstack -s build/$(1)_arm.out
endef endef
define compile_arm_cpp define compile_arm_cpp
@echo "Compiling arm target:" $(1) @echo "Compiling arm target:" $(1)
$(CPP_ARM) $(CPPFLAGS_ARM) -o build/$(1)_arm.out $(1).cpp $(CPP_ARM) $(CPPFLAGS_ARM) $(2) -o build/$(1)_arm.out $(1).cpp
execstack -s build/$(1)_arm.out execstack -s build/$(1)_arm.out
endef endef
define compile_ppc define compile_ppc
@echo "Compiling ppc target:" $(1) @echo "Compiling ppc target:" $(1)
$(CC_PPC) $(CFLAGS_PPC) -o build/$(1)_ppc.out $(1).c $(CC_PPC) $(CFLAGS_PPC) $(2) -o build/$(1)_ppc.out $(1).c
execstack -s build/$(1)_ppc.out execstack -s build/$(1)_ppc.out
endef endef
define compile_ppc_cpp define compile_ppc_cpp
@echo "Compiling ppc target:" $(1) @echo "Compiling ppc target:" $(1)
$(CPP_PPC) $(CPPFLAGS_PPC) -o build/$(1)_ppc.out $(1).cpp $(CPP_PPC) $(CPPFLAGS_PPC) $(2) -o build/$(1)_ppc.out $(1).cpp
execstack -s build/$(1)_ppc.out execstack -s build/$(1)_ppc.out
endef endef
define compile_all define compile_all
$(shell mkdir -p "build") $(shell mkdir -p "build")
$(call compile_x64,$(1)) $(call compile_x64,$(1),$(2))
$(call compile_x86,$(1)) $(call compile_x86,$(1),$(2))
$(call compile_arm,$(1)) $(call compile_arm,$(1),$(2))
$(call compile_mips,$(1)) $(call compile_mips,$(1),$(2))
$(call compile_ppc,$(1)) $(call compile_ppc,$(1),$(2))
endef endef
define compile_all_cpp define compile_all_cpp
$(shell mkdir -p "build") $(shell mkdir -p "build")
$(call compile_x64_cpp,$(1)) $(call compile_x64_cpp,$(1),$(2))
$(call compile_x86_cpp,$(1)) $(call compile_x86_cpp,$(1),$(2))
$(call compile_arm_cpp,$(1)) $(call compile_arm_cpp,$(1),$(2))
$(call compile_mips_cpp,$(1)) $(call compile_mips_cpp,$(1),$(2))
$(call compile_ppc_cpp,$(1)) $(call compile_ppc_cpp,$(1),$(2))
endef endef
all: all:
$(call compile_all,c_constructs) $(call compile_all,c_constructs,$(NO_OPTIMIZE))
$(call compile_all,cwe_190) $(call compile_all,cwe_190,$(NO_OPTIMIZE))
$(call compile_all,cwe_243) $(call compile_all,cwe_243,$(NO_OPTIMIZE))
$(call compile_all,cwe_243_clean) $(call compile_all,cwe_243_clean,$(NO_OPTIMIZE))
$(call compile_all_cpp,cwe_248) $(call compile_all_cpp,cwe_248,$(NO_OPTIMIZE))
$(call compile_all,cwe_332) $(call compile_all,cwe_332,$(NO_OPTIMIZE))
$(call compile_all,cwe_367) $(call compile_all,cwe_367,$(NO_OPTIMIZE))
$(call compile_all,cwe_415) $(call compile_all,cwe_415,$(NO_OPTIMIZE))
$(call compile_all,cwe_426) $(call compile_all,cwe_426,$(NO_OPTIMIZE))
$(call compile_all,cwe_457) $(call compile_all,cwe_457,$(NO_OPTIMIZE))
$(call compile_all,cwe_467) $(call compile_all,cwe_467,$(NO_OPTIMIZE))
$(call compile_all,cwe_476) $(call compile_all,cwe_476,$(OPTIMIZE))
$(call compile_all,cwe_478) $(call compile_all,cwe_478,$(NO_OPTIMIZE))
$(call compile_all,cwe_676) $(call compile_all,cwe_676,$(NO_OPTIMIZE))
$(call compile_x64,cwe_782) $(call compile_x64,cwe_782,$(NO_OPTIMIZE))
$(call compile_all,arrays) $(call compile_all,arrays,$(NO_OPTIMIZE))
$(call compile_all,memory_access) $(call compile_all,memory_access,$(NO_OPTIMIZE))
clean: clean:
rm -rf build rm -rf build
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment