source/interprocedural_analyses/taint/domains.ml (1,143 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 Ast
let location_to_json
{
Location.start = { line = start_line; column = start_column };
stop = { line = end_line; column = end_column };
}
: Yojson.Safe.json
=
(* If the location spans multiple lines, we only return the position of the first character. *)
`Assoc
[
"line", `Int start_line;
"start", `Int start_column;
"end", `Int (if start_line = end_line then end_column else start_column);
]
let location_with_module_to_json ~filename_lookup location_with_module : Yojson.Safe.json =
let optionally_add_filename fields =
match filename_lookup with
| Some lookup ->
let { Location.WithPath.path; _ } =
Location.WithModule.instantiate ~lookup location_with_module
in
("filename", `String path) :: fields
| None -> fields
in
match location_to_json (Location.strip_module location_with_module) with
| `Assoc fields -> `Assoc (optionally_add_filename fields)
| _ -> failwith "unreachable"
(* Represents the link between frames. *)
module CallInfo = struct
let name = "call info"
type t =
(* User-specified taint on a model. *)
| Declaration of {
(* If not provided, the leaf name set is set as the callee when taint is propagated. *)
leaf_name_provided: bool;
}
(* Leaf taint at the callsite of a tainted model, i.e the start or end of the trace. *)
| Origin of Location.WithModule.t
(* Taint propagated from a call. *)
| CallSite of {
port: AccessPath.Root.t;
path: Abstract.TreeDomain.Label.path;
location: Location.WithModule.t;
callees: Interprocedural.Target.t list;
}
[@@deriving compare]
let pp formatter = function
| Declaration _ -> Format.fprintf formatter "Declaration"
| Origin location -> Format.fprintf formatter "Origin(%a)" Location.WithModule.pp location
| CallSite { location; callees; port; path } ->
let port = AccessPath.create port path |> AccessPath.show in
Format.fprintf
formatter
"CallSite(callees=[%s], location=%a, port=%s)"
(String.concat
~sep:", "
(List.map ~f:Interprocedural.Target.external_target_name callees))
Location.WithModule.pp
location
port
let show = Format.asprintf "%a" pp
(* Breaks recursion among trace info and overall taint domain. *)
(* See implementation in taintResult.ml. *)
let has_significant_summary =
ref
(fun
(_ : AccessPath.Root.t)
(_ : Abstract.TreeDomain.Label.path)
(_ : Interprocedural.Target.non_override_t)
-> true)
(* Only called when emitting models before we compute the json so we can dedup *)
let expand_call_site trace =
match trace with
| CallSite { location; callees; port; path } ->
let callees =
Interprocedural.DependencyGraph.expand_callees callees
|> List.filter ~f:(!has_significant_summary port path)
in
CallSite { location; callees = (callees :> Interprocedural.Target.t list); port; path }
| _ -> trace
let create_json ~filename_lookup trace : string * Yojson.Safe.json =
match trace with
| Declaration _ -> "decl", `Null
| Origin location ->
let location_json = location_with_module_to_json ~filename_lookup location in
"root", location_json
| CallSite { location; callees; port; path } ->
let callee_json =
callees
|> List.map ~f:(fun callable ->
`String (Interprocedural.Target.external_target_name callable))
in
let location_json = location_with_module_to_json ~filename_lookup location in
let port_json = AccessPath.create port path |> AccessPath.to_json in
let call_json =
`Assoc ["position", location_json; "resolves_to", `List callee_json; "port", port_json]
in
"call", call_json
(* Returns the (dictionary key * json) to emit *)
let to_json = create_json ~filename_lookup:None
let to_external_json ~filename_lookup = create_json ~filename_lookup:(Some filename_lookup)
let less_or_equal ~left ~right =
match left, right with
| ( CallSite
{ path = path_left; location = location_left; port = port_left; callees = callees_left },
CallSite
{
path = path_right;
location = location_right;
port = port_right;
callees = callees_right;
} ) ->
[%compare.equal: AccessPath.Root.t] port_left port_right
&& Location.WithModule.compare location_left location_right = 0
&& [%compare.equal: Interprocedural.Target.t list] callees_left callees_right
&& Abstract.TreeDomain.Label.compare_path path_right path_left = 0
| _ -> [%compare.equal: t] left right
let widen set = set
let strip_for_callsite = function
| Origin _ -> Origin Location.WithModule.any
| CallSite { port; path; location = _; callees } ->
CallSite { port; path; location = Location.WithModule.any; callees }
| Declaration _ -> Declaration { leaf_name_provided = false }
end
module TraceLength = Abstract.SimpleDomain.Make (struct
type t = int
let name = "trace length"
let join = min
let meet = max
let less_or_equal ~left ~right = left >= right
let bottom = max_int
let show length = if Int.equal length max_int then "<bottom>" else string_of_int length
end)
module ClassIntervalDomain = struct
include Abstract.SimpleDomain.Make (struct
let name = "class interval"
include Interprocedural.ClassInterval
end)
let top = Interprocedural.ClassInterval.top
end
(* Represents a frame, i.e a single hop between functions. *)
module Frame = struct
module Slots = struct
let name = "frame"
type 'a slot =
| Breadcrumb : Features.BreadcrumbSet.t slot
| ViaFeature : Features.ViaFeatureSet.t slot
| ReturnAccessPath : Features.ReturnAccessPathSet.t slot
| TraceLength : TraceLength.t slot
| LeafName : Features.LeafNameSet.t slot
| FirstIndex : Features.FirstIndexSet.t slot
| FirstField : Features.FirstFieldSet.t slot
(* Must be consistent with above variants *)
let slots = 7
let slot_name (type a) (slot : a slot) =
match slot with
| Breadcrumb -> "Breadcrumb"
| ViaFeature -> "ViaFeature"
| ReturnAccessPath -> "ReturnAccessPath"
| TraceLength -> "TraceLength"
| LeafName -> "LeafName"
| FirstIndex -> "FirstIndex"
| FirstField -> "FirstField"
let slot_domain (type a) (slot : a slot) =
match slot with
| Breadcrumb -> (module Features.BreadcrumbSet : Abstract.Domain.S with type t = a)
| ViaFeature -> (module Features.ViaFeatureSet : Abstract.Domain.S with type t = a)
| ReturnAccessPath ->
(module Features.ReturnAccessPathSet : Abstract.Domain.S with type t = a)
| TraceLength -> (module TraceLength : Abstract.Domain.S with type t = a)
| LeafName -> (module Features.LeafNameSet : Abstract.Domain.S with type t = a)
| FirstIndex -> (module Features.FirstIndexSet : Abstract.Domain.S with type t = a)
| FirstField -> (module Features.FirstFieldSet : Abstract.Domain.S with type t = a)
let strict _ = false
end
include Abstract.ProductDomain.Make (Slots)
let initial =
create
[Part (Features.BreadcrumbSet.Self, Features.BreadcrumbSet.empty); Part (TraceLength.Self, 0)]
let add_propagated_breadcrumb breadcrumb =
transform Features.BreadcrumbSet.Element Add ~f:breadcrumb
let add_propagated_breadcrumbs breadcrumbs =
transform Features.BreadcrumbSet.Self Add ~f:breadcrumbs
let product_pp = pp (* shadow *)
let pp formatter = Format.fprintf formatter "Frame(%a)" product_pp
let show = Format.asprintf "%a" pp
let subtract to_remove ~from =
(* Do not partially subtract slots, since this is unsound. *)
if to_remove == from then
bottom
else if is_bottom to_remove then
from
else if less_or_equal ~left:from ~right:to_remove then
bottom
else
from
end
module type TAINT_DOMAIN = sig
include Abstract.Domain.S
type kind [@@deriving eq]
val kind : kind Abstract.Domain.part
val call_info : CallInfo.t Abstract.Domain.part
val add_local_breadcrumb : Features.BreadcrumbInterned.t -> t -> t
val add_local_breadcrumbs : Features.BreadcrumbSet.t -> t -> t
val add_local_first_index : Abstract.TreeDomain.Label.t -> t -> t
val add_local_first_field : string -> t -> t
(* All breadcrumbs from all flows, accumulated with an `add`.
* The over-under approximation is lost when accumulating. *)
val accumulated_breadcrumbs : t -> Features.BreadcrumbSet.t
(* All breadcrumbs from all flows, accumulated with a `join`.
* The over-under approximation is properly preserved. *)
val joined_breadcrumbs : t -> Features.BreadcrumbSet.t
val first_indices : t -> Features.FirstIndexSet.t
val first_fields : t -> Features.FirstFieldSet.t
val via_features : t -> Features.ViaFeatureSet.t
val transform_on_widening_collapse : t -> t
val prune_maximum_length : TraceLength.t -> t -> t
type kind_set
val is_empty_kind_set : kind_set -> bool
val sanitize : kind_set -> t -> t
val apply_sanitize_transforms : SanitizeTransform.Set.t -> t -> t
(* Apply sanitize transforms only to the special `LocalReturn` sink. *)
val apply_sanitize_sink_transforms : SanitizeTransform.Set.t -> t -> t
val apply_named_transforms : TaintTransform.t list -> t -> t
(* Add trace info at call-site *)
val apply_call
: resolution:Analysis.Resolution.t ->
location:Location.WithModule.t ->
callee:Interprocedural.Target.t option ->
arguments:Ast.Expression.Call.Argument.t list ->
port:AccessPath.Root.t ->
path:Abstract.TreeDomain.Label.path ->
element:t ->
is_self_call:bool ->
caller_class_interval:Interprocedural.ClassInterval.t ->
receiver_class_interval:Interprocedural.ClassInterval.t ->
t
(* Return the taint with only essential elements. *)
val essential
: return_access_paths:(Features.ReturnAccessPathSet.t -> Features.ReturnAccessPathSet.t) ->
t ->
t
val to_json : t -> Yojson.Safe.json
val to_external_json : filename_lookup:(Reference.t -> string option) -> t -> Yojson.Safe.json
end
module type KIND_ARG = sig
include Abstract.SetDomain.ELEMENT
val equal : t -> t -> bool
val show : t -> string
val ignore_kind_at_call : t -> bool
val apply_call : t -> t
val discard_subkind : t -> t
val discard_transforms : t -> t
val apply_sanitize_transforms : SanitizeTransform.Set.t -> t -> t
val apply_sanitize_sink_transforms : SanitizeTransform.Set.t -> t -> t
val apply_named_transforms : TaintTransform.t list -> t -> t
module Set : sig
include Stdlib.Set.S with type elt = t
val pp : Format.formatter -> t -> unit
val show : t -> string
end
end
(* Represents a map from a taint kind (`Sources.t` or `Sinks.t`) to a frame. *)
module MakeKindTaint (Kind : KIND_ARG) = struct
module Key = struct
include Kind
let absence_implicitly_maps_to_bottom = false
end
include Abstract.MapDomain.Make (Key) (Frame)
let singleton kind frame = set bottom ~key:kind ~data:frame
end
(* Represents taint originating from a specific call. *)
module MakeLocalTaint (Kind : KIND_ARG) = struct
module KindTaintDomain = MakeKindTaint (Kind)
module Slots = struct
let name = "local taint"
type 'a slot =
| Kinds : KindTaintDomain.t slot
| TitoPosition : Features.TitoPositionSet.t slot
| Breadcrumb : Features.BreadcrumbSet.t slot
| FirstIndex : Features.FirstIndexSet.t slot
| FirstField : Features.FirstFieldSet.t slot
| ClassInterval : Interprocedural.ClassInterval.t slot
(* Must be consistent with above variants *)
let slots = 6
let slot_name (type a) (slot : a slot) =
match slot with
| Kinds -> "Kinds"
| TitoPosition -> "TitoPosition"
| Breadcrumb -> "Breadcrumb"
| FirstIndex -> "FirstIndex"
| FirstField -> "FirstField"
| ClassInterval -> "ClassInterval"
let slot_domain (type a) (slot : a slot) =
match slot with
| Kinds -> (module KindTaintDomain : Abstract.Domain.S with type t = a)
| TitoPosition -> (module Features.TitoPositionSet : Abstract.Domain.S with type t = a)
| Breadcrumb -> (module Features.BreadcrumbSet : Abstract.Domain.S with type t = a)
| FirstIndex -> (module Features.FirstIndexSet : Abstract.Domain.S with type t = a)
| FirstField -> (module Features.FirstFieldSet : Abstract.Domain.S with type t = a)
| ClassInterval -> (module ClassIntervalDomain : Abstract.Domain.S with type t = a)
let strict (type a) (slot : a slot) =
match slot with
| Kinds
| ClassInterval ->
true
| _ -> false
end
include Abstract.ProductDomain.Make (Slots)
(* Warning: do NOT use `BreadcrumbSet`, `FirstFieldSet` and `FirstFieldSet` abstract parts
* (e.g,`BreadcrumbSet.Self`, `BreadcrumbSet.Element`, etc.) since these are ambiguous.
* They can refer to the sets in `Frame` or in `LocalTaint`. *)
let singleton kind frame =
(* Initialize strict slots first *)
create
[
Abstract.Domain.Part (KindTaintDomain.KeyValue, (kind, frame));
Abstract.Domain.Part (ClassIntervalDomain.Self, Interprocedural.ClassInterval.top);
]
|> update Slots.Breadcrumb Features.BreadcrumbSet.empty
let product_pp = pp (* shadow *)
let pp formatter = Format.fprintf formatter "LocalTaint(%a)" product_pp
let show = Format.asprintf "%a" pp
let subtract to_remove ~from =
(* Do not partially subtract slots, since this is unsound. *)
if to_remove == from then
bottom
else if is_bottom to_remove then
from
else if less_or_equal ~left:from ~right:to_remove then
bottom
else
from
end
module MakeTaint (Kind : KIND_ARG) : sig
include TAINT_DOMAIN with type kind = Kind.t and type kind_set = Kind.Set.t
val kinds : t -> kind list
val singleton : ?location:Location.WithModule.t -> Kind.t -> Frame.t -> t
val of_list : ?location:Location.WithModule.t -> Kind.t list -> t
end = struct
type kind = Kind.t [@@deriving compare, eq]
module CallInfoKey = struct
include CallInfo
let absence_implicitly_maps_to_bottom = true
end
module LocalTaintDomain = MakeLocalTaint (Kind)
module KindTaintDomain = LocalTaintDomain.KindTaintDomain
module Map = Abstract.MapDomain.Make (CallInfoKey) (LocalTaintDomain)
include Map
let add ?location map kind frame =
let call_info =
match location with
| None -> CallInfo.Declaration { leaf_name_provided = false }
| Some location -> CallInfo.Origin location
in
let local_taint = LocalTaintDomain.singleton kind frame in
Map.update map call_info ~f:(function
| None -> local_taint
| Some existing -> LocalTaintDomain.join local_taint existing)
let singleton ?location kind frame = add ?location Map.bottom kind frame
let of_list ?location kinds =
List.fold kinds ~init:Map.bottom ~f:(fun taint kind -> add ?location taint kind Frame.initial)
let kind = KindTaintDomain.Key
let call_info = Map.Key
let kinds map =
Map.fold kind ~init:[] ~f:List.cons map |> List.dedup_and_sort ~compare:Kind.compare
let create_json ~call_info_to_json taint =
let cons_if_non_empty key list assoc =
if List.is_empty list then
assoc
else
(key, `List list) :: assoc
in
let open Features in
let breadcrumbs_to_json ~breadcrumbs ~first_indices ~first_fields =
let breadcrumb_to_json { Abstract.OverUnderSetDomain.element; in_under } breadcrumbs =
let element = BreadcrumbInterned.unintern element in
let json = Breadcrumb.to_json element ~on_all_paths:in_under in
json :: breadcrumbs
in
let breadcrumbs =
BreadcrumbSet.fold BreadcrumbSet.ElementAndUnder ~f:breadcrumb_to_json ~init:[] breadcrumbs
in
let first_index_breadcrumbs =
first_indices
|> FirstIndexSet.elements
|> List.map ~f:FirstIndexInterned.unintern
|> FirstIndex.to_json
in
let first_field_breadcrumbs =
first_fields
|> FirstFieldSet.elements
|> List.map ~f:FirstFieldInterned.unintern
|> FirstField.to_json
in
List.concat [first_index_breadcrumbs; first_field_breadcrumbs; breadcrumbs]
in
let trace_to_json (trace_info, local_taint) =
let json = [call_info_to_json trace_info] in
let tito_positions =
LocalTaintDomain.get LocalTaintDomain.Slots.TitoPosition local_taint
|> TitoPositionSet.elements
|> List.map ~f:location_to_json
in
let json = cons_if_non_empty "tito" tito_positions json in
let local_breadcrumbs =
breadcrumbs_to_json
~breadcrumbs:(LocalTaintDomain.get LocalTaintDomain.Slots.Breadcrumb local_taint)
~first_indices:(LocalTaintDomain.get LocalTaintDomain.Slots.FirstIndex local_taint)
~first_fields:(LocalTaintDomain.get LocalTaintDomain.Slots.FirstField local_taint)
in
let json = cons_if_non_empty "local_features" local_breadcrumbs json in
let add_kind (kind, frame) =
let json = ["kind", `String (Kind.show kind)] in
let trace_length = Frame.get Frame.Slots.TraceLength frame in
let json =
if trace_length = 0 then
json
else
("length", `Int trace_length) :: json
in
let leaves =
Frame.get Frame.Slots.LeafName frame
|> LeafNameSet.elements
|> List.map ~f:LeafNameInterned.unintern
|> List.map ~f:LeafName.to_json
in
let json = cons_if_non_empty "leaves" leaves json in
let return_paths =
Frame.get Frame.Slots.ReturnAccessPath frame
|> ReturnAccessPathSet.elements
|> List.map ~f:ReturnAccessPath.to_json
in
let json = cons_if_non_empty "return_paths" return_paths json in
let via_features =
Frame.get Frame.Slots.ViaFeature frame
|> ViaFeatureSet.elements
|> List.map ~f:ViaFeature.to_json
in
let json = cons_if_non_empty "via_features" via_features json in
let breadcrumbs =
breadcrumbs_to_json
~breadcrumbs:(Frame.get Frame.Slots.Breadcrumb frame)
~first_indices:(Frame.get Frame.Slots.FirstIndex frame)
~first_fields:(Frame.get Frame.Slots.FirstField frame)
in
let json = cons_if_non_empty "features" breadcrumbs json in
`Assoc json
in
let kinds =
LocalTaintDomain.get LocalTaintDomain.Slots.Kinds local_taint
|> KindTaintDomain.to_alist
|> List.map ~f:add_kind
in
let json = cons_if_non_empty "kinds" kinds json in
let json =
let interval = LocalTaintDomain.get LocalTaintDomain.Slots.ClassInterval local_taint in
if Interprocedural.ClassInterval.is_top interval then
json
else
let class_interval =
[
"lower", `Int (Interprocedural.ClassInterval.lower_bound_exn interval);
"upper", `Int (Interprocedural.ClassInterval.upper_bound_exn interval);
]
in
let class_interval = `Assoc class_interval in
cons_if_non_empty "class_interval" [class_interval] json
in
`Assoc json
in
(* Expand overrides into actual callables and dedup *)
let taint = Map.transform Key Map ~f:CallInfo.expand_call_site taint in
let elements = Map.to_alist taint |> List.map ~f:trace_to_json in
`List elements
let to_json = create_json ~call_info_to_json:CallInfo.to_json
let to_external_json ~filename_lookup =
create_json ~call_info_to_json:(CallInfo.to_external_json ~filename_lookup)
let add_local_breadcrumbs breadcrumbs taint =
let apply_local_taint local_taint =
let breadcrumbs =
LocalTaintDomain.get LocalTaintDomain.Slots.Breadcrumb local_taint
|> Features.BreadcrumbSet.add_set ~to_add:breadcrumbs
in
LocalTaintDomain.update LocalTaintDomain.Slots.Breadcrumb breadcrumbs local_taint
in
transform LocalTaintDomain.Self Map ~f:apply_local_taint taint
let add_local_breadcrumb breadcrumb taint =
add_local_breadcrumbs (Features.BreadcrumbSet.singleton breadcrumb) taint
let add_local_first_index index taint =
let apply_local_taint local_taint =
let first_indices =
LocalTaintDomain.get LocalTaintDomain.Slots.FirstIndex local_taint
|> Features.FirstIndexSet.add_first index
in
LocalTaintDomain.update LocalTaintDomain.Slots.FirstIndex first_indices local_taint
in
transform LocalTaintDomain.Self Map ~f:apply_local_taint taint
let add_local_first_field attribute taint =
let apply_local_taint local_taint =
let first_fields =
LocalTaintDomain.get LocalTaintDomain.Slots.FirstField local_taint
|> Features.FirstFieldSet.add_first attribute
in
LocalTaintDomain.update LocalTaintDomain.Slots.FirstField first_fields local_taint
in
transform LocalTaintDomain.Self Map ~f:apply_local_taint taint
let get_features ~frame_slot ~local_slot ~bottom ~join ~sequence_join taint =
let local_taint_features local_taint sofar =
let frame_features frame sofar = Frame.get frame_slot frame |> join sofar in
let features = LocalTaintDomain.fold Frame.Self local_taint ~init:bottom ~f:frame_features in
let features = LocalTaintDomain.get local_slot local_taint |> sequence_join features in
join sofar features
in
fold LocalTaintDomain.Self ~f:local_taint_features ~init:bottom taint
let accumulated_breadcrumbs taint =
get_features
~frame_slot:Frame.Slots.Breadcrumb
~local_slot:LocalTaintDomain.Slots.Breadcrumb
~bottom:Features.BreadcrumbSet.bottom
~join:Features.BreadcrumbSet.sequence_join
~sequence_join:Features.BreadcrumbSet.sequence_join
taint
let joined_breadcrumbs taint =
get_features
~frame_slot:Frame.Slots.Breadcrumb
~local_slot:LocalTaintDomain.Slots.Breadcrumb
~bottom:Features.BreadcrumbSet.bottom
~join:Features.BreadcrumbSet.join
~sequence_join:Features.BreadcrumbSet.sequence_join
taint
let get_first ~frame_slot ~local_slot ~bottom ~join ~sequence_join taint =
let local_taint_first local_taint sofar =
let local_first = LocalTaintDomain.get local_slot local_taint in
let frame_first frame sofar =
Frame.get frame_slot frame |> sequence_join local_first |> join sofar
in
LocalTaintDomain.fold Frame.Self local_taint ~init:sofar ~f:frame_first
in
fold LocalTaintDomain.Self ~f:local_taint_first ~init:bottom taint
let first_indices taint =
get_first
~frame_slot:Frame.Slots.FirstIndex
~local_slot:LocalTaintDomain.Slots.FirstIndex
~bottom:Features.FirstIndexSet.bottom
~join:Features.FirstIndexSet.join
~sequence_join:Features.FirstIndexSet.sequence_join
taint
let first_fields taint =
get_first
~frame_slot:Frame.Slots.FirstField
~local_slot:LocalTaintDomain.Slots.FirstField
~bottom:Features.FirstFieldSet.bottom
~join:Features.FirstFieldSet.join
~sequence_join:Features.FirstFieldSet.sequence_join
taint
let via_features taint =
fold
Features.ViaFeatureSet.Self
~f:Features.ViaFeatureSet.join
~init:Features.ViaFeatureSet.bottom
taint
let transform_on_widening_collapse taint =
(* using an always-feature here would break the widening invariant: a <= a widen b *)
let open Features in
let broadening =
BreadcrumbSet.of_approximation
[
{ element = Features.broadening (); in_under = false };
{ element = Features.issue_broadening (); in_under = false };
]
in
add_local_breadcrumbs broadening taint
let prune_maximum_length maximum_length =
let filter_flow (_, frame) =
let length = Frame.get Frame.Slots.TraceLength frame in
TraceLength.is_bottom length || TraceLength.less_or_equal ~left:maximum_length ~right:length
in
transform KindTaintDomain.KeyValue Filter ~f:filter_flow
type kind_set = Kind.Set.t
let is_empty_kind_set = Kind.Set.is_empty
let sanitize sanitized_kinds taint =
if Kind.Set.is_empty sanitized_kinds then
taint
else
transform
KindTaintDomain.Key
Filter
~f:(fun kind ->
let kind = kind |> Kind.discard_transforms |> Kind.discard_subkind in
not (Kind.Set.mem kind sanitized_kinds))
taint
let apply_sanitize_transforms transforms taint =
if SanitizeTransform.Set.is_empty transforms then
taint
else
transform KindTaintDomain.Key Map ~f:(Kind.apply_sanitize_transforms transforms) taint
let apply_sanitize_sink_transforms transforms taint =
if SanitizeTransform.Set.is_empty transforms then
taint
else
transform KindTaintDomain.Key Map ~f:(Kind.apply_sanitize_sink_transforms transforms) taint
let apply_named_transforms transforms taint =
if List.is_empty transforms then
taint
else
transform KindTaintDomain.Key Map ~f:(Kind.apply_named_transforms transforms) taint
let apply_call
~resolution
~location
~callee
~arguments
~port
~path
~element:taint
~is_self_call
~caller_class_interval
~receiver_class_interval
=
let callees =
match callee with
| Some callee -> [callee]
| None -> []
in
let apply (call_info, local_taint) =
let local_taint =
local_taint
|> LocalTaintDomain.transform KindTaintDomain.Key Filter ~f:(fun kind ->
not (Kind.ignore_kind_at_call kind))
|> LocalTaintDomain.transform KindTaintDomain.Key Map ~f:Kind.apply_call
in
let via_features_breadcrumbs =
LocalTaintDomain.fold
Features.ViaFeatureSet.Element
~f:Features.ViaFeatureSet.add
~init:Features.ViaFeatureSet.bottom
local_taint
|> Features.expand_via_features ~resolution ~callees ~arguments
in
let local_breadcrumbs = LocalTaintDomain.get LocalTaintDomain.Slots.Breadcrumb local_taint in
let local_first_indices =
LocalTaintDomain.get LocalTaintDomain.Slots.FirstIndex local_taint
in
let local_first_fields = LocalTaintDomain.get LocalTaintDomain.Slots.FirstField local_taint in
let local_taint =
local_taint
|> LocalTaintDomain.update
LocalTaintDomain.Slots.TitoPosition
Features.TitoPositionSet.bottom
|> LocalTaintDomain.update LocalTaintDomain.Slots.Breadcrumb via_features_breadcrumbs
|> LocalTaintDomain.update LocalTaintDomain.Slots.FirstIndex Features.FirstIndexSet.bottom
|> LocalTaintDomain.update LocalTaintDomain.Slots.FirstField Features.FirstFieldSet.bottom
in
let apply_frame frame =
frame
|> Frame.update Frame.Slots.ViaFeature Features.ViaFeatureSet.bottom
|> Frame.transform
Features.BreadcrumbSet.Self
Map
~f:(Features.BreadcrumbSet.sequence_join local_breadcrumbs)
|> Frame.transform
Features.FirstIndexSet.Self
Map
~f:(Features.FirstIndexSet.sequence_join local_first_indices)
|> Frame.transform
Features.FirstFieldSet.Self
Map
~f:(Features.FirstFieldSet.sequence_join local_first_fields)
in
let local_taint = LocalTaintDomain.transform Frame.Self Map ~f:apply_frame local_taint in
let local_taint =
match callee with
| None
| Some (`Object _)
| Some (`Function _) ->
LocalTaintDomain.update
LocalTaintDomain.Slots.ClassInterval
caller_class_interval
local_taint
| Some (`Method _)
| Some (`OverrideTarget _) ->
let open Interprocedural in
let old_interval =
LocalTaintDomain.get LocalTaintDomain.Slots.ClassInterval local_taint
in
if is_self_call then
let new_interval = ClassInterval.meet old_interval caller_class_interval in
LocalTaintDomain.update LocalTaintDomain.Slots.ClassInterval new_interval local_taint
else
let new_interval = ClassInterval.meet old_interval receiver_class_interval in
if ClassInterval.is_empty new_interval then
LocalTaintDomain.bottom
else
LocalTaintDomain.update
LocalTaintDomain.Slots.ClassInterval
caller_class_interval
local_taint
in
match call_info with
| CallInfo.Origin _
| CallInfo.CallSite _ ->
let increase_length n = if n < max_int then n + 1 else n in
let call_info = CallInfo.CallSite { location; callees; port; path } in
let local_taint =
local_taint |> LocalTaintDomain.transform TraceLength.Self Map ~f:increase_length
in
call_info, local_taint
| CallInfo.Declaration { leaf_name_provided } ->
let call_info = CallInfo.Origin location in
let new_leaf_names =
if leaf_name_provided then
Features.LeafNameSet.bottom
else
let open Features in
let make_leaf_name callee =
LeafName.{ leaf = Interprocedural.Target.external_target_name callee; port = None }
|> LeafNameInterned.intern
in
List.map ~f:make_leaf_name callees |> Features.LeafNameSet.of_list
in
let local_taint =
LocalTaintDomain.transform Features.LeafNameSet.Self Add ~f:new_leaf_names local_taint
in
call_info, local_taint
in
Map.transform Map.KeyValue Map ~f:apply taint
let essential ~return_access_paths taint =
let apply (_, local_taint) =
let call_info = CallInfo.Declaration { leaf_name_provided = false } in
let local_taint =
local_taint
|> LocalTaintDomain.update
LocalTaintDomain.Slots.TitoPosition
Features.TitoPositionSet.bottom
|> LocalTaintDomain.update LocalTaintDomain.Slots.Breadcrumb Features.BreadcrumbSet.empty
|> LocalTaintDomain.update LocalTaintDomain.Slots.FirstIndex Features.FirstIndexSet.bottom
|> LocalTaintDomain.update LocalTaintDomain.Slots.FirstField Features.FirstFieldSet.bottom
in
let apply_frame frame =
frame
|> Frame.update Frame.Slots.ViaFeature Features.ViaFeatureSet.bottom
|> Frame.update Frame.Slots.Breadcrumb Features.BreadcrumbSet.bottom
|> Frame.update Frame.Slots.FirstIndex Features.FirstIndexSet.bottom
|> Frame.update Frame.Slots.FirstField Features.FirstFieldSet.bottom
|> Frame.update Frame.Slots.LeafName Features.LeafNameSet.bottom
|> Frame.transform Features.ReturnAccessPathSet.Self Map ~f:return_access_paths
in
let local_taint = LocalTaintDomain.transform Frame.Self Map ~f:apply_frame local_taint in
call_info, local_taint
in
Map.transform Map.KeyValue Map ~f:apply taint
end
module ForwardTaint = MakeTaint (Sources)
module BackwardTaint = MakeTaint (Sinks)
module MakeTaintTree (Taint : TAINT_DOMAIN) () = struct
include
Abstract.TreeDomain.Make
(struct
let max_tree_depth_after_widening () = TaintConfiguration.maximum_tree_depth_after_widening
let check_invariants = true
end)
(Taint)
()
let apply_call
~resolution
~location
~callee
~arguments
~port
~is_self_call
~caller_class_interval
~receiver_class_interval
taint_tree
=
let transform_path (path, tip) =
( path,
Taint.apply_call
~resolution
~location
~callee
~arguments
~port
~path
~element:tip
~is_self_call
~caller_class_interval
~receiver_class_interval )
in
transform Path Map ~f:transform_path taint_tree
let empty = bottom
let is_empty = is_bottom
(* Return the taint tree with only the essential structure. *)
let essential tree =
let return_access_paths _ = Features.ReturnAccessPathSet.bottom in
transform Taint.Self Map ~f:(Taint.essential ~return_access_paths) tree
let essential_for_constructor tree =
transform Taint.Self Map ~f:(Taint.essential ~return_access_paths:Fn.id) tree
let approximate_return_access_paths ~maximum_return_access_path_length tree =
let cut_off paths =
if Features.ReturnAccessPathSet.count paths > maximum_return_access_path_length then
Features.ReturnAccessPathSet.elements paths
|> Features.ReturnAccessPath.common_prefix
|> Features.ReturnAccessPathSet.singleton
else
paths
in
transform Features.ReturnAccessPathSet.Self Map ~f:cut_off tree
let prune_maximum_length maximum_length =
transform Taint.Self Map ~f:(Taint.prune_maximum_length maximum_length)
let filter_by_kind ~kind taint_tree =
taint_tree
|> transform Taint.kind Filter ~f:(Taint.equal_kind kind)
|> collapse ~transform:Fn.id
let add_local_breadcrumb breadcrumb =
transform Taint.Self Map ~f:(Taint.add_local_breadcrumb breadcrumb)
let add_local_breadcrumbs breadcrumbs taint_tree =
if Features.BreadcrumbSet.is_bottom breadcrumbs || Features.BreadcrumbSet.is_empty breadcrumbs
then
taint_tree
else
transform Taint.Self Map ~f:(Taint.add_local_breadcrumbs breadcrumbs) taint_tree
let add_local_first_index index = transform Taint.Self Map ~f:(Taint.add_local_first_index index)
let add_local_first_field attribute =
transform Taint.Self Map ~f:(Taint.add_local_first_field attribute)
let accumulated_breadcrumbs taint_tree =
let gather_breadcrumbs taint sofar =
Taint.accumulated_breadcrumbs taint |> Features.BreadcrumbSet.add_set ~to_add:sofar
in
fold Taint.Self ~f:gather_breadcrumbs ~init:Features.BreadcrumbSet.bottom taint_tree
let joined_breadcrumbs taint_tree =
let gather_breadcrumbs taint sofar =
Taint.accumulated_breadcrumbs taint |> Features.BreadcrumbSet.join sofar
in
fold Taint.Self ~f:gather_breadcrumbs ~init:Features.BreadcrumbSet.bottom taint_tree
let add_via_features via_features taint_tree =
if Features.ViaFeatureSet.is_bottom via_features then
taint_tree
else
transform Features.ViaFeatureSet.Self Add ~f:via_features taint_tree
let sanitize sanitized_kinds taint =
if Taint.is_empty_kind_set sanitized_kinds then
taint
else
transform Taint.Self Map ~f:(Taint.sanitize sanitized_kinds) taint
let apply_sanitize_transforms transforms taint =
if SanitizeTransform.Set.is_empty transforms then
taint
else
transform Taint.Self Map ~f:(Taint.apply_sanitize_transforms transforms) taint
let apply_sanitize_sink_transforms transforms taint =
if SanitizeTransform.Set.is_empty transforms then
taint
else
transform Taint.Self Map ~f:(Taint.apply_sanitize_sink_transforms transforms) taint
let apply_named_transforms transforms taint =
if List.is_empty transforms then
taint
else
transform Taint.Self Map ~f:(Taint.apply_named_transforms transforms) taint
end
module MakeTaintEnvironment (Taint : TAINT_DOMAIN) () = struct
module Tree = MakeTaintTree (Taint) ()
include
Abstract.MapDomain.Make
(struct
let name = "env"
include AccessPath.Root
let absence_implicitly_maps_to_bottom = true
end)
(Tree)
let create_json ~taint_to_json environment =
let element_to_json json_list (root, tree) =
let path_to_json (path, tip) json_list =
let port = AccessPath.create root path |> AccessPath.to_json in
(path, ["port", port; "taint", taint_to_json tip]) :: json_list
in
let ports =
Tree.fold Tree.Path ~f:path_to_json tree ~init:[]
|> List.dedup_and_sort ~compare:(fun (p1, _) (p2, _) ->
Abstract.TreeDomain.Label.compare_path p1 p2)
|> List.rev_map ~f:(fun (_, fields) -> `Assoc fields)
in
List.rev_append ports json_list
in
let paths = to_alist environment |> List.fold ~f:element_to_json ~init:[] in
`List paths
let to_json = create_json ~taint_to_json:Taint.to_json
let to_external_json ~filename_lookup =
create_json ~taint_to_json:(Taint.to_external_json ~filename_lookup)
let assign ?(weak = false) ~root ~path subtree environment =
let assign_tree = function
| None -> Tree.assign ~weak ~tree:Tree.bottom path ~subtree
| Some tree -> Tree.assign ~weak ~tree path ~subtree
in
update environment root ~f:assign_tree
let read_tree_raw
?(transform_non_leaves = fun _ e -> e)
?(use_precise_labels = false)
~root
~path
environment
=
match get_opt root environment with
| None -> Taint.bottom, Tree.bottom
| Some tree -> Tree.read_raw ~transform_non_leaves ~use_precise_labels path tree
let read ?(transform_non_leaves = fun _ e -> e) ~root ~path environment =
match get_opt root environment with
| None -> Tree.bottom
| Some tree -> Tree.read ~transform_non_leaves path tree
let empty = bottom
let is_empty = is_bottom
let roots environment = fold Key ~f:List.cons ~init:[] environment
let add_local_breadcrumb breadcrumb =
transform Taint.Self Map ~f:(Taint.add_local_breadcrumb breadcrumb)
let add_local_breadcrumbs breadcrumbs taint_tree =
if Features.BreadcrumbSet.is_bottom breadcrumbs || Features.BreadcrumbSet.is_empty breadcrumbs
then
taint_tree
else
transform Taint.Self Map ~f:(Taint.add_local_breadcrumbs breadcrumbs) taint_tree
let add_via_features via_features taint_tree =
if Features.ViaFeatureSet.is_bottom via_features then
taint_tree
else
transform Features.ViaFeatureSet.Self Add ~f:via_features taint_tree
let extract_features_to_attach ~root ~attach_to_kind taint =
let taint =
read ~root ~path:[] taint
|> Tree.transform Taint.kind Filter ~f:(Taint.equal_kind attach_to_kind)
|> Tree.collapse ~transform:Fn.id
in
Taint.accumulated_breadcrumbs taint, Taint.via_features taint
let sanitize sanitized_kinds taint =
if Taint.is_empty_kind_set sanitized_kinds then
taint
else
transform Taint.Self Map ~f:(Taint.sanitize sanitized_kinds) taint
let apply_sanitize_transforms transforms taint =
if SanitizeTransform.Set.is_empty transforms then
taint
else
transform Taint.Self Map ~f:(Taint.apply_sanitize_transforms transforms) taint
end
module ForwardState = MakeTaintEnvironment (ForwardTaint) ()
(** Used to infer which sources reach the exit points of a function. *)
module BackwardState = MakeTaintEnvironment (BackwardTaint) ()
(** Used to infer which sinks are reached from parameters, as well as the taint-in-taint-out (TITO)
using the special LocalReturn sink. *)
let local_return_frame =
Frame.create
[
Part (TraceLength.Self, 0);
Part (Features.ReturnAccessPathSet.Element, []);
Part (Features.BreadcrumbSet.Self, Features.BreadcrumbSet.empty);
]
(* Special sink as it needs the return access path *)
let local_return_taint = BackwardTaint.singleton Sinks.LocalReturn local_return_frame
module MakeSanitizeKinds (Kind : KIND_ARG) = struct
type set =
| All
| Specific of Kind.Set.t
[@@deriving show, eq]
include Abstract.SimpleDomain.Make (struct
type t = set option [@@deriving show]
let name = Format.sprintf "sanitize %ss" Kind.name
let bottom = None
let less_or_equal ~left ~right =
if phys_equal left right then
true
else
match left, right with
| None, _ -> true
| Some _, None -> false
| Some All, Some All -> true
| Some All, Some (Specific _) -> false
| Some (Specific _), Some All -> true
| Some (Specific left), Some (Specific right) -> Kind.Set.subset left right
let join left right =
if phys_equal left right then
left
else
match left, right with
| None, Some _ -> right
| Some _, None -> left
| Some All, _
| _, Some All ->
Some All
| Some (Specific left_sources), Some (Specific right_sources) ->
Some (Specific (Kind.Set.union left_sources right_sources))
| None, None -> None
let meet a b = if less_or_equal ~left:b ~right:a then b else a
end)
let all = Some All
let equal = [%equal: set option]
let to_json set =
let label = Format.sprintf "%ss" Kind.name in
match set with
| Some All -> [label, `String "All"]
| Some (Specific set) ->
[
( label,
let to_string name = `String name in
`List (set |> Kind.Set.elements |> List.map ~f:Kind.show |> List.map ~f:to_string) );
]
| None -> []
end
module SanitizeSources = MakeSanitizeKinds (Sources)
module SanitizeSinks = MakeSanitizeKinds (Sinks)
module SanitizeTito = struct
type set =
| All
| Specific of {
sanitized_tito_sources: Sources.Set.t;
sanitized_tito_sinks: Sinks.Set.t;
}
[@@deriving show, eq]
include Abstract.SimpleDomain.Make (struct
type t = set option [@@deriving show]
let name = "sanitize tito"
let bottom = None
let less_or_equal ~left ~right =
if phys_equal left right then
true
else
match left, right with
| None, _ -> true
| Some _, None -> false
| Some All, Some All -> true
| Some All, Some (Specific _) -> false
| Some (Specific _), Some All -> true
| Some (Specific left), Some (Specific right) ->
Sources.Set.subset left.sanitized_tito_sources right.sanitized_tito_sources
&& Sinks.Set.subset left.sanitized_tito_sinks right.sanitized_tito_sinks
let join left right =
if phys_equal left right then
left
else
match left, right with
| None, Some tito
| Some tito, None ->
Some tito
| Some All, _
| _, Some All ->
Some All
| Some (Specific left), Some (Specific right) ->
Some
(Specific
{
sanitized_tito_sources =
Sources.Set.union left.sanitized_tito_sources right.sanitized_tito_sources;
sanitized_tito_sinks =
Sinks.Set.union left.sanitized_tito_sinks right.sanitized_tito_sinks;
})
| None, None -> None
let meet a b = if less_or_equal ~left:b ~right:a then b else a
end)
let all = Some All
let equal = [%equal: set option]
let to_json set =
let to_string name = `String name in
let sources_to_json sources =
`List (sources |> Sources.Set.elements |> List.map ~f:Sources.show |> List.map ~f:to_string)
in
let sinks_to_json sinks =
`List (sinks |> Sinks.Set.elements |> List.map ~f:Sinks.show |> List.map ~f:to_string)
in
match set with
| Some All -> ["tito", `String "All"]
| Some (Specific { sanitized_tito_sources; sanitized_tito_sinks }) ->
[
"tito_sources", sources_to_json sanitized_tito_sources;
"tito_sinks", sinks_to_json sanitized_tito_sinks;
]
| None -> []
end
module Sanitize = struct
type sanitize = {
sources: SanitizeSources.t;
sinks: SanitizeSinks.t;
tito: SanitizeTito.t;
}
[@@deriving show, eq]
include Abstract.SimpleDomain.Make (struct
type t = sanitize
let name = "sanitize"
let bottom =
{ sources = SanitizeSources.bottom; sinks = SanitizeSinks.bottom; tito = SanitizeTito.bottom }
let less_or_equal ~left ~right =
if phys_equal left right then
true
else
SanitizeSources.less_or_equal ~left:left.sources ~right:right.sources
&& SanitizeSinks.less_or_equal ~left:left.sinks ~right:right.sinks
&& SanitizeTito.less_or_equal ~left:left.tito ~right:right.tito
let join left right =
if phys_equal left right then
left
else
let sources = SanitizeSources.join left.sources right.sources in
let sinks = SanitizeSinks.join left.sinks right.sinks in
let tito = SanitizeTito.join left.tito right.tito in
{ sources; sinks; tito }
let meet a b = if less_or_equal ~left:b ~right:a then b else a
let show = show_sanitize
end)
let all = { sources = SanitizeSources.all; sinks = SanitizeSinks.all; tito = SanitizeTito.all }
let empty = bottom
let is_empty = is_bottom
let equal = equal_sanitize
let to_json { sources; sinks; tito } =
let sources_json = SanitizeSources.to_json sources in
let sinks_json = SanitizeSinks.to_json sinks in
let tito_json = SanitizeTito.to_json tito in
`Assoc (sources_json @ sinks_json @ tito_json)
end
(** Map from parameters or return value to a sanitizer. *)
module SanitizeRootMap = struct
include
Abstract.MapDomain.Make
(struct
let name = "sanitize"
include AccessPath.Root
let absence_implicitly_maps_to_bottom = true
end)
(Sanitize)
let roots map = fold Key ~f:List.cons ~init:[] map
let to_json map =
map
|> to_alist
|> List.map ~f:(fun (root, sanitize) ->
let (`Assoc fields) = Sanitize.to_json sanitize in
let port = AccessPath.create root [] |> AccessPath.to_json in
`Assoc (("port", port) :: fields))
|> fun elements -> `List elements
end