From a02b37a03cfbf3074cc6f73005d781368d493d47 Mon Sep 17 00:00:00 2001 From: Sam Blackshear Date: Wed, 3 May 2017 09:53:19 -0700 Subject: [PATCH] [quandary] allow custom sources/sinks in C++ Reviewed By: akotulski Differential Revision: D4975414 fbshipit-source-id: 4fd7078 --- infer/src/quandary/ClangTrace.ml | 91 ++++++++++++++----- infer/src/quandary/JavaTrace.ml | 25 +++-- infer/src/quandary/QuandaryConfig.ml | 8 +- infer/src/quandary/QuandaryConfig.mli | 4 +- .../codetoanalyze/cpp/quandary/.inferconfig | 37 ++++++++ .../codetoanalyze/cpp/quandary/basics.cpp | 27 +++++- .../codetoanalyze/cpp/quandary/issues.exp | 3 + 7 files changed, 154 insertions(+), 41 deletions(-) create mode 100644 infer/tests/codetoanalyze/cpp/quandary/.inferconfig diff --git a/infer/src/quandary/ClangTrace.ml b/infer/src/quandary/ClangTrace.ml index a46a77c44..19868cb1a 100644 --- a/infer/src/quandary/ClangTrace.ml +++ b/infer/src/quandary/ClangTrace.ml @@ -12,8 +12,7 @@ open! IStd module F = Format module L = Logging - -module Kind = struct +module SourceKind = struct type t = | EnvironmentVariable (** source that was read from an environment variable *) | Other (** for testing or uncategorized sources *) @@ -22,19 +21,34 @@ module Kind = struct let unknown = Unknown + let of_string = function + | "EnvironmentVariable" -> EnvironmentVariable + | _ -> Other + + let external_sources = + List.map + ~f:(fun { QuandaryConfig.Source.procedure; kind; } -> + QualifiedCppName.Match.of_fuzzy_qual_names [procedure], kind) + (QuandaryConfig.Source.of_json Config.quandary_sources) + + (* return Some(source kind) if [procedure_name] is in the list of externally specified sources *) + let get_external_source pname = + let qualified_pname = Typ.Procname.get_qualifiers pname in + List.find_map + ~f:(fun (qualifiers, kind) -> + if QualifiedCppName.Match.match_qualifiers qualifiers qualified_pname + then Some (of_string kind) + else None) + external_sources + let get pname _ = match pname with - | (Typ.Procname.ObjC_Cpp cpp_pname) as pname -> - begin - match Typ.Procname.objc_cpp_get_class_name cpp_pname, Typ.Procname.get_method pname with - | "InferTaint", "source" -> Some Other - | _ -> None - end - | (Typ.Procname.C _) as pname -> + | Typ.Procname.ObjC_Cpp _ -> + get_external_source pname + | Typ.Procname.C _ -> begin match Typ.Procname.to_string pname with | "getenv" -> Some EnvironmentVariable - | "__infer_taint_source" -> Some Other - | _ -> None + | _ -> get_external_source pname end | Typ.Procname.Block _ -> None @@ -52,7 +66,7 @@ module Kind = struct | Unknown -> F.fprintf fmt "Unknown" end -module CppSource = Source.Make(Kind) +module CppSource = Source.Make(SourceKind) module SinkKind = struct @@ -61,27 +75,54 @@ module SinkKind = struct | Other (** for testing or uncategorized sinks *) [@@deriving compare] + let of_string = function + | "ShellExec" -> ShellExec + | _ -> Other + + let external_sinks = + List.map + ~f:(fun { QuandaryConfig.Sink.procedure; kind; index; } -> + QualifiedCppName.Match.of_fuzzy_qual_names [procedure], kind, index) + (QuandaryConfig.Sink.of_json Config.quandary_sinks) + + (* taint the nth parameter (0-indexed) *) + let taint_nth n kind ~report_reachable = + [kind, n, report_reachable] + + let taint_all actuals kind ~report_reachable = + List.mapi + ~f:(fun actual_num _ -> kind, actual_num, report_reachable) + actuals + + (* return Some(sink kind) if [procedure_name] is in the list of externally specified sinks *) + let get_external_sink pname actuals = + let qualified_pname = Typ.Procname.get_qualifiers pname in + List.find_map + ~f:(fun (qualifiers, kind, index) -> + if QualifiedCppName.Match.match_qualifiers qualifiers qualified_pname + then + let kind = of_string kind in + try + let n = int_of_string index in + Some (taint_nth n kind ~report_reachable:true) + with Failure _ -> + (* couldn't parse the index, just taint everything *) + Some (taint_all actuals kind ~report_reachable:true) + else + None) + external_sinks + let get pname actuals _ = - let taint_all actuals kind ~report_reachable = - List.mapi - ~f:(fun actual_num _ -> kind, actual_num, report_reachable) - actuals in match pname with - | (Typ.Procname.ObjC_Cpp cpp_pname) as pname -> - begin - match Typ.Procname.objc_cpp_get_class_name cpp_pname, Typ.Procname.get_method pname with - | "InferTaint", "sink:" -> taint_all actuals Other ~report_reachable:true - | _ -> [] - end + | Typ.Procname.ObjC_Cpp _ -> + Option.value (get_external_sink pname actuals) ~default:[] | Typ.Procname.C _ -> begin match Typ.Procname.to_string pname with | "execl" | "execlp" | "execle" | "execv" | "execvp" -> taint_all actuals ShellExec ~report_reachable:false - | "__infer_taint_sink" -> - [Other, 0, false] | _ -> - [] + Option.value (get_external_sink pname actuals) ~default:[] end | Typ.Procname.Block _ -> [] diff --git a/infer/src/quandary/JavaTrace.ml b/infer/src/quandary/JavaTrace.ml index 478365006..150252696 100644 --- a/infer/src/quandary/JavaTrace.ml +++ b/infer/src/quandary/JavaTrace.ml @@ -29,7 +29,10 @@ module SourceKind = struct | "UserControlledURI" -> UserControlledURI | _ -> Other - let external_sources = QuandaryConfig.Source.of_json Config.quandary_sources + let external_sources = + List.map + ~f:(fun { QuandaryConfig.Source.procedure; kind; } -> Str.regexp procedure, kind) + (QuandaryConfig.Source.of_json Config.quandary_sources) let get pname tenv = match pname with | Typ.Procname.Java pname -> @@ -70,9 +73,9 @@ module SourceKind = struct (* check the list of externally specified sources *) let procedure = class_name ^ "." ^ method_name in List.find_map - ~f:(fun (source_spec : QuandaryConfig.Source.t) -> - if Str.string_match source_spec.procedure procedure 0 - then Some (of_string source_spec.kind) + ~f:(fun (procedure_regex, kind) -> + if Str.string_match procedure_regex procedure 0 + then Some (of_string kind) else None) external_sources end @@ -193,7 +196,11 @@ module SinkKind = struct | "StartComponent" -> StartComponent | _ -> Other - let external_sinks = QuandaryConfig.Sink.of_json Config.quandary_sinks + let external_sinks = + List.map + ~f:(fun { QuandaryConfig.Sink.procedure; kind; index; } -> + Str.regexp procedure, kind, index) + (QuandaryConfig.Sink.of_json Config.quandary_sinks) let get pname actuals tenv = (* taint all the inputs of [pname]. for non-static procedures, taints the "this" parameter only @@ -278,12 +285,12 @@ module SinkKind = struct (* check the list of externally specified sinks *) let procedure = class_name ^ "." ^ method_name in List.find_map - ~f:(fun (sink_spec : QuandaryConfig.Sink.t) -> - if Str.string_match sink_spec.procedure procedure 0 + ~f:(fun (procedure_regex, kind, index) -> + if Str.string_match procedure_regex procedure 0 then - let kind = of_string sink_spec.kind in + let kind = of_string kind in try - let n = int_of_string sink_spec.index in + let n = int_of_string index in Some (taint_nth n kind ~report_reachable:true) with Failure _ -> (* couldn't parse the index, just taint everything *) diff --git a/infer/src/quandary/QuandaryConfig.ml b/infer/src/quandary/QuandaryConfig.ml index 157e74697..bd0e5e3da 100644 --- a/infer/src/quandary/QuandaryConfig.ml +++ b/infer/src/quandary/QuandaryConfig.ml @@ -14,13 +14,13 @@ module F = Format (** utilities for importing JSON specifications of sources/sinks into Quandary *) module Source = struct - type t = { procedure : Str.regexp; kind : string; } + type t = { procedure : string; kind : string; } let of_json = function | `List sources -> let parse_source json = let open Yojson.Basic in - let procedure = Util.member "procedure" json |> Util.to_string |> Str.regexp in + let procedure = Util.member "procedure" json |> Util.to_string in let kind = Util.member "kind" json |> Util.to_string in { procedure; kind; } in List.map ~f:parse_source sources @@ -29,13 +29,13 @@ module Source = struct end module Sink = struct - type t = { procedure : Str.regexp; kind : string; index : string} + type t = { procedure : string; kind : string; index : string} let of_json = function | `List sinks -> let parse_sink json = let open Yojson.Basic in - let procedure = Util.member "procedure" json |> Util.to_string |> Str.regexp in + let procedure = Util.member "procedure" json |> Util.to_string in let kind = Util.member "kind" json |> Util.to_string in let index = Util.member "index" json |> Util.to_string_option |> Option.value ~default:"all" in diff --git a/infer/src/quandary/QuandaryConfig.mli b/infer/src/quandary/QuandaryConfig.mli index d84411a5d..4b2cf2f29 100644 --- a/infer/src/quandary/QuandaryConfig.mli +++ b/infer/src/quandary/QuandaryConfig.mli @@ -12,13 +12,13 @@ open! IStd (** utilities for importing JSON specifications of sources/sinks into Quandary*) module Source : sig - type t = { procedure : Str.regexp; kind : string; } + type t = { procedure : string; kind : string; } val of_json : [> `List of Yojson.Basic.json list ] -> t list end module Sink : sig - type t = { procedure : Str.regexp; kind : string; index : string; } + type t = { procedure : string; kind : string; index : string; } val of_json : [> `List of Yojson.Basic.json list ] -> t list end diff --git a/infer/tests/codetoanalyze/cpp/quandary/.inferconfig b/infer/tests/codetoanalyze/cpp/quandary/.inferconfig new file mode 100644 index 000000000..8569ad047 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/quandary/.inferconfig @@ -0,0 +1,37 @@ +{ + "quandary-sources": [ + { + "procedure": "__infer_taint_source", + "kind": "Other" + }, + { + "procedure": "basics::Obj::method_source", + "kind": "Other" + }, + { + "procedure": "basics::Obj::static_source", + "kind": "Other" + }, + { + "procedure": "basics::template_source", + "kind": "Other" + } + ], + "quandary-sinks": [ + { + "procedure": "__infer_taint_sink", + "kind": "Other", + "index": "0" + }, + { + "procedure": "basics::Obj::method_sink", + "kind": "Other", + "index": "1" + }, + { + "procedure": "basics::Obj::static_sink", + "kind": "Other", + "index": "0" + } + ] +} diff --git a/infer/tests/codetoanalyze/cpp/quandary/basics.cpp b/infer/tests/codetoanalyze/cpp/quandary/basics.cpp index 15cfd8478..5fae91906 100644 --- a/infer/tests/codetoanalyze/cpp/quandary/basics.cpp +++ b/infer/tests/codetoanalyze/cpp/quandary/basics.cpp @@ -14,7 +14,10 @@ namespace basics { class Obj { public: - int field; + void* method_source() { return (void*)0; } + void method_sink(void*) {} + static void* static_source() { return (void*)0; } + static void static_sink(void*) {} }; void* returnSource() { return __infer_taint_source(); } @@ -43,4 +46,26 @@ void propagateBad() { void* launderedSource = id(source); callSink(launderedSource); } + +// make sure specifying external sources/sinks as instance methods works +void object_source_sink_bad(Obj obj) { + void* source = obj.method_source(); + obj.method_sink(source); +} + +// make sure specifying external sources/sinks as static methods works +void static_source_sink_bad(Obj obj) { + void* source = Obj::static_source(); + Obj::static_sink(source); +} + +template +T* template_source() { + return nullptr; +} + +void template_source_bad() { + void* source = template_source(); + __infer_taint_sink(source); +} } diff --git a/infer/tests/codetoanalyze/cpp/quandary/issues.exp b/infer/tests/codetoanalyze/cpp/quandary/issues.exp index df449caaa..fdf7f43f8 100644 --- a/infer/tests/codetoanalyze/cpp/quandary/issues.exp +++ b/infer/tests/codetoanalyze/cpp/quandary/issues.exp @@ -1,7 +1,10 @@ +codetoanalyze/cpp/quandary/basics.cpp, basics::object_source_sink_bad, 2, QUANDARY_TAINT_ERROR, [return from basics::Obj_method_source,call to basics::Obj_method_sink] codetoanalyze/cpp/quandary/basics.cpp, basics::propagateBad, 3, QUANDARY_TAINT_ERROR, [return from __infer_taint_source,flow through basics::id,call to basics::callSink,call to __infer_taint_sink] codetoanalyze/cpp/quandary/basics.cpp, basics::returnSourceToSinkBad, 2, QUANDARY_TAINT_ERROR, [return from __infer_taint_source,return from basics::returnSource,call to __infer_taint_sink] codetoanalyze/cpp/quandary/basics.cpp, basics::sourceThenCallSinkBad, 2, QUANDARY_TAINT_ERROR, [return from __infer_taint_source,call to basics::callSink,call to __infer_taint_sink] codetoanalyze/cpp/quandary/basics.cpp, basics::sourceToSinkDirectBad, 2, QUANDARY_TAINT_ERROR, [return from __infer_taint_source,call to __infer_taint_sink] +codetoanalyze/cpp/quandary/basics.cpp, basics::static_source_sink_bad, 2, QUANDARY_TAINT_ERROR, [return from basics::Obj_static_source,call to basics::Obj_static_sink] +codetoanalyze/cpp/quandary/basics.cpp, basics::template_source_bad, 2, QUANDARY_TAINT_ERROR, [return from basics::template_source,call to __infer_taint_sink] codetoanalyze/cpp/quandary/execs.cpp, execs::callExecBad, 6, QUANDARY_TAINT_ERROR, [return from getenv,call to execl] codetoanalyze/cpp/quandary/execs.cpp, execs::callExecBad, 8, QUANDARY_TAINT_ERROR, [return from getenv,call to execl] codetoanalyze/cpp/quandary/execs.cpp, execs::callExecBad, 11, QUANDARY_TAINT_ERROR, [return from getenv,call to execl]