source/interprocedural_analyses/taint/sinks.ml (184 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
let name = "sink"
module T = struct
type partial_sink = {
kind: string;
label: string;
}
[@@deriving compare, hash, sexp, show]
type t =
| Attach
| PartialSink of partial_sink
| TriggeredPartialSink of partial_sink
| LocalReturn (* Special marker to describe function in-out behavior *)
| NamedSink of string
| ParametricSink of {
sink_name: string;
subkind: string;
}
| ParameterUpdate of int (* Special marker to describe side effect in-out behavior *)
| AddFeatureToArgument
(* Special marker to designate modifying the state the parameter passed in. *)
| Transform of {
(* Invariant: concatenation of local @ global is non-empty. *)
local: TaintTransforms.t;
global: TaintTransforms.t;
(* Invariant: not a transform. *)
base: t;
}
[@@deriving compare, hash, sexp]
let rec pp formatter = function
| Attach -> Format.fprintf formatter "Attach"
| PartialSink { kind; label } -> Format.fprintf formatter "PartialSink[%s[%s]]" kind label
| TriggeredPartialSink { kind; label } ->
Format.fprintf formatter "TriggeredPartialSink[%s[%s]]" kind label
| LocalReturn -> Format.fprintf formatter "LocalReturn"
| NamedSink name -> Format.fprintf formatter "%s" name
| ParametricSink { sink_name; subkind } -> Format.fprintf formatter "%s[%s]" sink_name subkind
| ParameterUpdate index -> Format.fprintf formatter "ParameterUpdate%d" index
| AddFeatureToArgument -> Format.fprintf formatter "AddFeatureToArgument"
| Transform { local; global; base } ->
TaintTransforms.pp_kind ~formatter ~pp_base:pp ~local ~global ~base
let equal = [%compare.equal: t]
let show = Format.asprintf "%a" pp
end
include T
let ignore_kind_at_call = function
| Attach -> true
| _ -> false
let apply_call = function
| Transform { local; global; base } ->
Transform
{ local = TaintTransforms.empty; global = TaintTransforms.merge ~local ~global; base }
| sink -> sink
module Set = struct
include Stdlib.Set.Make (struct
include T
end)
let show set =
set |> elements |> List.map ~f:T.show |> String.concat ~sep:", " |> Format.asprintf "[%s]"
let pp format set = Format.fprintf format "%s" (show set)
let to_sanitize_transforms_exn set =
let to_transform = function
| NamedSink name -> SanitizeTransform.NamedSink name
| sink -> Format.asprintf "cannot sanitize the sink `%a`" T.pp sink |> failwith
in
set |> elements |> List.map ~f:to_transform |> SanitizeTransform.Set.of_list
let is_singleton set =
(* The only way to implement this in O(1) is with `for_all` or `exists`. *)
(not (is_empty set))
&&
let count = ref 0 in
for_all
(fun _ ->
incr count;
!count = 1)
set
let as_singleton set = if is_singleton set then Some (choose set) else None
end
module Map = struct
include Stdlib.Map.Make (struct
include T
end)
let of_alist_exn =
let add map (key, data) =
update
key
(function
| None -> Some data
| Some _ -> failwith "key already exists")
map
in
List.fold ~init:empty ~f:add
let to_alist map =
let gather key data sofar = (key, data) :: sofar in
fold gather map []
end
let discard_subkind = function
| ParametricSink { sink_name; _ } -> NamedSink sink_name
| sink -> sink
let discard_transforms = function
(* TODO(T90698159): Assumes only sanitizing transforms present, revisit. *)
| Transform { base; _ } -> base
| sink -> sink
let discard_sanitize_transforms = function
| Transform { base; local; global } ->
let local = TaintTransforms.discard_sanitize_transforms local in
let global = TaintTransforms.discard_sanitize_transforms global in
if TaintTransforms.is_empty local && TaintTransforms.is_empty global then
base
else
Transform { base; local; global }
| sink -> sink
let extract_sanitized_sinks_from_transforms transforms =
let extract transform sinks =
match transform with
| SanitizeTransform.NamedSink name -> Set.add (NamedSink name) sinks
| _ -> sinks
in
SanitizeTransform.Set.fold extract transforms Set.empty
let extract_sanitize_transforms = function
| Transform { local; global; _ } ->
TaintTransforms.merge ~local ~global |> TaintTransforms.get_sanitize_transforms
| _ -> SanitizeTransform.Set.empty
let rec extract_partial_sink = function
| Transform { base; _ } -> extract_partial_sink base
| PartialSink { kind; label } -> Some { kind; label }
| _ -> None
let apply_sanitize_transforms transforms sink =
match sink with
| Attach
| AddFeatureToArgument ->
sink
| PartialSink _
| TriggeredPartialSink _
| LocalReturn
| NamedSink _
| ParametricSink _
| ParameterUpdate _ ->
Transform
{
local = TaintTransforms.of_sanitize_transforms transforms;
global = TaintTransforms.empty;
base = sink;
}
| Transform { local; global; base } ->
let transforms = SanitizeTransform.Set.diff transforms (extract_sanitize_transforms sink) in
Transform { local = TaintTransforms.add_sanitize_transforms local transforms; global; base }
let apply_sanitize_sink_transforms transforms sink =
match sink with
| LocalReturn
| Transform { base = LocalReturn; _ } ->
apply_sanitize_transforms transforms sink
| _ -> sink
let apply_named_transforms transforms sink =
match sink with
| Attach
| AddFeatureToArgument ->
sink
| PartialSink _
| TriggeredPartialSink _
| LocalReturn
| NamedSink _
| ParametricSink _
| ParameterUpdate _ ->
Transform
{
local = TaintTransforms.of_named_transforms transforms;
global = TaintTransforms.empty;
base = sink;
}
| Transform { local; global; base } ->
Transform { local = TaintTransforms.add_named_transforms local transforms; global; base }
let get_named_transforms = function
| Transform { local; global; _ } ->
TaintTransforms.merge ~local ~global |> TaintTransforms.get_named_transforms
| _ -> []
let contains_sanitize_transform sink sanitize_transform =
SanitizeTransform.Set.mem sanitize_transform (extract_sanitize_transforms sink)