[quandary] Split exec_instr

Reviewed By: ngorogiannis

Differential Revision: D13465603

fbshipit-source-id: 02799eec4
master
Mehdi Bouaziz 6 years ago committed by Facebook Github Bot
parent 9d6a9f52ec
commit 3ad33c979e

@ -445,77 +445,262 @@ module Make (TaintSpecification : TaintSpec.S) = struct
TaintDomain.trace_fold add_to_caller_tree summary caller_access_tree
let exec_instr (astate : Domain.t) (proc_data : extras ProcData.t) _ (instr : HilInstr.t) =
(* not all sinks are function calls; we might want to treat an array or field access as a
sink too. do this by pretending an access is a call to a dummy function and using the
existing machinery for adding function call sinks *)
let add_sinks_for_access_path access_expr loc astate =
let rec add_sinks_for_access astate_acc = function
| HilExp.AccessExpression.Base _ ->
astate_acc
| HilExp.AccessExpression.FieldOffset (ae, _)
| ArrayOffset (ae, _, None)
| AddressOf ae
| Dereference ae ->
add_sinks_for_access astate_acc ae
| HilExp.AccessExpression.ArrayOffset (ae, _, Some index) ->
let dummy_call_site = CallSite.make BuiltinDecl.__array_access loc in
let dummy_actuals =
List.map
~f:(fun index_ae -> HilExp.AccessExpression index_ae)
(HilExp.get_access_exprs index)
in
let sinks =
TraceDomain.Sink.get dummy_call_site dummy_actuals CallFlags.default
proc_data.ProcData.tenv
in
let astate_acc_result =
List.fold sinks ~init:astate_acc ~f:(fun astate sink ->
add_sink sink dummy_actuals astate proc_data dummy_call_site )
in
add_sinks_for_access astate_acc_result ae
(* not all sinks are function calls; we might want to treat an array or field access as a
sink too. do this by pretending an access is a call to a dummy function and using the
existing machinery for adding function call sinks *)
let add_sinks_for_access_path (proc_data : extras ProcData.t) access_expr loc astate =
let rec add_sinks_for_access astate_acc = function
| HilExp.AccessExpression.Base _ ->
astate_acc
| HilExp.AccessExpression.FieldOffset (ae, _)
| ArrayOffset (ae, _, None)
| AddressOf ae
| Dereference ae ->
add_sinks_for_access astate_acc ae
| HilExp.AccessExpression.ArrayOffset (ae, _, Some index) ->
let dummy_call_site = CallSite.make BuiltinDecl.__array_access loc in
let dummy_actuals =
List.map
~f:(fun index_ae -> HilExp.AccessExpression index_ae)
(HilExp.get_access_exprs index)
in
let sinks =
TraceDomain.Sink.get dummy_call_site dummy_actuals CallFlags.default
proc_data.ProcData.tenv
in
let astate_acc_result =
List.fold sinks ~init:astate_acc ~f:(fun astate sink ->
add_sink sink dummy_actuals astate proc_data dummy_call_site )
in
add_sinks_for_access astate_acc_result ae
in
add_sinks_for_access astate access_expr
let add_sources_for_access_path (proc_data : extras ProcData.t) access_expr loc astate =
let var, _ = HilExp.AccessExpression.get_base access_expr in
if Var.is_global var then
let dummy_call_site = CallSite.make BuiltinDecl.__global_access loc in
let sources =
TraceDomain.Source.get dummy_call_site
[HilExp.AccessExpression access_expr]
proc_data.tenv
in
add_sinks_for_access astate access_expr
List.fold sources ~init:astate ~f:(fun astate {TraceDomain.Source.source} ->
let access_path =
AccessPath.Abs.Exact (HilExp.AccessExpression.to_access_path access_expr)
in
let trace, subtree =
Option.value ~default:TaintDomain.empty_node
(TaintDomain.get_node access_path astate)
in
TaintDomain.add_node access_path (TraceDomain.add_source source trace, subtree) astate
)
else astate
let rec add_sources_sinks_for_exp (proc_data : extras ProcData.t) exp loc astate =
match exp with
| HilExp.Cast (_, e) ->
add_sources_sinks_for_exp proc_data e loc astate
| HilExp.AccessExpression access_expr ->
add_sinks_for_access_path proc_data access_expr loc astate
|> add_sources_for_access_path proc_data access_expr loc
| _ ->
astate
let exec_write (proc_data : extras ProcData.t) lhs_access_expr rhs_exp astate =
let rhs_node =
Option.value (hil_exp_get_node rhs_exp astate proc_data) ~default:TaintDomain.empty_node
in
let lhs_access_path = HilExp.AccessExpression.to_access_path lhs_access_expr in
TaintDomain.add_node (AccessPath.Abs.Exact lhs_access_path) rhs_node astate
let analyze_call (proc_data : extras ProcData.t) ~ret_ap ~callee_pname ~actuals ~call_flags
~callee_loc astate =
let astate =
List.fold
~f:(fun acc exp -> add_sources_sinks_for_exp proc_data exp callee_loc acc)
actuals ~init:astate
in
let add_sources_for_access_path access_expr loc astate =
let var, _ = HilExp.AccessExpression.get_base access_expr in
if Var.is_global var then
let dummy_call_site = CallSite.make BuiltinDecl.__global_access loc in
let sources =
TraceDomain.Source.get dummy_call_site
[HilExp.AccessExpression access_expr]
proc_data.tenv
let handle_model callee_pname access_tree model =
let is_variadic =
match callee_pname with
| Typ.Procname.Java pname ->
Typ.Procname.Java.is_vararg pname
| _ ->
false
in
let should_taint_typ typ = is_variadic || TaintSpecification.is_taintable_type typ in
let exp_join_traces trace_acc exp =
match hil_exp_get_node ~abstracted:true exp access_tree proc_data with
| Some (trace, _) ->
TraceDomain.join trace trace_acc
| None ->
trace_acc
in
let propagate_to_access_path access_path actuals access_tree =
let initial_trace = access_path_get_trace access_path access_tree proc_data in
let trace_with_propagation = List.fold ~f:exp_join_traces ~init:initial_trace actuals in
let sources = TraceDomain.sources trace_with_propagation in
let filtered_footprint =
TraceDomain.Sources.Footprint.fold
(fun acc access_path (is_mem, _) ->
if
is_mem
&& Option.exists
(AccessPath.get_typ (AccessPath.Abs.extract access_path) proc_data.tenv)
~f:should_taint_typ
then TraceDomain.Sources.Footprint.add_trace access_path true acc
else acc )
sources.footprint TraceDomain.Sources.Footprint.empty
in
List.fold sources ~init:astate ~f:(fun astate {TraceDomain.Source.source} ->
let access_path =
AccessPath.Abs.Exact (HilExp.AccessExpression.to_access_path access_expr)
in
let trace, subtree =
Option.value ~default:TaintDomain.empty_node
(TaintDomain.get_node access_path astate)
in
TaintDomain.add_node access_path
(TraceDomain.add_source source trace, subtree)
astate )
else astate
let filtered_sources = {sources with footprint= filtered_footprint} in
if TraceDomain.Sources.is_empty filtered_sources then access_tree
else
let trace' = TraceDomain.update_sources trace_with_propagation filtered_sources in
let pruned_trace =
if TraceDomain.Sources.Footprint.is_empty filtered_footprint then
(* empty footprint; nothing else can flow into these sinks. so don't track them *)
TraceDomain.update_sinks trace' TraceDomain.Sinks.empty
else trace'
in
TaintDomain.add_trace access_path pruned_trace access_tree
in
let handle_model_ astate_acc propagation =
match (propagation, actuals) with
| _, [] ->
astate_acc
| TaintSpec.Propagate_to_return, actuals ->
propagate_to_access_path (AccessPath.Abs.Abstracted (ret_ap, [])) actuals astate_acc
| ( TaintSpec.Propagate_to_receiver
, HilExp.AccessExpression receiver_ae :: (_ :: _ as other_actuals) ) ->
propagate_to_access_path
(AccessPath.Abs.Abstracted (HilExp.AccessExpression.to_access_path receiver_ae))
other_actuals astate_acc
| TaintSpec.Propagate_to_actual actual_index, _ -> (
match Option.map (List.nth actuals actual_index) ~f:HilExp.ignore_cast with
| Some (HilExp.AccessExpression actual_ae) ->
propagate_to_access_path
(AccessPath.Abs.Abstracted (HilExp.AccessExpression.to_access_path actual_ae))
actuals astate_acc
| _ ->
astate_acc )
| _ ->
astate_acc
in
List.fold ~f:handle_model_ ~init:access_tree model
in
let rec add_sources_sinks_for_exp exp loc astate =
match exp with
| HilExp.Cast (_, e) ->
add_sources_sinks_for_exp e loc astate
| HilExp.AccessExpression access_expr ->
add_sinks_for_access_path access_expr loc astate
|> add_sources_for_access_path access_expr loc
let handle_unknown_call callee_pname access_tree =
match Typ.Procname.get_method callee_pname with
| "operator=" when not (Typ.Procname.is_java callee_pname) -> (
(* treat unknown calls to C++ operator= as assignment *)
match List.map actuals ~f:HilExp.ignore_cast with
| [AccessExpression lhs_access_expr; rhs_exp] ->
exec_write proc_data lhs_access_expr rhs_exp access_tree
| [AccessExpression lhs_access_expr; rhs_exp; HilExp.AccessExpression access_expr] -> (
let dummy_ret_access_expr = access_expr in
match dummy_ret_access_expr with
| HilExp.AccessExpression.Base (Var.ProgramVar pvar, _)
when Pvar.is_frontend_tmp pvar ->
(* the frontend translates operator=(x, y) as operator=(x, y, dummy_ret) when
operator= returns a value type *)
exec_write proc_data lhs_access_expr rhs_exp access_tree
|> exec_write proc_data dummy_ret_access_expr rhs_exp
| _ ->
L.internal_error "Unexpected call to operator= at %a" Location.pp callee_loc ;
access_tree )
| _ ->
L.internal_error "Unexpected call to operator= at %a" Location.pp callee_loc ;
access_tree )
| _ ->
astate
let model =
TaintSpecification.handle_unknown_call callee_pname (snd ret_ap) actuals
proc_data.tenv
in
handle_model callee_pname access_tree model
in
let exec_write lhs_access_expr rhs_exp astate =
let rhs_node =
Option.value (hil_exp_get_node rhs_exp astate proc_data) ~default:TaintDomain.empty_node
let dummy_ret_opt =
match ret_ap with
| _, {Typ.desc= Tvoid} when not (Typ.Procname.is_java callee_pname) -> (
(* the C++ frontend handles returns of non-pointers by adding a dummy
pass-by-reference variable as the last actual, then returning the value by
assigning to it. understand this pattern by pretending it's the return value *)
match List.last actuals with
| Some (HilExp.AccessExpression access_expr) -> (
match HilExp.AccessExpression.to_access_path access_expr with
| ((Var.ProgramVar pvar, _) as ret_base), [] when Pvar.is_frontend_tmp pvar ->
Some ret_base
| _ ->
None )
| _ ->
None )
| _ ->
Some ret_ap
in
let analyze_call astate_acc callee_pname =
let call_site = CallSite.make callee_pname callee_loc in
let astate_with_sink =
if List.is_empty actuals then astate
else
let sinks =
TraceDomain.Sink.get call_site actuals call_flags proc_data.ProcData.tenv
in
List.fold sinks ~init:astate ~f:(fun astate sink ->
add_sink sink actuals astate proc_data call_site )
in
let astate_with_summary =
let sources = TraceDomain.Source.get call_site actuals proc_data.tenv in
match sources with
| _ :: _ ->
(* don't use a summary for a procedure that is a direct source *)
List.fold sources ~init:astate_with_sink
~f:(fun astate {TraceDomain.Source.source; index} ->
match index with
| None ->
Option.value_map dummy_ret_opt ~default:astate ~f:(fun ret_base ->
add_return_source source ret_base astate )
| Some index ->
add_actual_source source index actuals astate_with_sink proc_data )
| [] -> (
match Payload.read proc_data.pdesc callee_pname with
| None ->
handle_unknown_call callee_pname astate_with_sink
| Some summary -> (
let ret_typ = snd ret_ap in
let access_tree = TaintSpecification.of_summary_access_tree summary in
match
TaintSpecification.get_model callee_pname ret_typ actuals proc_data.tenv
access_tree
with
| Some model ->
handle_model callee_pname astate_with_sink model
| None ->
apply_summary dummy_ret_opt actuals access_tree astate_with_sink proc_data
call_site ) )
in
let astate_with_sanitizer =
match dummy_ret_opt with
| None ->
astate_with_summary
| Some ret_base -> (
match TraceDomain.Sanitizer.get callee_pname proc_data.tenv with
| Some sanitizer ->
let ret_ap = AccessPath.Abs.Exact (ret_base, []) in
let ret_trace = access_path_get_trace ret_ap astate_with_summary proc_data in
let ret_trace' = TraceDomain.add_sanitizer sanitizer ret_trace in
TaintDomain.add_trace ret_ap ret_trace' astate_with_summary
| None ->
astate_with_summary )
in
let lhs_access_path = HilExp.AccessExpression.to_access_path lhs_access_expr in
TaintDomain.add_node (AccessPath.Abs.Exact lhs_access_path) rhs_node astate
Domain.join astate_acc astate_with_sanitizer
in
analyze_call Domain.empty callee_pname
let exec_instr (astate : Domain.t) (proc_data : extras ProcData.t) _ (instr : HilInstr.t) =
match instr with
| Assign (Base (Var.ProgramVar pvar, _), HilExp.Exception _, _) when Pvar.is_return pvar ->
(* the Java frontend translates `throw Exception` as `return Exception`, which is a bit
@ -530,198 +715,13 @@ module Make (TaintSpecification : TaintSpec.S) = struct
`return null` in a void function *)
astate
| Assign (lhs_access_expr, rhs_exp, loc) ->
add_sources_sinks_for_exp rhs_exp loc astate
|> add_sinks_for_access_path lhs_access_expr loc
|> exec_write lhs_access_expr rhs_exp
add_sources_sinks_for_exp proc_data rhs_exp loc astate
|> add_sinks_for_access_path proc_data lhs_access_expr loc
|> exec_write proc_data lhs_access_expr rhs_exp
| Assume (assume_exp, _, _, loc) ->
add_sources_sinks_for_exp assume_exp loc astate
| Call (ret_ap, Direct called_pname, actuals, call_flags, callee_loc) ->
let astate =
List.fold
~f:(fun acc exp -> add_sources_sinks_for_exp exp callee_loc acc)
actuals ~init:astate
in
let handle_model callee_pname access_tree model =
let is_variadic =
match callee_pname with
| Typ.Procname.Java pname ->
Typ.Procname.Java.is_vararg pname
| _ ->
false
in
let should_taint_typ typ = is_variadic || TaintSpecification.is_taintable_type typ in
let exp_join_traces trace_acc exp =
match hil_exp_get_node ~abstracted:true exp access_tree proc_data with
| Some (trace, _) ->
TraceDomain.join trace trace_acc
| None ->
trace_acc
in
let propagate_to_access_path access_path actuals access_tree =
let initial_trace = access_path_get_trace access_path access_tree proc_data in
let trace_with_propagation =
List.fold ~f:exp_join_traces ~init:initial_trace actuals
in
let sources = TraceDomain.sources trace_with_propagation in
let filtered_footprint =
TraceDomain.Sources.Footprint.fold
(fun acc access_path (is_mem, _) ->
if
is_mem
&& Option.exists
(AccessPath.get_typ (AccessPath.Abs.extract access_path) proc_data.tenv)
~f:should_taint_typ
then TraceDomain.Sources.Footprint.add_trace access_path true acc
else acc )
sources.footprint TraceDomain.Sources.Footprint.empty
in
let filtered_sources = {sources with footprint= filtered_footprint} in
if TraceDomain.Sources.is_empty filtered_sources then access_tree
else
let trace' = TraceDomain.update_sources trace_with_propagation filtered_sources in
let pruned_trace =
if TraceDomain.Sources.Footprint.is_empty filtered_footprint then
(* empty footprint; nothing else can flow into these sinks. so don't track them *)
TraceDomain.update_sinks trace' TraceDomain.Sinks.empty
else trace'
in
TaintDomain.add_trace access_path pruned_trace access_tree
in
let handle_model_ astate_acc propagation =
match (propagation, actuals) with
| _, [] ->
astate_acc
| TaintSpec.Propagate_to_return, actuals ->
propagate_to_access_path
(AccessPath.Abs.Abstracted (ret_ap, []))
actuals astate_acc
| ( TaintSpec.Propagate_to_receiver
, AccessExpression receiver_ae :: (_ :: _ as other_actuals) ) ->
propagate_to_access_path
(AccessPath.Abs.Abstracted (HilExp.AccessExpression.to_access_path receiver_ae))
other_actuals astate_acc
| TaintSpec.Propagate_to_actual actual_index, _ -> (
match Option.map (List.nth actuals actual_index) ~f:HilExp.ignore_cast with
| Some (HilExp.AccessExpression actual_ae) ->
propagate_to_access_path
(AccessPath.Abs.Abstracted (HilExp.AccessExpression.to_access_path actual_ae))
actuals astate_acc
| _ ->
astate_acc )
| _ ->
astate_acc
in
List.fold ~f:handle_model_ ~init:access_tree model
in
let handle_unknown_call callee_pname access_tree =
match Typ.Procname.get_method callee_pname with
| "operator=" when not (Typ.Procname.is_java callee_pname) -> (
(* treat unknown calls to C++ operator= as assignment *)
match List.map actuals ~f:HilExp.ignore_cast with
| [AccessExpression lhs_access_expr; rhs_exp] ->
exec_write lhs_access_expr rhs_exp access_tree
| [AccessExpression lhs_access_expr; rhs_exp; HilExp.AccessExpression access_expr] -> (
let dummy_ret_access_expr = access_expr in
match dummy_ret_access_expr with
| HilExp.AccessExpression.Base (Var.ProgramVar pvar, _)
when Pvar.is_frontend_tmp pvar ->
(* the frontend translates operator=(x, y) as operator=(x, y, dummy_ret) when
operator= returns a value type *)
exec_write lhs_access_expr rhs_exp access_tree
|> exec_write dummy_ret_access_expr rhs_exp
| _ ->
L.internal_error "Unexpected call to operator= %a in %a" HilInstr.pp instr
Typ.Procname.pp
(Procdesc.get_proc_name proc_data.pdesc) ;
access_tree )
| _ ->
L.internal_error "Unexpected call to operator= %a in %a" HilInstr.pp instr
Typ.Procname.pp
(Procdesc.get_proc_name proc_data.pdesc) ;
access_tree )
| _ ->
let model =
TaintSpecification.handle_unknown_call callee_pname (snd ret_ap) actuals
proc_data.tenv
in
handle_model callee_pname access_tree model
in
let dummy_ret_opt =
match ret_ap with
| _, {Typ.desc= Tvoid} when not (Typ.Procname.is_java called_pname) -> (
(* the C++ frontend handles returns of non-pointers by adding a dummy
pass-by-reference variable as the last actual, then returning the value by
assigning to it. understand this pattern by pretending it's the return value *)
match List.last actuals with
| Some (HilExp.AccessExpression access_expr) -> (
match HilExp.AccessExpression.to_access_path access_expr with
| ((Var.ProgramVar pvar, _) as ret_base), [] when Pvar.is_frontend_tmp pvar ->
Some ret_base
| _ ->
None )
| _ ->
None )
| _ ->
Some ret_ap
in
let analyze_call astate_acc callee_pname =
let call_site = CallSite.make callee_pname callee_loc in
let astate_with_sink =
if List.is_empty actuals then astate
else
let sinks =
TraceDomain.Sink.get call_site actuals call_flags proc_data.ProcData.tenv
in
List.fold sinks ~init:astate ~f:(fun astate sink ->
add_sink sink actuals astate proc_data call_site )
in
let astate_with_summary =
let sources = TraceDomain.Source.get call_site actuals proc_data.tenv in
match sources with
| _ :: _ ->
(* don't use a summary for a procedure that is a direct source *)
List.fold sources ~init:astate_with_sink
~f:(fun astate {TraceDomain.Source.source; index} ->
match index with
| None ->
Option.value_map dummy_ret_opt ~default:astate ~f:(fun ret_base ->
add_return_source source ret_base astate )
| Some index ->
add_actual_source source index actuals astate_with_sink proc_data )
| [] -> (
match Payload.read proc_data.pdesc callee_pname with
| None ->
handle_unknown_call callee_pname astate_with_sink
| Some summary -> (
let ret_typ = snd ret_ap in
let access_tree = TaintSpecification.of_summary_access_tree summary in
match
TaintSpecification.get_model callee_pname ret_typ actuals proc_data.tenv
access_tree
with
| Some model ->
handle_model callee_pname astate_with_sink model
| None ->
apply_summary dummy_ret_opt actuals access_tree astate_with_sink proc_data
call_site ) )
in
let astate_with_sanitizer =
match dummy_ret_opt with
| None ->
astate_with_summary
| Some ret_base -> (
match TraceDomain.Sanitizer.get callee_pname proc_data.tenv with
| Some sanitizer ->
let ret_ap = AccessPath.Abs.Exact (ret_base, []) in
let ret_trace = access_path_get_trace ret_ap astate_with_summary proc_data in
let ret_trace' = TraceDomain.add_sanitizer sanitizer ret_trace in
TaintDomain.add_trace ret_ap ret_trace' astate_with_summary
| None ->
astate_with_summary )
in
Domain.join astate_acc astate_with_sanitizer
in
analyze_call Domain.empty called_pname
add_sources_sinks_for_exp proc_data assume_exp loc astate
| Call (ret_ap, Direct callee_pname, actuals, call_flags, callee_loc) ->
analyze_call proc_data ~ret_ap ~callee_pname ~actuals ~call_flags ~callee_loc astate
| _ ->
astate

Loading…
Cancel
Save