source/interprocedural_analyses/taint/modelParser.ml (3,135 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 open Analysis open Expression open Pyre open PyreParser open Interprocedural open Statement open Domains module Internal = struct type breadcrumbs = Features.Breadcrumb.t list [@@deriving show, compare] type via_features = Features.ViaFeature.t list [@@deriving show, compare] type leaf_kind = | Leaf of { name: string; subkind: string option; } | Breadcrumbs of breadcrumbs | ViaFeatures of via_features [@@deriving show, compare] type sanitize_annotation = | AllSources | SpecificSource of Sources.t | AllSinks | SpecificSink of Sinks.t | AllTito | SpecificTito of { sources: Sources.t list; sinks: Sinks.t list; } [@@deriving show, compare] type taint_annotation = | Sink of { sink: Sinks.t; breadcrumbs: breadcrumbs; via_features: via_features; path: Abstract.TreeDomain.Label.path; leaf_names: Features.LeafName.t list; leaf_name_provided: bool; trace_length: int option; } | Source of { source: Sources.t; breadcrumbs: breadcrumbs; via_features: via_features; path: Abstract.TreeDomain.Label.path; leaf_names: Features.LeafName.t list; leaf_name_provided: bool; trace_length: int option; } | Tito of { tito: Sinks.t; breadcrumbs: breadcrumbs; via_features: via_features; path: Abstract.TreeDomain.Label.path; } | AddFeatureToArgument of { breadcrumbs: breadcrumbs; via_features: via_features; path: Abstract.TreeDomain.Label.path; } | Sanitize of sanitize_annotation list [@@deriving show, compare] type annotation_kind = | ParameterAnnotation of AccessPath.Root.t | ReturnAnnotation [@@deriving show, compare] module ModelQuery = struct type name_constraint = | Equals of string | Matches of Re2.t [@@deriving compare] let pp_name_constraint formatter name_constraint = match name_constraint with | Equals equals -> Format.fprintf formatter "Equals(%s)" equals | Matches regular_expression -> Format.fprintf formatter "Matches(%s)" (Re2.to_string regular_expression) let show_name_constraint = Format.asprintf "%a" pp_name_constraint type annotation_constraint = | IsAnnotatedTypeConstraint | AnnotationNameConstraint of name_constraint [@@deriving compare, show] module ArgumentsConstraint = struct type t = | Equals of Ast.Expression.Call.Argument.t list | Contains of Ast.Expression.Call.Argument.t list [@@deriving compare, show] end module ParameterConstraint = struct type t = | AnnotationConstraint of annotation_constraint | NameConstraint of name_constraint | IndexConstraint of int | AnyOf of t list | AllOf of t list | Not of t [@@deriving compare, show] end type class_constraint = | NameSatisfies of name_constraint | Extends of { class_name: string; is_transitive: bool; } [@@deriving compare] let pp_class_constraint formatter class_constraint = match class_constraint with | NameSatisfies name_constraint -> Format.fprintf formatter "NameSatisfies(%s)" (show_name_constraint name_constraint) | Extends { class_name; is_transitive } -> Format.fprintf formatter "Extends(%s, is_transitive=%b)" class_name is_transitive let show_class_constraint = Format.asprintf "%a" pp_class_constraint type model_constraint = | NameConstraint of name_constraint | AnnotationConstraint of annotation_constraint | ReturnConstraint of annotation_constraint | AnyParameterConstraint of ParameterConstraint.t | AnyOf of model_constraint list | AllOf of model_constraint list | ParentConstraint of class_constraint | DecoratorConstraint of { name_constraint: name_constraint; arguments_constraint: ArgumentsConstraint.t option; } | Not of model_constraint [@@deriving compare, show] type kind = | FunctionModel | MethodModel | AttributeModel [@@deriving show, compare] type produced_taint = | TaintAnnotation of taint_annotation | ParametricSourceFromAnnotation of { source_pattern: string; kind: string; } | ParametricSinkFromAnnotation of { sink_pattern: string; kind: string; } [@@deriving show, compare] type production = | AllParametersTaint of { excludes: string list; taint: produced_taint list; } | NamedParameterTaint of { name: string; taint: produced_taint list; } | PositionalParameterTaint of { index: int; taint: produced_taint list; } | ParameterTaint of { where: ParameterConstraint.t list; taint: produced_taint list; } | ReturnTaint of produced_taint list | AttributeTaint of produced_taint list [@@deriving show, compare] type rule = { query: model_constraint list; productions: production list; rule_kind: kind; name: string option; } [@@deriving show, compare] end type parse_result = { models: Model.t Interprocedural.Target.Map.t; queries: ModelQuery.rule list; skip_overrides: Reference.Set.t; errors: ModelVerificationError.t list; } end include Internal let model_verification_error ~path ~location kind = { ModelVerificationError.kind; path; location } module ClassDefinitionsCache = ModelVerifier.ClassDefinitionsCache (* We don't have real models for attributes, so we make a fake callable model with a 'parameter' $global which acts as the taint sink whenever attributes are marked as sinks. *) let attribute_symbolic_parameter = "$global" let decorators = String.Set.union Recognized.property_decorators Recognized.classproperty_decorators let is_property define = String.Set.exists decorators ~f:(Define.has_decorator define) let signature_is_property signature = String.Set.exists decorators ~f:(Define.Signature.has_decorator signature) (* Return `X` if the expression is of the form `X[Y]`, otherwise `None`. *) let base_name expression = match expression with | { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier identifier); _ }; attribute = "__getitem__"; special = true; }); _; } -> Some identifier | _ -> None let rec parse_annotations ~path ~location ~model_name ~configuration ~parameters ~callable_parameter_names_to_positions ?(is_object_target = false) ?(is_model_query = false) annotation = let open Core.Result in let annotation_error reason = model_verification_error ~path ~location (InvalidTaintAnnotation { taint_annotation = annotation; reason }) in let get_parameter_position name = let callable_parameter_names_to_positions = Option.value ~default:String.Map.empty callable_parameter_names_to_positions in match Map.find callable_parameter_names_to_positions name with | Some (position :: _) -> Ok position | Some [] | None -> ( (* `callable_parameter_names_to_positions` might be missing the `self` parameter. *) let matches_parameter_name index { Node.value = parameter; _ } = if String.equal parameter.Parameter.name name then Some index else None in match List.find_mapi parameters ~f:matches_parameter_name with | Some index -> Ok index | None -> Error (annotation_error (Format.sprintf "No such parameter `%s`" name))) in let rec extract_breadcrumbs ?(is_dynamic = false) expression = let open TaintConfiguration in match expression.Node.value with | Expression.Name (Name.Identifier breadcrumb) -> if is_dynamic then Ok [Features.Breadcrumb.SimpleVia breadcrumb] else Features.Breadcrumb.simple_via ~allowed:configuration.features breadcrumb >>| (fun breadcrumb -> [breadcrumb]) |> map_error ~f:annotation_error | Tuple expressions -> List.map ~f:(extract_breadcrumbs ~is_dynamic) expressions |> all |> map ~f:List.concat | _ -> Error (annotation_error (Format.sprintf "Invalid expression for breadcrumb: %s" (show_expression expression.Node.value))) in let extract_subkind { Node.value = expression; _ } = match expression with | Expression.Name (Name.Identifier subkind) -> Some subkind | _ -> None in let rec extract_via_parameters expression = match expression.Node.value with | Expression.Name (Name.Identifier name) -> get_parameter_position name >>| fun position -> [AccessPath.Root.PositionalParameter { name; position; positional_only = false }] | Tuple expressions -> List.map ~f:extract_via_parameters expressions |> all >>| List.concat | Call { callee; _ } when Option.equal String.equal (base_name callee) (Some "WithTag") -> Ok [] | _ -> Error (annotation_error (Format.sprintf "Invalid expression for ViaValueOf or ViaTypeOf: %s" (show_expression expression.Node.value))) in let rec extract_via_tag expression = match expression.Node.value with | Expression.Call { callee; arguments = [ { Call.Argument.value = { Node.value = Expression.Constant (Constant.String { StringLiteral.value; _ }); _; }; _; }; ]; } when Option.equal String.equal (base_name callee) (Some "WithTag") -> Ok (Some value) | Expression.Call _ -> Error (annotation_error (Format.sprintf "Invalid expression in ViaValueOf or ViaTypeOf declaration: %s" (Expression.show expression))) | Tuple expressions -> List.map expressions ~f:extract_via_tag |> all >>| List.find_map ~f:ident | _ -> Ok None in let rec extract_names expression = match expression.Node.value with | Expression.Name (Name.Identifier name) -> Ok [name] | Tuple expressions -> List.map ~f:extract_names expressions |> all >>| List.concat | _ -> Error (annotation_error (Format.sprintf "Invalid expression name: %s" (show_expression expression.Node.value))) in let rec extract_kinds expression = match expression.Node.value with | Expression.Name (Name.Identifier "ViaTypeOf") -> if is_object_target or is_model_query then (* ViaTypeOf is treated as ViaTypeOf[$global] *) Ok [ ViaFeatures [ Features.ViaFeature.ViaTypeOf { parameter = AccessPath.Root.PositionalParameter { name = attribute_symbolic_parameter; position = 0; positional_only = false; }; tag = None; }; ]; ] else Error (annotation_error "A standalone `ViaTypeOf` without arguments can only be used in attribute or global \ models.") | Expression.Name (Name.Identifier taint_kind) -> Ok [Leaf { name = taint_kind; subkind = None }] | Name (Name.Attribute { base; _ }) -> extract_kinds base | Call { callee; arguments = { Call.Argument.value = expression; _ } :: _ } -> ( match base_name callee with | Some "Via" -> extract_breadcrumbs expression >>| fun breadcrumbs -> [Breadcrumbs breadcrumbs] | Some "ViaDynamicFeature" -> extract_breadcrumbs ~is_dynamic:true expression >>| fun breadcrumbs -> [Breadcrumbs breadcrumbs] | Some "ViaValueOf" -> extract_via_tag expression >>= fun tag -> extract_via_parameters expression >>| List.map ~f:(fun parameter -> Features.ViaFeature.ViaValueOf { parameter; tag }) >>| fun via_features -> [ViaFeatures via_features] | Some "ViaTypeOf" -> extract_via_tag expression >>= fun tag -> extract_via_parameters expression >>| List.map ~f:(fun parameter -> Features.ViaFeature.ViaTypeOf { parameter; tag }) >>| fun via_features -> [ViaFeatures via_features] | Some "Updates" -> let to_leaf name = get_parameter_position name >>| fun position -> Leaf { name = Format.sprintf "ParameterUpdate%d" position; subkind = None } in extract_names expression >>= fun names -> List.map ~f:to_leaf names |> all | _ -> let subkind = extract_subkind expression in extract_kinds callee >>| fun kinds -> List.map kinds ~f:(fun kind -> match kind with | Leaf { name; subkind = None } -> Leaf { name; subkind } | _ -> kind)) | Call { callee; _ } -> extract_kinds callee | Tuple expressions -> List.map ~f:extract_kinds expressions |> all >>| List.concat | _ -> Error (annotation_error (Format.sprintf "Invalid expression for taint kind: %s" (show_expression expression.Node.value))) in let extract_leafs expression = extract_kinds expression >>| fun leaves -> let kinds, leaves = List.partition_map ~f:(function | Leaf { name = leaf; subkind } -> Either.First (leaf, subkind) | other -> Either.Second other) leaves in let breadcrumbs, via_features = List.partition_map ~f:(function | Breadcrumbs b -> Either.First b | ViaFeatures f -> Either.Second f | _ -> failwith "impossible") leaves in kinds, List.concat breadcrumbs, List.concat via_features in let get_source_kinds expression = let open TaintConfiguration in extract_leafs expression >>= fun (kinds, breadcrumbs, via_features) -> List.map kinds ~f:(fun (kind, subkind) -> AnnotationParser.parse_source ~allowed:configuration.sources ?subkind kind >>| fun source -> Source { source; breadcrumbs; via_features; path = []; leaf_names = []; leaf_name_provided = false; trace_length = None; }) |> all |> map_error ~f:annotation_error in let get_sink_kinds expression = let open TaintConfiguration in extract_leafs expression >>= fun (kinds, breadcrumbs, via_features) -> List.map kinds ~f:(fun (kind, subkind) -> AnnotationParser.parse_sink ~allowed:configuration.sinks ?subkind kind >>| fun sink -> Sink { sink; breadcrumbs; via_features; path = []; leaf_names = []; leaf_name_provided = false; trace_length = None; }) |> all |> map_error ~f:annotation_error in let get_taint_in_taint_out expression = let open TaintConfiguration in extract_leafs expression >>= fun (kinds, breadcrumbs, via_features) -> match kinds with | [] -> Ok [Tito { tito = Sinks.LocalReturn; breadcrumbs; via_features; path = [] }] | _ -> List.map kinds ~f:(fun (kind, subkind) -> AnnotationParser.parse_tito ~allowed_transforms:configuration.transforms ?subkind kind >>| fun tito -> Tito { tito; breadcrumbs; via_features; path = [] }) |> all |> map_error ~f:annotation_error in let extract_attach_features ~name expression = let keep_features = function | Breadcrumbs breadcrumbs -> Some (Either.First breadcrumbs) | ViaFeatures via_features -> Some (Either.Second via_features) | _ -> None in (* Ensure AttachToX annotations don't have any non-Via annotations for now. *) extract_kinds expression >>| List.map ~f:keep_features >>| Option.all >>| Option.map ~f:(List.partition_map ~f:Fn.id) >>= function | Some (breadcrumbs, via_features) -> Ok (List.concat breadcrumbs, List.concat via_features) | None -> Error (annotation_error (Format.sprintf "All parameters to `%s` must be of the form `Via[feature]`." name)) in let invalid_annotation_error () = Error (annotation_error "Failed to parse the given taint annotation.") in let rec parse_annotation = function | Expression.Call { callee; arguments = { Call.Argument.value = { Node.value = Expression.Tuple [{ Node.value = index; _ }; { Node.value = expression; _ }]; _; }; _; } :: _; } when [%compare.equal: string option] (base_name callee) (Some "AppliesTo") -> let field = match index with | Expression.Constant (Constant.Integer index) -> Ok (Abstract.TreeDomain.Label.create_int_index index) | Expression.Constant (Constant.String { StringLiteral.value = index; _ }) -> Ok (Abstract.TreeDomain.Label.create_name_index index) | _ -> Error (annotation_error "Expected either integer or string as index in AppliesTo annotation.") in let extend_path field = function | Sink ({ path; _ } as sink) -> Ok (Sink { sink with path = field :: path }) | Source ({ path; _ } as source) -> Ok (Source { source with path = field :: path }) | Tito ({ path; _ } as tito) -> Ok (Tito { tito with path = field :: path }) | AddFeatureToArgument ({ path; _ } as add_feature_to_argument) -> Ok (AddFeatureToArgument { add_feature_to_argument with path = field :: path }) | Sanitize _ -> Error (annotation_error "`AppliesTo[Sanitize[...]]` is not supported.") in field >>= fun field -> parse_annotation expression >>= fun annotations -> List.map ~f:(extend_path field) annotations |> all | Call { callee; arguments } when [%compare.equal: string option] (base_name callee) (Some "CrossRepositoryTaint") -> ( let required_arguments, optional_arguments = match arguments with | [ { Call.Argument.value = { Node.value = Expression.Tuple ({ Node.value = taint; _ } :: { Node.value = Expression.Constant (Constant.String { StringLiteral.value = canonical_name; _ }); _; } :: { Node.value = Expression.Constant (Constant.String { StringLiteral.value = canonical_port; _ }); _; } :: { Node.value = Expression.Constant (Constant.Integer producer_id); _ } :: remaining_arguments); _; }; _; }; ] -> Some (taint, canonical_name, canonical_port, producer_id), remaining_arguments | _ -> None, [] in let optional_arguments = match optional_arguments with | [{ Node.value = Expression.Constant (Constant.Integer trace_length); _ }] -> Some trace_length | [] -> Some 0 | _ -> None in match required_arguments, optional_arguments with | Some (taint, canonical_name, canonical_port, producer_id), Some trace_length -> let add_cross_repository_information annotation = let leaf_name = Features.LeafName. { leaf = canonical_name; port = Some (Format.sprintf "producer:%d:%s" producer_id canonical_port); } in match annotation with | Source source -> Source { source with leaf_names = leaf_name :: source.leaf_names; leaf_name_provided = true; trace_length = Option.merge ~f:min source.trace_length (Some trace_length); } | Sink sink -> Sink { sink with leaf_names = leaf_name :: sink.leaf_names; leaf_name_provided = true; trace_length = Option.merge ~f:min sink.trace_length (Some trace_length); } | _ -> annotation in parse_annotation taint |> map ~f:(List.map ~f:add_cross_repository_information) | _ -> Error (annotation_error "Cross repository taint must be of the form CrossRepositoryTaint[taint, \ canonical_name, canonical_port, producer_id, trace_length].")) | Call { callee; arguments } when [%compare.equal: string option] (base_name callee) (Some "CrossRepositoryTaintAnchor") -> ( match arguments with | [ { Call.Argument.value = { Node.value = Expression.Tuple [ { Node.value = taint; _ }; { Node.value = Expression.Constant (Constant.String { StringLiteral.value = canonical_name; _ }); _; }; { Node.value = Expression.Constant (Constant.String { StringLiteral.value = canonical_port; _ }); _; }; ]; _; }; _; }; ] -> let add_cross_repository_information annotation = let leaf_name = Features.LeafName. { leaf = canonical_name; port = Some (Format.sprintf "anchor:%s" canonical_port) } in match annotation with | Source source -> Source { source with leaf_names = leaf_name :: source.leaf_names; leaf_name_provided = true; } | Sink sink -> Sink { sink with leaf_names = leaf_name :: sink.leaf_names; leaf_name_provided = true; } | _ -> annotation in parse_annotation taint |> map ~f:(List.map ~f:add_cross_repository_information) | _ -> Error (annotation_error "Cross repository taint anchor must be of the form \ CrossRepositoryTaintAnchor[taint, canonical_name, canonical_port].")) | Name (Name.Identifier "Sanitize") -> Ok [Sanitize [AllSources; AllSinks; AllTito]] | Call { callee; arguments } when [%compare.equal: string option] (base_name callee) (Some "Sanitize") -> let parse_argument { Call.Argument.value = { Node.value = expression; _ }; _ } = parse_sanitize_annotation expression in List.map ~f:parse_argument arguments |> all >>| fun annotations -> [Sanitize (List.concat annotations)] | Call { callee; arguments = [{ Call.Argument.value = { value = Tuple expressions; _ }; _ }] } when [%compare.equal: string option] (base_name callee) (Some "Union") -> List.map expressions ~f:(fun expression -> parse_annotations ~path ~location:expression.Node.location ~model_name ~configuration ~parameters ~callable_parameter_names_to_positions ~is_object_target expression) |> all |> map ~f:List.concat | Call { callee; arguments = [{ Call.Argument.value = expression; _ }] } -> ( let open Core.Result in match base_name callee with | Some "TaintSink" -> get_sink_kinds expression | Some "TaintSource" -> get_source_kinds expression | Some "TaintInTaintOut" -> get_taint_in_taint_out expression | Some "AddFeatureToArgument" -> extract_leafs expression >>| fun (_, breadcrumbs, via_features) -> [AddFeatureToArgument { breadcrumbs; via_features; path = [] }] | Some "AttachToSink" -> extract_attach_features ~name:"AttachToSink" expression >>| fun (breadcrumbs, via_features) -> [ Sink { sink = Sinks.Attach; breadcrumbs; via_features; path = []; leaf_names = []; leaf_name_provided = false; trace_length = None; }; ] | Some "AttachToTito" -> extract_attach_features ~name:"AttachToTito" expression >>| fun (breadcrumbs, via_features) -> [Tito { tito = Sinks.Attach; breadcrumbs; via_features; path = [] }] | Some "AttachToSource" -> extract_attach_features ~name:"AttachToSource" expression >>| fun (breadcrumbs, via_features) -> [ Source { source = Sources.Attach; breadcrumbs; via_features; path = []; leaf_names = []; leaf_name_provided = false; trace_length = None; }; ] | Some "PartialSink" -> let partial_sink = match Node.value expression with | Call { callee = { Node.value = Name (Name.Attribute { base = { Node.value = Expression.Name (Name.Identifier kind); _ }; attribute = "__getitem__"; _; }); _; }; arguments = [ { Call.Argument.value = { Node.value = Name (Name.Identifier label); _ }; _ }; ]; } -> if not (String.Map.Tree.mem configuration.partial_sink_labels kind) then Error (annotation_error (Format.asprintf "Unrecognized partial sink `%s`." kind)) else let label_options = String.Map.Tree.find_exn configuration.partial_sink_labels kind in if not (List.mem label_options label ~equal:String.equal) then Error (annotation_error (Format.sprintf "Unrecognized label `%s` for partial sink `%s` (choices: `%s`)" label kind (String.concat label_options ~sep:", "))) else Ok (Sinks.PartialSink { kind; label }) | _ -> invalid_annotation_error () in partial_sink >>| fun partial_sink -> [ Sink { sink = partial_sink; breadcrumbs = []; via_features = []; path = []; leaf_names = []; leaf_name_provided = false; trace_length = None; }; ] | _ -> invalid_annotation_error ()) | Name (Name.Identifier "TaintInTaintOut") -> Ok [Tito { tito = Sinks.LocalReturn; breadcrumbs = []; via_features = []; path = [] }] | Name (Name.Identifier "ViaTypeOf") -> if is_object_target then (* Attribute annotations of the form `a: ViaTypeOf = ...` is equivalent to: TaintInTaintOut[ViaTypeOf[$global]] = ...` *) let via_features = [ Features.ViaFeature.ViaTypeOf { parameter = AccessPath.Root.PositionalParameter { name = attribute_symbolic_parameter; position = 0; positional_only = false }; tag = None; }; ] in Ok [Tito { tito = Sinks.LocalReturn; breadcrumbs = []; via_features; path = [] }] else Error (annotation_error "A standalone `ViaTypeOf` without arguments can only be used in attribute or global \ models.") | Expression.Tuple expressions -> List.map expressions ~f:(fun expression -> parse_annotations ~path ~location:expression.Node.location ~model_name ~configuration ~parameters ~callable_parameter_names_to_positions ~is_object_target expression) |> all >>| List.concat | _ -> invalid_annotation_error () and parse_sanitize_annotation = function | Expression.Tuple expressions -> List.map ~f:(fun expression -> parse_sanitize_annotation expression.Node.value) expressions |> all >>| List.concat | Expression.Call { Call.callee; arguments = [{ Call.Argument.value = { Node.value = expression; _ }; _ }] } when [%compare.equal: string option] (base_name callee) (Some "TaintInTaintOut") -> let gather_sources_sinks (sources, sinks) = function | Source { source; breadcrumbs = []; via_features = []; leaf_names = []; leaf_name_provided = false; trace_length = None; path = []; } -> Ok (source :: sources, sinks) | Sink { sink; breadcrumbs = []; via_features = []; leaf_names = []; leaf_name_provided = false; trace_length = None; path = []; } -> Ok (sources, sink :: sinks) | taint_annotation -> Error (annotation_error (Format.asprintf "`%a` is not supported within `Sanitize[TaintInTaintOut[...]]`" pp_taint_annotation taint_annotation)) in parse_annotation expression >>= List.fold_result ~init:([], []) ~f:gather_sources_sinks >>| fun (sources, sinks) -> [SpecificTito { sources; sinks }] | Expression.Name (Name.Identifier ("TaintSource" as identifier)) | Expression.Name (Name.Identifier ("TaintSink" as identifier)) -> Error (annotation_error (Format.asprintf "`Sanitize[%s]` is ambiguous here. Did you mean `Sanitize`?" identifier)) | Expression.Name (Name.Identifier "TaintInTaintOut") -> Ok [AllTito] | expression -> let to_sanitize = function | Source { source; breadcrumbs = []; via_features = []; leaf_names = []; leaf_name_provided = false; trace_length = None; path = []; } -> Ok (SpecificSource source) | Sink { sink; breadcrumbs = []; via_features = []; leaf_names = []; leaf_name_provided = false; trace_length = None; path = []; } -> Ok (SpecificSink sink) | taint_annotation -> Error (annotation_error (Format.asprintf "`%a` is not supported within `Sanitize[...]`" pp_taint_annotation taint_annotation)) in parse_annotation expression >>= fun annotations -> List.map ~f:to_sanitize annotations |> all in parse_annotation (Node.value annotation) let introduce_sink_taint ~root ~sinks_to_keep ~path ~leaf_names ~leaf_name_provided ~trace_length ({ Model.backward = { sink_taint; _ }; _ } as taint) taint_sink_kind via_features breadcrumbs = let open Core.Result in let should_keep_taint = match sinks_to_keep with | None -> true | Some sinks_to_keep -> Sinks.Set.mem taint_sink_kind sinks_to_keep in if should_keep_taint then let backward = let assign_backward_taint environment taint = BackwardState.assign ~weak:true ~root ~path taint environment in match taint_sink_kind with | Sinks.LocalReturn -> Error "Invalid TaintSink annotation `LocalReturn`" | _ -> let transform_call_information taint = if leaf_name_provided then BackwardTaint.transform BackwardTaint.call_info Map ~f:(function | CallInfo.Declaration _ -> CallInfo.Declaration { leaf_name_provided = true } | call_info -> call_info) taint else taint in let transform_trace_length taint = match trace_length with | Some trace_length -> Frame.transform TraceLength.Self Map ~f:(fun _ -> trace_length) taint | None -> taint in let leaf_names = leaf_names |> List.map ~f:Features.LeafNameInterned.intern |> Features.LeafNameSet.of_list in let breadcrumbs = Features.BreadcrumbSet.of_approximation breadcrumbs in let via_features = Features.ViaFeatureSet.of_list via_features in let leaf_taint = Frame.initial |> Frame.transform Features.LeafNameSet.Self Add ~f:leaf_names |> Frame.transform Features.BreadcrumbSet.Self Add ~f:breadcrumbs |> Frame.transform Features.ViaFeatureSet.Self Add ~f:via_features |> transform_trace_length |> BackwardTaint.singleton taint_sink_kind |> transform_call_information |> BackwardState.Tree.create_leaf in let sink_taint = assign_backward_taint sink_taint leaf_taint in Ok { taint.backward with sink_taint } in backward >>| fun backward -> { taint with backward } else Ok taint let introduce_taint_in_taint_out ~root ~path ({ Model.backward = { taint_in_taint_out; _ }; _ } as taint) taint_sink_kind via_features breadcrumbs = let open Core.Result in let backward = let assign_backward_taint environment taint = BackwardState.assign ~weak:true ~root ~path taint environment in let breadcrumbs = Features.BreadcrumbSet.of_approximation breadcrumbs in let via_features = Features.ViaFeatureSet.of_list via_features in match taint_sink_kind with | Sinks.LocalReturn | Sinks.Transform _ -> let return_taint = Domains.local_return_frame |> Frame.transform Features.BreadcrumbSet.Self Add ~f:breadcrumbs |> Frame.transform Features.ViaFeatureSet.Self Add ~f:via_features |> BackwardTaint.singleton taint_sink_kind |> BackwardState.Tree.create_leaf in let taint_in_taint_out = assign_backward_taint taint_in_taint_out return_taint in Ok { taint.backward with taint_in_taint_out } | Sinks.Attach when Features.BreadcrumbSet.is_empty breadcrumbs && Features.ViaFeatureSet.is_bottom via_features -> Error "`Attach` must be accompanied by a list of features to attach." | Sinks.ParameterUpdate _ | Sinks.Attach -> let update_taint = Frame.initial |> Frame.transform Features.BreadcrumbSet.Self Add ~f:breadcrumbs |> Frame.transform Features.ViaFeatureSet.Self Add ~f:via_features |> BackwardTaint.singleton taint_sink_kind |> BackwardState.Tree.create_leaf in let taint_in_taint_out = assign_backward_taint taint_in_taint_out update_taint in Ok { taint.backward with taint_in_taint_out } | _ -> let error = Format.asprintf "Invalid TaintInTaintOut annotation `%s`" (Sinks.show taint_sink_kind) in Error error in backward >>| fun backward -> { taint with backward } let introduce_source_taint ~root ~sources_to_keep ~path ~leaf_names ~leaf_name_provided ~trace_length ({ Model.forward = { source_taint }; _ } as taint) taint_source_kind via_features breadcrumbs = let open Core.Result in let should_keep_taint = match sources_to_keep with | None -> true | Some sources_to_keep -> Sources.Set.mem taint_source_kind sources_to_keep in if Sources.equal taint_source_kind Sources.Attach && List.is_empty breadcrumbs && List.is_empty via_features then Error "`Attach` must be accompanied by a list of features to attach." else if should_keep_taint then let breadcrumbs = Features.BreadcrumbSet.of_approximation breadcrumbs in let via_features = Features.ViaFeatureSet.of_list via_features in let source_taint = let transform_call_information taint = if leaf_name_provided then ForwardTaint.transform ForwardTaint.call_info Map ~f:(function | CallInfo.Declaration _ -> CallInfo.Declaration { leaf_name_provided = true } | call_info -> call_info) taint else taint in let transform_trace_length taint = match trace_length with | Some trace_length -> Frame.transform TraceLength.Self Map ~f:(fun _ -> trace_length) taint | None -> taint in let leaf_taint = let leaf_names = leaf_names |> List.map ~f:Features.LeafNameInterned.intern |> Features.LeafNameSet.of_list in Frame.initial |> Frame.transform Features.LeafNameSet.Self Add ~f:leaf_names |> Frame.transform Features.BreadcrumbSet.Self Add ~f:breadcrumbs |> Frame.transform Features.ViaFeatureSet.Self Add ~f:via_features |> transform_trace_length |> ForwardTaint.singleton taint_source_kind |> transform_call_information |> ForwardState.Tree.create_leaf in ForwardState.assign ~weak:true ~root ~path leaf_taint source_taint in Ok { taint with forward = { source_taint } } else Ok taint let sanitize_from_annotations annotations = let open Domains in let to_sanitize = function | AllSources -> { Sanitize.empty with sources = Some All } | SpecificSource source -> { Sanitize.empty with sources = Some (Specific (Sources.Set.singleton source)) } | AllSinks -> { Sanitize.empty with sinks = Some All } | SpecificSink sink -> { Sanitize.empty with sinks = Some (Specific (Sinks.Set.singleton sink)) } | AllTito -> { Sanitize.empty with tito = Some All } | SpecificTito { sources; sinks } -> { Sanitize.empty with tito = Some (Specific { sanitized_tito_sources = Sources.Set.of_list sources; sanitized_tito_sinks = Sinks.Set.of_list sinks; }); } in annotations |> List.map ~f:to_sanitize |> List.fold ~init:Sanitize.empty ~f:Sanitize.join let introduce_sanitize ~root model annotations = let roots = Domains.SanitizeRootMap.of_list [root, sanitize_from_annotations annotations] |> Domains.SanitizeRootMap.join model.Model.sanitizers.roots in let sanitizers = { model.sanitizers with roots } in { model with sanitizers } let parse_find_clause ~path ({ Node.value; location } as expression) = match value with | Expression.Constant (Constant.String { StringLiteral.value; _ }) -> ( match value with | "functions" -> Ok ModelQuery.FunctionModel | "methods" -> Ok ModelQuery.MethodModel | "attributes" -> Ok ModelQuery.AttributeModel | unsupported -> Error (model_verification_error ~path ~location (UnsupportedFindClause unsupported))) | _ -> Error (model_verification_error ~path ~location (InvalidFindClauseType expression)) let get_find_clause_as_string find_clause = match find_clause with | Ok ModelQuery.AttributeModel -> "attributes" | Ok ModelQuery.MethodModel -> "methods" | Ok ModelQuery.FunctionModel -> "functions" | _ -> "unsupported" let is_callable_clause_kind find_clause = match find_clause with | Ok ModelQuery.MethodModel | Ok ModelQuery.FunctionModel -> true | _ -> false let is_class_member_clause_kind find_clause = match find_clause with | Ok ModelQuery.MethodModel | Ok ModelQuery.AttributeModel -> true | _ -> false let parse_name_constraint ~path ~location ({ Node.value; _ } as constraint_expression) = match value with | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "name"); _ }; attribute = ("matches" | "equals") as attribute; _; }); _; } as callee; arguments; } -> ( match arguments with | [ { Call.Argument.value = { Node.value = Expression.Constant (Constant.String { StringLiteral.value = name_constraint; _ }); _; }; _; }; ] -> ( match attribute with | "matches" -> Ok (ModelQuery.Matches (Re2.create_exn name_constraint)) | "equals" -> Ok (ModelQuery.Equals name_constraint) | _ -> failwith "impossible case") | _ -> Error (model_verification_error ~path ~location (InvalidModelQueryClauseArguments { callee; arguments }))) | _ -> Error (model_verification_error ~path ~location (InvalidNameClause constraint_expression)) let parse_annotation_constraint ~path ~location ({ Node.value; _ } as constraint_expression) = match value with | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { attribute = ("equals" | "matches" | "is_annotated_type") as attribute; _ }); _; } as callee; arguments; } -> ( match attribute, arguments with | ( "equals", [ { Call.Argument.value = { Node.value = Expression.Constant (Constant.String { StringLiteral.value = type_name; _ }); _; }; _; }; ] ) -> Ok (ModelQuery.AnnotationNameConstraint (ModelQuery.Equals type_name)) | ( "matches", [ { Call.Argument.value = { Node.value = Expression.Constant (Constant.String { StringLiteral.value = type_name_pattern; _ }); _; }; _; }; ] ) -> Ok (ModelQuery.AnnotationNameConstraint (ModelQuery.Matches (Re2.create_exn type_name_pattern))) | "is_annotated_type", [] -> Ok ModelQuery.IsAnnotatedTypeConstraint | _ -> Error (model_verification_error ~path ~location (InvalidModelQueryClauseArguments { callee; arguments }))) | _ -> Error (model_verification_error ~path ~location (InvalidTypeAnnotationClause constraint_expression)) let parse_where_clause ~path ~find_clause ({ Node.value; location } as expression) = let open Core.Result in let invalid_model_query_where_clause ~path ~location callee = model_verification_error ~path ~location (InvalidModelQueryWhereClause { expression = callee; find_clause_kind = get_find_clause_as_string find_clause }) in let parse_arguments_constraint ({ Node.value; _ } as constraint_expression) = match value with | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "arguments"); _ }; attribute = ("contains" | "equals") as attribute; _; }); _; }; arguments; } -> ( match attribute with | "contains" -> Ok (ModelQuery.ArgumentsConstraint.Contains arguments) | "equals" -> Ok (ModelQuery.ArgumentsConstraint.Equals arguments) | _ -> failwith "impossible case") | _ -> Error (model_verification_error ~path ~location (InvalidArgumentsClause constraint_expression)) in let rec parse_constraint ({ Node.value; _ } as constraint_expression) = match value with | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "name"); _ }; _ }); _; }; _; } -> parse_name_constraint ~path ~location constraint_expression >>= fun name_constraint -> Ok (ModelQuery.NameConstraint name_constraint) | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "type_annotation"); _ }; _ }); _; } as callee; _; } -> ( match is_callable_clause_kind find_clause with | true -> Error (invalid_model_query_where_clause ~path ~location callee) | _ -> parse_annotation_constraint ~path ~location constraint_expression >>= fun annotation_constraint -> Ok (ModelQuery.AnnotationConstraint annotation_constraint)) | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Identifier "Decorator"); _ } as callee; arguments; } -> ( match arguments, is_callable_clause_kind find_clause with | _, false -> Error (invalid_model_query_where_clause ~path ~location callee) | [{ Call.Argument.name = None; Call.Argument.value = decorator_name_constraint }], _ -> parse_name_constraint ~path ~location decorator_name_constraint >>= fun name_constraint -> Ok (ModelQuery.DecoratorConstraint { name_constraint; arguments_constraint = None }) | ( [ { Call.Argument.name = None; value = first_constraint }; { Call.Argument.name = None; value = second_constraint }; ], _ ) -> ( match ( parse_name_constraint ~path ~location first_constraint, parse_arguments_constraint second_constraint ) with | Ok name_constraint, Ok arguments_constraint -> Ok (ModelQuery.DecoratorConstraint { name_constraint; arguments_constraint = Some arguments_constraint }) | _ -> parse_name_constraint ~path ~location second_constraint >>= fun name_constraint -> parse_arguments_constraint first_constraint >>= fun arguments_constraint -> Ok (ModelQuery.DecoratorConstraint { name_constraint; arguments_constraint = Some arguments_constraint })) | _ -> Error (model_verification_error ~path ~location (InvalidModelQueryClauseArguments { callee; arguments }))) | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "return_annotation"); _ }; _ }); _; } as callee; _; } -> ( match is_callable_clause_kind find_clause with | false -> Error (invalid_model_query_where_clause ~path ~location callee) | _ -> parse_annotation_constraint ~path ~location constraint_expression >>= fun annotation_constraint -> Ok (ModelQuery.ReturnConstraint annotation_constraint)) | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "any_parameter"); _ }; attribute = parameter_constraint_kind; _; }); _; }; _; }); _; } as callee; _; } -> ( match is_callable_clause_kind find_clause, parameter_constraint_kind with | true, "annotation" -> parse_annotation_constraint ~path ~location constraint_expression >>= fun parameter_constraint -> Ok (ModelQuery.AnyParameterConstraint (ModelQuery.ParameterConstraint.AnnotationConstraint parameter_constraint)) | _ -> Error (invalid_model_query_where_clause ~path ~location callee)) | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Identifier "AnyOf"); _ }; arguments = constraints; } -> List.map constraints ~f:(fun { Call.Argument.value; _ } -> parse_constraint value) |> all >>| fun constraints -> ModelQuery.AnyOf constraints | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Identifier "AllOf"); _ }; arguments = constraints; } -> List.map constraints ~f:(fun { Call.Argument.value; _ } -> parse_constraint value) |> all >>| fun constraints -> ModelQuery.AllOf constraints | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Identifier "Not"); _ }; arguments = [{ Call.Argument.value; _ }]; } -> parse_constraint value >>= fun model_constraint -> Ok (ModelQuery.Not model_constraint) | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "parent"); _ }; attribute = ("equals" | "matches") as attribute; _; }); _; } as callee; arguments; } -> ( match is_class_member_clause_kind find_clause with | false -> Error (invalid_model_query_where_clause ~path ~location callee) | _ -> ( match arguments with | [ { Call.Argument.value = { Node.value = Expression.Constant (Constant.String { StringLiteral.value = class_name; _ }); _; }; _; }; ] -> let name_constraint = match attribute with | "equals" -> ModelQuery.Equals class_name | "matches" -> ModelQuery.Matches (Re2.create_exn class_name) | _ -> failwith "impossible case" in Ok (ModelQuery.ParentConstraint (ModelQuery.NameSatisfies name_constraint)) | _ -> Error (model_verification_error ~path ~location (InvalidModelQueryClauseArguments { callee; arguments })))) | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "parent"); _ }; attribute = "extends"; _; }); _; } as callee; arguments; } -> ( match is_class_member_clause_kind find_clause with | false -> Error (invalid_model_query_where_clause ~path ~location callee) | _ -> ( match arguments with | [ { Call.Argument.value = { Node.value = Expression.Constant (Constant.String { StringLiteral.value = class_name; _ }); _; }; _; }; ] -> Ok (ModelQuery.ParentConstraint (Extends { class_name; is_transitive = false })) | [ { Call.Argument.value = { Node.value = Expression.Constant (Constant.String { StringLiteral.value = class_name; _ }); _; }; _; }; { Call.Argument.name = Some { Node.value = "is_transitive"; _ }; value = { Node.value = is_transitive_value; _ } as is_transitive_expression; }; ] -> (match is_transitive_value with | Expression.Constant Constant.True -> Ok true | Expression.Constant Constant.False -> Ok false | _ -> Error (model_verification_error ~path ~location (InvalidExtendsIsTransitive is_transitive_expression))) >>= fun is_transitive -> Ok (ModelQuery.ParentConstraint (Extends { class_name; is_transitive })) | _ -> Error (model_verification_error ~path ~location (InvalidModelQueryClauseArguments { callee; arguments })))) | Expression.Call { Call.callee; arguments = _ } -> Error (model_verification_error ~path ~location (UnsupportedCallee callee)) | _ -> Error (model_verification_error ~path ~location (UnsupportedConstraint constraint_expression)) in match value with | Expression.List items -> List.map items ~f:parse_constraint |> all | _ -> parse_constraint expression >>| List.return let parse_parameter_where_clause ~path ({ Node.value; location } as expression) = let open Core.Result in let rec parse_constraint ({ Node.value; _ } as constraint_expression) = match value with | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "name"); _ }; _ }); _; }; _; } -> parse_name_constraint ~path ~location constraint_expression >>= fun name_constraint -> Ok (ModelQuery.ParameterConstraint.NameConstraint name_constraint) | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Identifier "AnyOf"); _ }; arguments = constraints; } -> List.map constraints ~f:(fun { Call.Argument.value; _ } -> parse_constraint value) |> all >>| fun constraints -> ModelQuery.ParameterConstraint.AnyOf constraints | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Identifier "AllOf"); _ }; arguments = constraints; } -> List.map constraints ~f:(fun { Call.Argument.value; _ } -> parse_constraint value) |> all >>| fun constraints -> ModelQuery.ParameterConstraint.AllOf constraints | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Identifier "Not"); _ }; arguments = [{ Call.Argument.value; _ }]; } -> parse_constraint value >>= fun query_constraint -> Ok (ModelQuery.ParameterConstraint.Not query_constraint) | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "type_annotation"); _ }; _ }); _; }; _; } -> parse_annotation_constraint ~path ~location constraint_expression >>= fun annotation_constraint -> Ok (ModelQuery.ParameterConstraint.AnnotationConstraint annotation_constraint) | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Attribute { base = { Node.value = Name (Name.Identifier "index"); _ }; attribute = "equals" as attribute; _; }); _; } as callee; arguments; } -> ( match attribute, arguments with | ( "equals", [ { Call.Argument.value = { Node.value = Expression.Constant (Constant.Integer index); _ }; _; }; ] ) -> Ok (ModelQuery.ParameterConstraint.IndexConstraint index) | _ -> Error (model_verification_error ~path ~location (InvalidModelQueryClauseArguments { callee; arguments }))) | Expression.Call { Call.callee; _ } -> Error (model_verification_error ~path ~location (InvalidModelQueryWhereClause { expression = callee; find_clause_kind = "parameters" })) | _ -> Error (model_verification_error ~path ~location (UnsupportedConstraint constraint_expression)) in match value with | Expression.List items -> List.map items ~f:parse_constraint |> all | _ -> parse_constraint expression >>| List.return let parse_model_clause ~path ~configuration ~find_clause ~is_object_target ({ Node.value; location } as expression) = let open Core.Result in let invalid_model_query_model_clause ~path ~location callee = model_verification_error ~path ~location (InvalidModelQueryModelClause { expression = callee; find_clause_kind = get_find_clause_as_string find_clause }) in let parse_model ({ Node.value; _ } as model_expression) = let parse_taint taint_expression = let parse_produced_taint expression = match Node.value expression with | Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Identifier (("ParametricSourceFromAnnotation" | "ParametricSinkFromAnnotation") as parametric_annotation)); _; }; arguments = [ { Call.Argument.name = Some { Node.value = "pattern"; _ }; value = { Node.value = Expression.Name (Name.Identifier pattern); _ }; }; { Call.Argument.name = Some { Node.value = "kind"; _ }; value = { Node.value = Expression.Name (Name.Identifier kind); _ }; }; ]; } -> ( match parametric_annotation with | "ParametricSourceFromAnnotation" -> Ok [ModelQuery.ParametricSourceFromAnnotation { source_pattern = pattern; kind }] | "ParametricSinkFromAnnotation" -> Ok [ModelQuery.ParametricSinkFromAnnotation { sink_pattern = pattern; kind }] | _ -> Error (model_verification_error ~path ~location (UnexpectedTaintAnnotation parametric_annotation))) | _ -> parse_annotations ~path ~location ~model_name:"model query" ~configuration ~parameters:[] ~callable_parameter_names_to_positions:None ~is_object_target ~is_model_query:true expression >>| List.map ~f:(fun taint -> ModelQuery.TaintAnnotation taint) in match Node.value taint_expression with | Expression.List taint_annotations -> List.map taint_annotations ~f:parse_produced_taint |> all >>| List.concat | _ -> parse_produced_taint taint_expression in match value with | Expression.Call { Call.callee = { Node.value = Name (Name.Identifier "Returns"); _ } as callee; arguments = [{ Call.Argument.value = taint; _ }]; } -> ( match is_callable_clause_kind find_clause with | false -> Error (invalid_model_query_model_clause ~path ~location callee) | _ -> parse_taint taint >>| fun taint -> ModelQuery.ReturnTaint taint) | Expression.Call { Call.callee = { Node.value = Name (Name.Identifier "AttributeModel"); _ } as callee; arguments = [{ Call.Argument.value = taint; _ }]; } -> ( match is_callable_clause_kind find_clause with | true -> Error (invalid_model_query_model_clause ~path ~location callee) | _ -> parse_taint taint >>| fun taint -> ModelQuery.AttributeTaint taint) | Expression.Call { Call.callee = { Node.value = Name (Name.Identifier "NamedParameter"); _ } as callee; arguments = [ { Call.Argument.value = { Node.value = Constant (Constant.String { StringLiteral.value = name; _ }); _ }; name = Some { Node.value = "name"; _ }; }; { Call.Argument.value = taint; name = Some { Node.value = "taint"; _ } }; ]; } -> ( match is_callable_clause_kind find_clause with | false -> Error (invalid_model_query_model_clause ~path ~location callee) | _ -> parse_taint taint >>| fun taint -> ModelQuery.NamedParameterTaint { name; taint }) | Expression.Call { Call.callee = { Node.value = Name (Name.Identifier "PositionalParameter"); _ } as callee; arguments = [ { Call.Argument.value = { Node.value = Constant (Constant.Integer index); _ }; name = Some { Node.value = "index"; _ }; }; { Call.Argument.value = taint; name = Some { Node.value = "taint"; _ } }; ]; } -> ( match is_callable_clause_kind find_clause with | false -> Error (invalid_model_query_model_clause ~path ~location callee) | _ -> parse_taint taint >>| fun taint -> ModelQuery.PositionalParameterTaint { index; taint }) | Expression.Call { Call.callee = { Node.value = Name (Name.Identifier "AllParameters"); _ } as callee; arguments = [{ Call.Argument.value = taint; _ }]; } -> ( match is_callable_clause_kind find_clause with | false -> Error (invalid_model_query_model_clause ~path ~location callee) | _ -> parse_taint taint >>| fun taint -> ModelQuery.AllParametersTaint { excludes = []; taint }) | Expression.Call { Call.callee = { Node.value = Name (Name.Identifier "AllParameters"); _ } as callee; arguments = [ { Call.Argument.value = taint; _ }; { Call.Argument.name = Some { Node.value = "exclude"; _ }; value = excludes }; ]; } -> ( match is_callable_clause_kind find_clause with | false -> Error (invalid_model_query_model_clause ~path ~location callee) | _ -> let excludes = let parse_string_to_exclude ({ Node.value; location } as exclude) = match value with | Expression.Constant (Constant.String { StringLiteral.value; _ }) -> Core.Result.Ok value | _ -> Error (model_verification_error ~path ~location (InvalidParameterExclude exclude)) in match Node.value excludes with | Expression.List exclude_strings -> List.map exclude_strings ~f:parse_string_to_exclude |> Core.Result.all | _ -> parse_string_to_exclude excludes >>| fun exclude -> [exclude] in excludes >>= fun excludes -> parse_taint taint >>| fun taint -> ModelQuery.AllParametersTaint { excludes; taint }) | Expression.Call { Call.callee = { Node.value = Name (Name.Identifier "Parameters"); _ } as callee; arguments; } -> ( match is_callable_clause_kind find_clause, arguments with | false, _ -> Error (invalid_model_query_model_clause ~path ~location callee) | _, [{ Call.Argument.value = taint; _ }] -> parse_taint taint >>| fun taint -> ModelQuery.ParameterTaint { where = []; taint } | ( _, [ { Call.Argument.value = taint; _ }; { Call.Argument.name = Some { Node.value = "where"; _ }; value = where_clause }; ] ) -> parse_parameter_where_clause ~path where_clause >>= fun where -> parse_taint taint >>| fun taint -> ModelQuery.ParameterTaint { where; taint } | _ -> Error (model_verification_error ~path ~location (InvalidModelQueryClauseArguments { callee; arguments }))) | _ -> Error (model_verification_error ~path ~location (UnexpectedModelExpression model_expression)) in match value with | Expression.List items -> List.map items ~f:parse_model |> all | _ -> parse_model expression >>| List.return let parameters_of_callable_annotation { Type.Callable.implementation; overloads; _ } = let parameters_of_overload = function | { Type.Callable.parameters = Type.Callable.Defined parameters; _ } -> List.mapi parameters ~f:(fun position parameter -> position, parameter) | _ -> [] in parameters_of_overload implementation @ (List.map overloads ~f:parameters_of_overload |> List.concat) let add_signature_based_breadcrumbs ~resolution root ~callable_annotation breadcrumbs = match callable_annotation with | None -> breadcrumbs | Some callable_annotation -> let type_breadcrumbs = match root with | AccessPath.Root.PositionalParameter { position; _ } -> parameters_of_callable_annotation callable_annotation |> List.filter_map ~f:(fun (parameter_position, parameter) -> Option.some_if (parameter_position = position) parameter) |> List.fold ~init:Features.BreadcrumbSet.bottom ~f:(fun sofar parameter -> parameter |> Type.Callable.Parameter.annotation |> Features.type_breadcrumbs ~resolution |> Features.BreadcrumbSet.add_set ~to_add:sofar) | AccessPath.Root.NamedParameter { name; _ } -> parameters_of_callable_annotation callable_annotation |> List.filter_map ~f:(fun (_, parameter) -> match parameter with | Type.Callable.Parameter.KeywordOnly { name = parameter_name; _ } when String.equal parameter_name ("$parameter$" ^ name) -> Some parameter | Type.Callable.Parameter.Named { name = parameter_name; _ } when String.equal parameter_name name -> Some parameter | _ -> None) |> List.fold ~init:Features.BreadcrumbSet.bottom ~f:(fun sofar parameter -> parameter |> Type.Callable.Parameter.annotation |> Features.type_breadcrumbs ~resolution |> Features.BreadcrumbSet.add_set ~to_add:sofar) | AccessPath.Root.LocalResult -> let { Type.Callable.implementation = { Type.Callable.annotation; _ }; _ } = callable_annotation in Features.type_breadcrumbs ~resolution (Some annotation) | _ -> Features.BreadcrumbSet.empty in List.rev_append (Features.BreadcrumbSet.to_approximation type_breadcrumbs) breadcrumbs let parse_parameter_taint ~path ~location ~model_name ~configuration ~parameters ~callable_parameter_names_to_positions ~is_object_target (root, _name, parameter) = parameter.Node.value.Parameter.annotation >>| parse_annotations ~path ~location ~model_name ~configuration ~parameters ~callable_parameter_names_to_positions ~is_object_target |> Option.value ~default:(Ok []) |> Core.Result.map ~f:(List.map ~f:(fun annotation -> annotation, ParameterAnnotation root)) let add_taint_annotation_to_model ~resolution ~path ~location ~model_name ~annotation_kind ~callable_annotation ~sources_to_keep ~sinks_to_keep model annotation = let open Core.Result in let invalid_model_for_taint error_message = model_verification_error ~path ~location (InvalidModelForTaint { model_name; error = error_message }) in match annotation_kind with | ReturnAnnotation -> ( let root = AccessPath.Root.LocalResult in match annotation with | Sink { sink; breadcrumbs; via_features; path; leaf_names; leaf_name_provided; trace_length } -> breadcrumbs |> List.map ~f:Features.BreadcrumbInterned.intern |> List.map ~f:Features.BreadcrumbSet.inject |> add_signature_based_breadcrumbs ~resolution root ~callable_annotation |> introduce_sink_taint ~root ~path ~leaf_names ~leaf_name_provided ~trace_length ~sinks_to_keep model sink via_features |> map_error ~f:invalid_model_for_taint | Source { source; breadcrumbs; via_features; path; leaf_names; leaf_name_provided; trace_length } -> breadcrumbs |> List.map ~f:Features.BreadcrumbInterned.intern |> List.map ~f:Features.BreadcrumbSet.inject |> add_signature_based_breadcrumbs ~resolution root ~callable_annotation |> introduce_source_taint ~root ~path ~leaf_names ~leaf_name_provided ~trace_length ~sources_to_keep model source via_features |> map_error ~f:invalid_model_for_taint | Tito _ -> Error (model_verification_error ~path ~location (InvalidReturnAnnotation { model_name; annotation = "TaintInTaintOut" })) | AddFeatureToArgument _ -> Error (model_verification_error ~path ~location (InvalidReturnAnnotation { model_name; annotation = "AddFeatureToArgument" })) | Sanitize annotations -> Ok (introduce_sanitize ~root model annotations)) | ParameterAnnotation root -> ( match annotation with | Sink { sink; breadcrumbs; via_features; path; leaf_names; leaf_name_provided; trace_length } -> breadcrumbs |> List.map ~f:Features.BreadcrumbInterned.intern |> List.map ~f:Features.BreadcrumbSet.inject |> add_signature_based_breadcrumbs ~resolution root ~callable_annotation |> introduce_sink_taint ~root ~path ~leaf_names ~leaf_name_provided ~trace_length ~sinks_to_keep model sink via_features |> map_error ~f:invalid_model_for_taint | Source { source; breadcrumbs; via_features; path; leaf_names; leaf_name_provided; trace_length } -> breadcrumbs |> List.map ~f:Features.BreadcrumbInterned.intern |> List.map ~f:Features.BreadcrumbSet.inject |> add_signature_based_breadcrumbs ~resolution root ~callable_annotation |> introduce_source_taint ~root ~path ~leaf_names ~leaf_name_provided ~trace_length ~sources_to_keep model source via_features |> map_error ~f:invalid_model_for_taint | Tito { tito; breadcrumbs; via_features; path } -> (* For tito, both the parameter and the return type can provide type based breadcrumbs *) breadcrumbs |> List.map ~f:Features.BreadcrumbInterned.intern |> List.map ~f:Features.BreadcrumbSet.inject |> add_signature_based_breadcrumbs ~resolution root ~callable_annotation |> add_signature_based_breadcrumbs ~resolution AccessPath.Root.LocalResult ~callable_annotation |> introduce_taint_in_taint_out ~root ~path model tito via_features |> map_error ~f:invalid_model_for_taint | AddFeatureToArgument { breadcrumbs; via_features; path } -> breadcrumbs |> List.map ~f:Features.BreadcrumbInterned.intern |> List.map ~f:Features.BreadcrumbSet.inject |> add_signature_based_breadcrumbs ~resolution root ~callable_annotation |> introduce_sink_taint ~root ~path ~leaf_names:[] ~leaf_name_provided:false ~trace_length:None ~sinks_to_keep model Sinks.AddFeatureToArgument via_features |> map_error ~f:invalid_model_for_taint | Sanitize annotations -> Ok (introduce_sanitize ~root model annotations)) let parse_return_taint ~path ~location ~model_name ~configuration ~parameters ~callable_parameter_names_to_positions ~is_object_target expression = let open Core.Result in parse_annotations ~path ~location ~model_name ~configuration ~parameters ~callable_parameter_names_to_positions ~is_object_target expression |> map ~f:(List.map ~f:(fun annotation -> annotation, ReturnAnnotation)) type parsed_signature = { signature: Define.Signature.t; location: Location.t; call_target: Target.callable_t; } type parsed_attribute = { name: Reference.t; source_annotation: Expression.t option; sink_annotation: Expression.t option; decorators: Decorator.t list; location: Location.t; call_target: Target.object_t; } type parsed_statement = | ParsedSignature of parsed_signature | ParsedAttribute of parsed_attribute | ParsedQuery of ModelQuery.rule type model_or_query = | Model of (Model.WithTarget.t * Reference.t option) | Query of ModelQuery.rule let resolve_global_callable ~path ~location ~verify_decorators ~resolution ({ Define.Signature.name; decorators; _ } as define) = (* Since properties and setters share the same undecorated name, we need to special-case them. *) let open ModelVerifier in if signature_is_property define then find_method_definitions ~resolution ~predicate:is_property name |> List.hd >>| Type.Callable.create_from_implementation >>| (fun callable -> Global.Attribute callable) |> Core.Result.return else if Define.Signature.is_property_setter define then find_method_definitions ~resolution ~predicate:Define.is_property_setter name |> List.hd >>| Type.Callable.create_from_implementation >>| (fun callable -> Global.Attribute callable) |> Core.Result.return else if verify_decorators && not (List.is_empty decorators) then (* Ensure that models don't declare decorators that our taint analyses doesn't understand. We check for the verify_decorators flag, as defines originating from `create_model_from_annotation` are not user-specified models that we're parsing. *) Error (model_verification_error ~path ~location (UnexpectedDecorators { name; unexpected_decorators = decorators })) else Ok (resolve_global ~resolution name) let adjust_sanitize_and_modes_and_skipped_override ~path ~define_name ~configuration ~top_level_decorators ~is_object_target model = let open Core.Result in let create_get_item_call ~location callee arguments = List.map ~f:(fun { Call.Argument.value; _ } -> value) arguments |> Ast.Expression.get_item_call callee ~location |> Node.create ~location in let create_function_call ~location callee arguments = Ast.Expression.Expression.Call { callee = { Node.location; value = Name (Name.Identifier callee) }; arguments } |> Node.create ~location in let parse_sanitize_annotations ~location ~original_expression arguments = (* Pretend that it is a `Sanitize[...]` expression and use the annotation parser. *) let expression = create_get_item_call ~location "Sanitize" arguments in parse_annotations ~path ~location ~model_name:(Reference.show define_name) ~configuration ~parameters:[] ~callable_parameter_names_to_positions:None ~is_object_target expression |> function | Ok [Sanitize sanitize_annotations] -> Ok (sanitize_from_annotations sanitize_annotations) | Ok _ -> failwith "impossible case" | Error ({ ModelVerificationError.kind = InvalidTaintAnnotation { reason; _ }; _ } as error) -> Error { error with kind = InvalidTaintAnnotation { taint_annotation = original_expression; reason }; } | Error error -> Error error in let join_with_sanitize_decorator ~sanitizers ~location ~original_expression arguments = let annotation_error reason = model_verification_error ~path ~location (InvalidTaintAnnotation { taint_annotation = original_expression; reason }) in match arguments with | None -> Ok { sanitizers with Model.Sanitizers.global = Sanitize.all } | Some [ { Call.Argument.value = { Node.value = Expression.Name (Name.Identifier (("TaintSource" | "TaintSink" | "TaintInTaintOut" | "Parameters") as identifier)); _; }; _; }; ] -> ( match identifier with | "TaintSource" when not is_object_target -> Error (annotation_error "`TaintSource` is not supported within `Sanitize()`. Did you mean to use \ `SanitizeSingleTrace(...)`?") | "TaintSink" when not is_object_target -> Error (annotation_error "`TaintSink` is not supported within `Sanitize()`. Did you mean to use \ `SanitizeSingleTrace(...)`?") | "TaintSource" -> let global = { sanitizers.global with sources = Some All } in Ok { sanitizers with global } | "TaintSink" -> let global = { sanitizers.global with sinks = Some All } in Ok { sanitizers with global } | "TaintInTaintOut" -> let global = { sanitizers.global with tito = Some All } in Ok { sanitizers with global } | "Parameters" -> Ok { sanitizers with parameters = Sanitize.all } | _ -> failwith "impossible") | Some [ { Call.Argument.value = { Node.value = Expression.Call { Call.callee; arguments }; _ }; _ }; ] when [%compare.equal: string option] (base_name callee) (Some "Parameters") -> parse_sanitize_annotations ~location ~original_expression arguments >>| fun parameters_sanitize -> { sanitizers with parameters = Sanitize.join sanitizers.parameters parameters_sanitize } | Some arguments -> ( parse_sanitize_annotations ~location ~original_expression arguments >>= function | { Sanitize.sources = Some _; _ } when not is_object_target -> Error (annotation_error "`TaintSource` is not supported within `Sanitize(...)`. Did you mean to use \ `SanitizeSingleTrace(...)`?") | { Sanitize.sinks = Some _; _ } when not is_object_target -> Error (annotation_error "`TaintSink` is not supported within `Sanitize(...)`. Did you mean to use \ `SanitizeSingleTrace(...)`?") | global_sanitize -> Ok { sanitizers with global = Sanitize.join sanitizers.global global_sanitize }) in let join_with_sanitize_single_trace_decorator ~sanitizers ~location ~original_expression arguments = let annotation_error reason = model_verification_error ~path ~location (InvalidTaintAnnotation { taint_annotation = original_expression; reason }) in match arguments with | None -> Error (annotation_error "`SanitizeSingleTrace()` is ambiguous. Did you mean \ `SanitizeSingleTrace(TaintSource)` or `SanitizeSingleTrace(TaintSink)`?") | Some [ { Call.Argument.value = { Node.value = Expression.Name (Name.Identifier "TaintSource"); _ }; _; }; ] -> let global = { sanitizers.Model.Sanitizers.global with sources = Some All } in Ok { sanitizers with global } | Some [ { Call.Argument.value = { Node.value = Expression.Name (Name.Identifier "TaintSink"); _ }; _; }; ] -> let global = { sanitizers.Model.Sanitizers.global with sinks = Some All } in Ok { sanitizers with global } | Some arguments -> ( parse_sanitize_annotations ~location ~original_expression arguments >>= function | { Sanitize.tito = Some _; _ } -> Error (annotation_error "`TaintInTaintOut` is not supported within `SanitizeSingleTrace(...)`. Did you \ mean to use `Sanitize(...)`?") | sanitizer -> Ok { sanitizers with global = Sanitize.join sanitizers.global sanitizer }) in let join_with_decorator (sanitizers, modes, skipped_override) { Decorator.name = { Node.value = name; location = decorator_location }; arguments } = let name = Reference.show name in let original_expression = create_function_call ~location:decorator_location name (Option.value ~default:[] arguments) in match name with | "Sanitize" -> join_with_sanitize_decorator ~sanitizers ~location:decorator_location ~original_expression arguments >>| fun sanitizers -> sanitizers, modes, skipped_override | "SanitizeSingleTrace" -> join_with_sanitize_single_trace_decorator ~sanitizers ~location:decorator_location ~original_expression arguments >>| fun sanitizers -> sanitizers, modes, skipped_override | "SkipAnalysis" -> Ok (sanitizers, Model.ModeSet.add SkipAnalysis modes, skipped_override) | "SkipDecoratorWhenInlining" -> Ok (sanitizers, Model.ModeSet.add SkipDecoratorWhenInlining modes, skipped_override) | "SkipOverrides" -> Ok (sanitizers, Model.ModeSet.add SkipOverrides modes, Some define_name) | "SkipObscure" -> Ok (sanitizers, Model.ModeSet.remove Obscure modes, skipped_override) | _ -> Ok (sanitizers, modes, skipped_override) in List.fold_result top_level_decorators ~f:join_with_decorator ~init:(model.Model.sanitizers, model.Model.modes, None) >>| fun (sanitizers, modes, skipped_override) -> { model with sanitizers; modes }, skipped_override let compute_sources_and_sinks_to_keep ~configuration ~rule_filter = match rule_filter with | None -> None, None | Some rule_filter -> let rule_filter = Int.Set.of_list rule_filter in let sources_to_keep, sinks_to_keep = let { TaintConfiguration.rules; _ } = configuration in let rules = (* The user annotations for partial sinks will be the untriggered ones, even though the rule expects triggered sinks. *) let untrigger_partial_sinks sink = match sink with | Sinks.TriggeredPartialSink { kind; label } -> Sinks.PartialSink { kind; label } | _ -> sink in List.filter_map rules ~f:(fun { TaintConfiguration.Rule.code; sources; sinks; _ } -> if Core.Set.mem rule_filter code then Some (sources, List.map sinks ~f:untrigger_partial_sinks) else None) in List.fold rules ~init: ( Sources.Set.singleton Sources.Attach, Sinks.Set.of_list [Sinks.AddFeatureToArgument; Sinks.Attach] ) ~f:(fun (sources, sinks) (rule_sources, rule_sinks) -> ( Sources.Set.union sources (Sources.Set.of_list rule_sources), Sinks.Set.union sinks (Sinks.Set.of_list rule_sinks) )) in Some sources_to_keep, Some sinks_to_keep let parse_statement ~resolution ~path ~configuration statement = let open Core.Result in let global_resolution = Resolution.global_resolution resolution in match statement with | { Node.value = Statement.Define { signature = { name; _ } as signature; body = [ { value = Statement.Expression { value = Expression.Constant Constant.Ellipsis; _ }; _ }; ]; _; }; location; } -> let class_candidate = Reference.prefix name |> Option.map ~f:(GlobalResolution.parse_reference global_resolution) |> Option.bind ~f:(GlobalResolution.class_definition global_resolution) in let call_target = match class_candidate with | Some _ when Define.Signature.is_property_setter signature -> Target.create_property_setter name | Some _ -> Target.create_method name | None -> Target.create_function name in Ok [ParsedSignature { signature; location; call_target }] | { Node.value = Statement.Define { signature = { name; _ }; _ }; location } -> Error (model_verification_error ~path ~location (DefineBodyNotEllipsis (Reference.show name))) | { Node.value = Class { Class.name; base_arguments; body = [ { Node.value = Statement.Expression { Node.value = Expression.Constant Constant.Ellipsis; _ }; _; }; ]; _; }; _; } -> let sink_annotations = let class_sink_base { Call.Argument.value; _ } = if Expression.show value |> String.is_prefix ~prefix:"TaintSink[" then Some value else None in List.filter_map base_arguments ~f:class_sink_base in let source_annotations, extra_decorators = let decorator_with_name name = Node.create_with_default_location (Expression.Name (Name.Identifier name)) in let class_source_base { Call.Argument.value; _ } = let name = Expression.show value in if String.is_prefix name ~prefix:"TaintSource[" then Some (Either.First value) else if String.equal name "SkipAnalysis" then Some (Either.Second (decorator_with_name "SkipAnalysis")) else if String.equal name "SkipOverrides" then Some (Either.Second (decorator_with_name "SkipOverrides")) else None in List.filter_map base_arguments ~f:class_source_base |> List.fold ~init:([], []) ~f:(fun (source_annotations, decorators) -> function | Either.First source_annotation -> source_annotation :: source_annotations, decorators | Either.Second decorator -> source_annotations, decorator :: decorators) in if (not (List.is_empty sink_annotations)) || (not (List.is_empty source_annotations)) || not (List.is_empty extra_decorators) then ModelVerifier.class_definitions ~resolution name |> Option.bind ~f:List.hd |> Option.map ~f:(fun { Node.value = { Class.body; _ }; _ } -> let signature { Node.value; location } = match value with | Statement.Define { Define.signature = { Define.Signature.name; parameters; decorators; _ } as signature; _; } -> let signature ~extra_decorators ~source_annotation ~sink_annotation = let parameters = let sink_parameter parameter = let update_annotation { Parameter.name; value; _ } = let value = match value with | None -> None | Some _ -> Some (Node.create_with_default_location (Expression.Constant Constant.Ellipsis)) in { Parameter.name; annotation = sink_annotation; value } in Node.map parameter ~f:update_annotation in List.map parameters ~f:sink_parameter in let decorators = if signature_is_property signature || Define.Signature.is_property_setter signature then decorators else [] in let decorators = List.rev_append extra_decorators decorators in ParsedSignature { signature = { signature with Define.Signature.parameters; return_annotation = source_annotation; decorators; }; location; call_target = Target.create_method name; } in let sources = List.map source_annotations ~f:(fun source_annotation -> signature ~extra_decorators:[] ~source_annotation:(Some source_annotation) ~sink_annotation:None) in let sinks = List.map sink_annotations ~f:(fun sink_annotation -> signature ~extra_decorators:[] ~source_annotation:None ~sink_annotation:(Some sink_annotation)) in let skip_analysis_or_overrides_defines = if not (List.is_empty extra_decorators) then [signature ~extra_decorators ~source_annotation:None ~sink_annotation:None] else [] in skip_analysis_or_overrides_defines @ sources @ sinks | _ -> [] in List.concat_map body ~f:signature) |> Option.value ~default:[] |> return else Ok [] | { Node.value = Class { Class.name; _ }; location } -> Error (model_verification_error ~path ~location (ClassBodyNotEllipsis (Reference.show name))) | { Node.value = Assign { Assign.target = { Node.value = Name name; _ } as target; annotation = Some annotation; _ }; location; } -> if not (is_simple_name name) then Error (model_verification_error ~path ~location (InvalidIdentifier target)) else if Expression.show annotation |> String.is_prefix ~prefix:"Sanitize[TaintInTaintOut[" then Error (model_verification_error ~path ~location (InvalidTaintAnnotation { taint_annotation = annotation; reason = "TaintInTaintOut sanitizers cannot be modelled on attributes"; })) else if Expression.show annotation |> String.equal "Sanitize" || Expression.show annotation |> String.is_prefix ~prefix:"Sanitize[TaintSource" || Expression.show annotation |> String.is_prefix ~prefix:"Sanitize[TaintSink" then let name = name_to_reference_exn name in let arguments = match annotation.Node.value with | Expression.Call { arguments; _ } -> Some arguments | _ -> None in let decorator = { Decorator.name = Node.create_with_default_location (Reference.create "Sanitize"); arguments; } in Ok [ ParsedAttribute { name; source_annotation = None; sink_annotation = None; decorators = [decorator]; location; call_target = Target.create_object name; }; ] else if Expression.show annotation |> String.is_substring ~substring:"TaintSource[" then let name = name_to_reference_exn name in Ok [ ParsedAttribute { name; source_annotation = Some annotation; sink_annotation = None; decorators = []; location; call_target = Target.create_object name; }; ] else if Expression.show annotation |> String.is_substring ~substring:"TaintSink[" || Expression.show annotation |> String.is_substring ~substring:"TaintInTaintOut[" then let name = name_to_reference_exn name in Ok [ ParsedAttribute { name; source_annotation = None; sink_annotation = Some annotation; decorators = []; location; call_target = Target.create_object name; }; ] else if Expression.show annotation |> String.equal "ViaTypeOf" then let name = name_to_reference_exn name in Ok [ ParsedAttribute { name; source_annotation = None; sink_annotation = Some annotation; decorators = []; location; call_target = Target.create_object name; }; ] else Error (model_verification_error ~path ~location (InvalidTaintAnnotation { taint_annotation = annotation; reason = "Unsupported annotation for attributes" })) | { Node.value = Expression { Node.value = Expression.Call { Call.callee = { Node.value = Expression.Name (Name.Identifier "ModelQuery"); _ }; arguments; }; _; }; location; } -> let clauses = match arguments with | [ { Call.Argument.name = Some { Node.value = "find"; _ }; value = find_clause }; { Call.Argument.name = Some { Node.value = "where"; _ }; value = where_clause }; { Call.Argument.name = Some { Node.value = "model"; _ }; value = model_clause }; ] -> let parsed_find_clause = parse_find_clause ~path find_clause in let is_object_target = not (is_callable_clause_kind parsed_find_clause) in Ok ( None, parsed_find_clause, parse_where_clause ~path ~find_clause:parsed_find_clause where_clause, parse_model_clause ~path ~configuration ~find_clause:parsed_find_clause ~is_object_target model_clause ) | [ { Call.Argument.name = Some { Node.value = "name"; _ }; value = { Node.value = Expression.Constant (Constant.String { StringLiteral.value = name; _ }); _; }; }; { Call.Argument.name = Some { Node.value = "find"; _ }; value = find_clause }; { Call.Argument.name = Some { Node.value = "where"; _ }; value = where_clause }; { Call.Argument.name = Some { Node.value = "model"; _ }; value = model_clause }; ] -> let parsed_find_clause = parse_find_clause ~path find_clause in let is_object_target = not (is_callable_clause_kind parsed_find_clause) in Ok ( Some name, parsed_find_clause, parse_where_clause ~path ~find_clause:parsed_find_clause where_clause, parse_model_clause ~path ~configuration ~find_clause:parsed_find_clause ~is_object_target model_clause ) | _ -> Error (model_verification_error ~path ~location (InvalidModelQueryClauses arguments)) in clauses >>= fun (name, find_clause, where_clause, model_clause) -> find_clause >>= fun rule_kind -> where_clause >>= fun query -> model_clause >>| fun productions -> [ParsedQuery { ModelQuery.rule_kind; query; productions; name }] | { Node.location; _ } -> Error (model_verification_error ~path ~location (UnexpectedStatement statement)) let create_model_from_signature ~resolution ~path ~configuration ~sources_to_keep ~sinks_to_keep ~is_obscure { signature = { Define.Signature.name = callable_name; parameters; return_annotation; decorators; _ } as define; location; call_target; } = let open Core.Result in let open ModelVerifier in let call_target = (call_target :> Target.t) in let is_object_target = false in (* Strip off the decorators only used for taint annotations. *) let top_level_decorators, define = let get_taint_decorator decorator_expression = match Decorator.from_expression decorator_expression with | None -> Either.Second decorator_expression | Some ({ Decorator.name = { Node.value = name; _ }; _ } as decorator) -> ( match Reference.as_list name with | ["Sanitize"] | ["SanitizeSingleTrace"] | ["SkipAnalysis"] | ["SkipDecoratorWhenInlining"] | ["SkipOverrides"] | ["SkipObscure"] -> Either.first decorator | _ -> Either.Second decorator_expression) in let sanitizers, nonsanitizers = List.partition_map define.decorators ~f:get_taint_decorator in sanitizers, { define with decorators = nonsanitizers } in (* To ensure that the start/stop lines can be used for commenting out models, * we include the earliest decorator location. *) let location = let start = match decorators with | [] -> location.Location.start | first :: _ -> first.location.start in { location with start } in (* Make sure we know about what we model. *) let model_verification_error kind = Error { ModelVerificationError.kind; path; location } in let callable_annotation = resolve_global_callable ~path ~location ~verify_decorators:true ~resolution define >>= function | None -> ( let module_name = Reference.first callable_name in let module_resolved = resolve_global ~resolution (Reference.create module_name) in match module_resolved with | Some _ -> model_verification_error (MissingSymbol { module_name; symbol_name = Reference.show callable_name }) | None -> model_verification_error (NotInEnvironment { module_name; name = Reference.show callable_name })) | Some Global.Class -> model_verification_error (ModelingClassAsDefine (Reference.show callable_name)) | Some Global.Module -> model_verification_error (ModelingModuleAsDefine (Reference.show callable_name)) | Some (Global.Attribute (Type.Callable t)) | Some (Global.Attribute (Type.Parametric { name = "BoundMethod"; parameters = [Type.Parameter.Single (Type.Callable t); _] })) -> Ok (Some t) | Some (Global.Attribute Type.Any) -> Ok None | Some (Global.Attribute Type.Top) -> Ok None | Some (Global.Attribute _) -> model_verification_error (ModelingAttributeAsDefine (Reference.show callable_name)) in (* Check model matches callables primary signature. *) let callable_parameter_names_to_positions = match callable_annotation with | Ok (Some callable_annotation) -> let add_parameter_to_position map (position, parameter) = match parameter with | Type.Callable.Parameter.Named { name; _ } | Type.Callable.Parameter.KeywordOnly { name; _ } -> Map.update map (Identifier.sanitized name) ~f:(function | None -> [position] | Some positions -> position :: positions) | _ -> map in callable_annotation |> parameters_of_callable_annotation |> List.fold ~init:String.Map.empty ~f:add_parameter_to_position |> String.Map.map ~f:(List.dedup_and_sort ~compare:Int.compare) |> Option.some | _ -> None in (* If there were parameters omitted from the model, the positioning will be off in the access path conversion. Let's fix the positions after the fact to make sure that our models aren't off. *) let normalized_model_parameters = let parameters = AccessPath.Root.normalize_parameters parameters in match callable_parameter_names_to_positions with | None -> Ok parameters | Some names_to_positions -> let create_error reason = { ModelVerificationError.kind = IncompatibleModelError { name = Reference.show callable_name; callable_type = Option.value_exn (Caml.Result.get_ok callable_annotation); errors = [ModelVerificationError.IncompatibleModelError.{ reason; overload = None }]; }; path; location; } in let adjust_position_of_positional_parameter ~name ~position = match Map.find names_to_positions name with | Some [accurate_position] -> Ok accurate_position | Some accurate_positions when List.mem ~equal:Int.equal accurate_positions position -> Ok position | Some valid_positions -> Error (create_error (ModelVerificationError.IncompatibleModelError.InvalidNamedParameterPosition { name; position; valid_positions })) | None -> Error (create_error (ModelVerificationError.IncompatibleModelError.UnexpectedNamedParameter name)) in let adjust_position_of_root = function | AccessPath.Root.PositionalParameter { name; position; positional_only = false } when not (String.is_prefix ~prefix:"__" name) -> adjust_position_of_positional_parameter ~name ~position >>| fun position -> AccessPath.Root.PositionalParameter { name; position; positional_only = false } | root -> Ok root in let adjust_position (root, name, parameter) = adjust_position_of_root root >>| fun root -> root, name, parameter in List.map parameters ~f:adjust_position |> all in let annotations () = normalized_model_parameters >>= fun normalized_model_parameters -> List.map normalized_model_parameters ~f: (parse_parameter_taint ~path ~location ~model_name:(Reference.show callable_name) ~configuration ~parameters ~callable_parameter_names_to_positions ~is_object_target) |> all >>| List.concat >>= fun parameter_taint -> return_annotation |> Option.map ~f: (parse_return_taint ~path ~location ~model_name:(Reference.show callable_name) ~configuration ~parameters ~callable_parameter_names_to_positions ~is_object_target) |> Option.value ~default:(Ok []) >>| fun return_taint -> List.rev_append parameter_taint return_taint in let model = callable_annotation >>= fun callable_annotation -> normalized_model_parameters >>= fun normalized_model_parameters -> ModelVerifier.verify_signature ~path ~location ~normalized_model_parameters ~name:callable_name callable_annotation >>= fun () -> annotations () >>= fun annotations -> let default_model = if is_obscure then Model.obscure_model else Model.empty_model in List.fold_result annotations ~init:default_model ~f:(fun accumulator (annotation, annotation_kind) -> add_taint_annotation_to_model ~path ~location ~model_name:(Reference.show callable_name) ~resolution:(Resolution.global_resolution resolution) ~annotation_kind ~callable_annotation ~sources_to_keep ~sinks_to_keep accumulator annotation) in model >>= adjust_sanitize_and_modes_and_skipped_override ~path ~configuration ~top_level_decorators ~define_name:callable_name ~is_object_target >>| fun (model, skipped_override) -> Model ({ Model.WithTarget.model; target = call_target }, skipped_override) let create_model_from_attribute ~resolution ~path ~configuration ~sources_to_keep ~sinks_to_keep { name; source_annotation; sink_annotation; decorators; location; call_target } = let open Core.Result in let call_target = (call_target :> Target.t) in let is_object_target = true in ModelVerifier.verify_global ~path ~location ~resolution ~name >>= fun () -> source_annotation |> Option.map ~f: (parse_return_taint ~path ~location ~model_name:(Reference.show name) ~configuration ~parameters:[] ~callable_parameter_names_to_positions:None ~is_object_target) |> Option.value ~default:(Ok []) >>= fun source_taint -> let parse_sink_taint annotation = let root = AccessPath.Root.PositionalParameter { position = 0; name = attribute_symbolic_parameter; positional_only = false } in let parameter = Parameter.create ~location:Location.any ~annotation ~name:attribute_symbolic_parameter () in parse_parameter_taint ~path ~location ~model_name:(Reference.show name) ~configuration ~parameters:[] ~callable_parameter_names_to_positions:None ~is_object_target (root, attribute_symbolic_parameter, parameter) in sink_annotation |> Option.map ~f:parse_sink_taint |> Option.value ~default:(Ok []) >>= fun sink_taint -> Ok (List.rev_append source_taint sink_taint) >>= fun annotations -> List.fold_result annotations ~init:Model.empty_model ~f:(fun accumulator (annotation, annotation_kind) -> add_taint_annotation_to_model ~path ~location ~model_name:(Reference.show name) ~resolution:(Resolution.global_resolution resolution) ~annotation_kind ~callable_annotation:None ~sources_to_keep ~sinks_to_keep accumulator annotation) >>= adjust_sanitize_and_modes_and_skipped_override ~path ~configuration ~top_level_decorators:decorators ~define_name:name ~is_object_target >>| fun (model, skipped_override) -> Model ({ Model.WithTarget.model; target = call_target }, skipped_override) let create ~resolution ~path ~configuration ~rule_filter ~callables ~stubs source = let sources_to_keep, sinks_to_keep = compute_sources_and_sinks_to_keep ~configuration ~rule_filter in let signatures_and_queries, errors = let open Core.Result in String.split ~on:'\n' source |> Parser.parse >>| Source.create >>| Source.statements >>| List.map ~f:(parse_statement ~resolution ~path ~configuration) >>| List.partition_result |> function | Ok (results, errors) -> List.concat results, errors | Error { Parser.Error.location; _ } -> [], [model_verification_error ~path ~location ParseError] in let is_obscure call_target = (* The callable is obscure if and only if it is a type stub or it is not in the set of known callables. *) Hash_set.mem stubs call_target || callables >>| Core.Fn.flip Hash_set.mem call_target >>| not |> Option.value ~default:false in let create_model_or_query = function | ParsedSignature ({ call_target; _ } as parsed_signature) -> create_model_from_signature ~resolution ~path ~configuration ~sources_to_keep ~sinks_to_keep ~is_obscure:(is_obscure (call_target :> Target.t)) parsed_signature | ParsedAttribute parsed_attribute -> create_model_from_attribute ~resolution ~path ~configuration ~sources_to_keep ~sinks_to_keep parsed_attribute | ParsedQuery query -> Core.Result.return (Query query) in List.rev_append (List.map errors ~f:(fun error -> Error error)) (List.map signatures_and_queries ~f:create_model_or_query) let get_model_sources ~paths = let path_and_content file = match File.content file with | Some content -> Some (File.path file, content) | None -> None in let model_files = PyrePath.get_matching_files_recursively ~suffix:".pysa" ~paths in Log.info "Finding taint models in `%s`." (paths |> List.map ~f:PyrePath.show |> String.concat ~sep:", "); model_files |> List.map ~f:File.create |> List.filter_map ~f:path_and_content let parse ~resolution ?path ?rule_filter ~source ~configuration ~callables ~stubs () = let new_models_and_queries, errors = create ~resolution ~path ~rule_filter ~configuration ~callables ~stubs source |> List.partition_result in let new_models, new_queries = List.fold new_models_and_queries ~f: (fun (models, queries) -> function | Model (model, skipped_override) -> (model, skipped_override) :: models, queries | Query query -> models, query :: queries) ~init:([], []) in { models = List.map new_models ~f:(fun (model, _) -> model.target, model.model) |> Target.Map.of_alist_reduce ~f:Model.join; skip_overrides = List.filter_map new_models ~f:(fun (_, skipped_override) -> skipped_override) |> Reference.Set.of_list; queries = new_queries; errors; } let invalid_model_query_error error = model_verification_error ~path:None ~location:Location.any error let create_callable_model_from_annotations ~resolution ~callable ~sources_to_keep ~sinks_to_keep ~is_obscure annotations = let open Core.Result in let open ModelVerifier in let global_resolution = Resolution.global_resolution resolution in match Interprocedural.Target.get_module_and_definition ~resolution:global_resolution callable with | None -> Error (invalid_model_query_error (NoCorrespondingCallable (Target.show callable))) | Some (_, { Node.value = { Define.signature = define; _ }; _ }) -> resolve_global_callable ~path:None ~location:Location.any ~resolution ~verify_decorators:false define >>| (function | Some (Global.Attribute (Type.Callable t)) | Some (Global.Attribute (Type.Parametric { name = "BoundMethod"; parameters = [Type.Parameter.Single (Type.Callable t); _]; })) -> Some t | _ -> None) >>= fun callable_annotation -> let default_model = if is_obscure then Model.obscure_model else Model.empty_model in List.fold annotations ~init:(Ok default_model) ~f:(fun accumulator (annotation_kind, annotation) -> accumulator >>= fun accumulator -> add_taint_annotation_to_model ~path:None ~location:Location.any ~model_name:"Model query" ~resolution:global_resolution ~annotation_kind ~callable_annotation ~sources_to_keep ~sinks_to_keep accumulator annotation) let create_attribute_model_from_annotations ~resolution ~name ~sources_to_keep ~sinks_to_keep annotations = let open Core.Result in let global_resolution = Resolution.global_resolution resolution in List.fold annotations ~init:(Ok Model.empty_model) ~f:(fun accumulator annotation -> accumulator >>= fun accumulator -> let annotation_kind = match annotation with | Source _ -> Ok ReturnAnnotation | Sink _ | Tito _ -> Ok (ParameterAnnotation (AccessPath.Root.PositionalParameter { position = 0; name = attribute_symbolic_parameter; positional_only = false })) | _ -> Error (invalid_model_query_error (InvalidAnnotationForAttributeModel { name; annotation = show_taint_annotation annotation })) in annotation_kind >>= fun annotation_kind -> add_taint_annotation_to_model ~path:None ~location:Location.any ~model_name:"Model query" ~resolution:global_resolution ~annotation_kind ~callable_annotation:None ~sources_to_keep ~sinks_to_keep accumulator annotation) let verify_model_syntax ~path ~source = match String.split ~on:'\n' source |> Parser.parse with | Ok _ -> () | Error { Parser.Error.location; _ } -> let error = model_verification_error ~path:(Some path) ~location ParseError in Yojson.Safe.pretty_to_string (`Assoc ["errors", `List [ModelVerificationError.to_json error]]) |> Log.print "%s"; exit (ExitStatus.exit_code ExitStatus.ModelVerificationError)