source/interprocedural_analyses/taint/callModel.ml (218 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open Core
open Pyre
open Ast
open Interprocedural
open Domains
let at_callsite ~resolution ~call_target ~arguments =
let call_target = (call_target :> Target.t) in
match Interprocedural.FixpointState.get_model call_target with
| None -> Model.obscure_model
| Some model ->
let expand_model_via_features
{ Model.forward; backward = { sink_taint; taint_in_taint_out }; sanitizers; modes }
=
let expand_frame_via_features frame =
let breadcrumbs =
Frame.get Frame.Slots.ViaFeature frame
|> Features.expand_via_features ~resolution ~callees:[call_target] ~arguments
in
Frame.add_propagated_breadcrumbs breadcrumbs frame
in
(* Note that we only need to do this for taint-in-taint-out, since
* via-features expansion is done in `apply_call` for sources and sinks. *)
let taint_in_taint_out =
BackwardState.transform Frame.Self Map ~f:expand_frame_via_features taint_in_taint_out
in
{ Model.forward; backward = { sink_taint; taint_in_taint_out }; sanitizers; modes }
in
let taint_model =
Interprocedural.AnalysisResult.get_model TaintResult.kind model
|> Option.value ~default:Model.empty_model
|> expand_model_via_features
in
let taint_model =
if model.is_obscure then
{ taint_model with Model.modes = Model.ModeSet.add Obscure taint_model.modes }
else
taint_model
in
taint_model
module ArgumentMatches = struct
type t = {
argument: Expression.t;
sink_matches: AccessPath.argument_match list;
tito_matches: AccessPath.argument_match list;
sanitize_matches: AccessPath.argument_match list;
}
end
let match_actuals_to_formals ~model:{ Model.backward; sanitizers; _ } ~arguments =
let sink_argument_matches =
BackwardState.roots backward.sink_taint
|> AccessPath.match_actuals_to_formals arguments
|> List.map ~f:(fun (argument, argument_match) ->
argument.Expression.Call.Argument.value, argument_match)
in
let tito_argument_matches =
BackwardState.roots backward.taint_in_taint_out
|> AccessPath.match_actuals_to_formals arguments
|> List.map ~f:(fun (argument, argument_match) ->
argument.Expression.Call.Argument.value, argument_match)
in
let sanitize_argument_matches =
SanitizeRootMap.roots sanitizers.roots
|> AccessPath.match_actuals_to_formals arguments
|> List.map ~f:(fun (argument, argument_match) ->
argument.Expression.Call.Argument.value, argument_match)
in
List.zip_exn tito_argument_matches sanitize_argument_matches
|> List.zip_exn sink_argument_matches
|> List.map ~f:(fun ((argument, sink_matches), ((_, tito_matches), (_, sanitize_matches))) ->
{ ArgumentMatches.argument; sink_matches; tito_matches; sanitize_matches })
let tito_sanitize_of_argument ~model:{ Model.sanitizers; _ } ~sanitize_matches =
let to_tito_sanitize { Sanitize.sources; sinks; tito } =
let sources_tito =
match sources with
| None -> None
| Some SanitizeSources.All -> Some SanitizeTito.All
| Some (SanitizeSources.Specific sources) ->
Some
(SanitizeTito.Specific
{ sanitized_tito_sources = sources; sanitized_tito_sinks = Sinks.Set.empty })
in
let sinks_tito =
match sinks with
| None -> None
| Some SanitizeSinks.All -> Some SanitizeTito.All
| Some (SanitizeSinks.Specific sinks) ->
Some
(SanitizeTito.Specific
{ sanitized_tito_sources = Sources.Set.empty; sanitized_tito_sinks = sinks })
in
tito |> SanitizeTito.join sources_tito |> SanitizeTito.join sinks_tito
in
List.map
~f:(fun { AccessPath.root; _ } -> SanitizeRootMap.get root sanitizers.roots |> to_tito_sanitize)
sanitize_matches
|> List.fold ~f:SanitizeTito.join ~init:SanitizeTito.bottom
|> SanitizeTito.join
(SanitizeRootMap.get AccessPath.Root.LocalResult sanitizers.roots |> to_tito_sanitize)
|> SanitizeTito.join (to_tito_sanitize sanitizers.global)
|> SanitizeTito.join (to_tito_sanitize sanitizers.parameters)
module TaintInTaintOutMap = struct
type t = (Sinks.t, BackwardState.Tree.t) Map.Poly.t
let empty = Map.Poly.empty
let join left right =
let join ~key:_ = function
| `Left left -> Some left
| `Right right -> Some right
| `Both (left, right) -> Some (BackwardState.Tree.join left right)
in
Map.Poly.merge left right ~f:join
let get map ~kind = Map.Poly.find map kind
let set map ~kind ~tito_tree = Map.Poly.set map ~key:kind ~data:tito_tree
let remove map ~kind = Map.Poly.remove map kind
let fold map ~init ~f = Map.Poly.fold map ~init ~f:(fun ~key ~data -> f ~kind:key ~tito_tree:data)
end
let taint_in_taint_out_mapping
~transform_non_leaves
~model:({ Model.backward; modes; _ } as model)
~tito_matches
~sanitize_matches
=
let combine_tito sofar { AccessPath.root; actual_path; formal_path } =
BackwardState.read ~transform_non_leaves ~root ~path:formal_path backward.taint_in_taint_out
|> BackwardState.Tree.prepend actual_path
|> BackwardState.Tree.partition Domains.BackwardTaint.kind By ~f:Fn.id
|> TaintInTaintOutMap.join sofar
in
let mapping = List.fold tito_matches ~f:combine_tito ~init:TaintInTaintOutMap.empty in
let mapping =
if Model.ModeSet.contains Obscure modes then
(* Turn source- and sink- specific tito sanitizers into a tito taint with
* sanitize taint transforms for obscure models. *)
let obscure_sanitize = tito_sanitize_of_argument ~model ~sanitize_matches in
let obscure_breadcrumbs =
TaintInTaintOutMap.get mapping ~kind:Sinks.LocalReturn
>>| BackwardState.Tree.joined_breadcrumbs
|> Option.value ~default:Features.BreadcrumbSet.empty
|> Features.BreadcrumbSet.add (Features.obscure_model ())
in
let mapping = TaintInTaintOutMap.remove mapping ~kind:Sinks.LocalReturn in
match obscure_sanitize with
| Some All -> mapping
| Some (Specific { sanitized_tito_sources; sanitized_tito_sinks }) ->
let sanitize_transforms =
Sources.Set.to_sanitize_transforms_exn sanitized_tito_sources
|> SanitizeTransform.Set.union
(Sinks.Set.to_sanitize_transforms_exn sanitized_tito_sinks)
in
let tito_kind =
Sinks.Transform
{
local = TaintTransforms.of_sanitize_transforms sanitize_transforms;
global = TaintTransforms.empty;
base = Sinks.LocalReturn;
}
in
let return_tito =
Domains.local_return_frame
|> Frame.update Frame.Slots.Breadcrumb obscure_breadcrumbs
|> BackwardTaint.singleton tito_kind
|> BackwardState.Tree.create_leaf
in
TaintInTaintOutMap.set mapping ~kind:tito_kind ~tito_tree:return_tito
| None ->
let return_tito =
Domains.local_return_frame
|> Frame.update Frame.Slots.Breadcrumb obscure_breadcrumbs
|> BackwardTaint.singleton Sinks.LocalReturn
|> BackwardState.Tree.create_leaf
in
TaintInTaintOutMap.set mapping ~kind:Sinks.LocalReturn ~tito_tree:return_tito
else
mapping
in
mapping
let return_paths ~kind ~tito_taint =
match Sinks.discard_transforms kind with
| Sinks.LocalReturn ->
BackwardTaint.fold Features.ReturnAccessPathSet.Element tito_taint ~f:List.cons ~init:[]
| _ ->
(* No special handling of paths for side effects *)
[[]]
let sink_trees_of_argument
~resolution
~transform_non_leaves
~model:{ Model.backward; _ }
~location
~call_target:({ CallGraph.CallTarget.target; _ } as call_target)
~arguments
~sink_matches
~is_self_call
~caller_class_interval
~receiver_class_interval
=
let to_sink_tree_with_identifier { AccessPath.root; actual_path; formal_path } =
let sink_tree =
BackwardState.read ~transform_non_leaves ~root ~path:[] backward.sink_taint
|> BackwardState.Tree.apply_call
~resolution
~location
~callee:(Some target)
~arguments
~port:root
~is_self_call
~caller_class_interval
~receiver_class_interval
|> BackwardState.Tree.read ~transform_non_leaves formal_path
|> BackwardState.Tree.prepend actual_path
in
{ Issue.SinkTreeWithHandle.sink_tree; handle = Issue.SinkHandle.make_call ~call_target ~root }
in
List.map sink_matches ~f:to_sink_tree_with_identifier |> Issue.SinkTreeWithHandle.filter_bottom