diff --git a/infer/src/backend/prettyPrintable.mli b/infer/src/backend/prettyPrintable.mli index 27dc17f79..72829468e 100644 --- a/infer/src/backend/prettyPrintable.mli +++ b/infer/src/backend/prettyPrintable.mli @@ -13,6 +13,8 @@ module F = Format (** Wrappers for making pretty-printable modules *) +val pp_collection : pp_item:(F.formatter -> 'a -> unit) -> F.formatter -> 'a list -> unit + module type SetOrderedType = sig type t val compare : t -> t -> int diff --git a/infer/src/quandary/TaintAnalysis.ml b/infer/src/quandary/TaintAnalysis.ml index c1eb98b9d..3e04c35e7 100644 --- a/infer/src/quandary/TaintAnalysis.ml +++ b/infer/src/quandary/TaintAnalysis.ml @@ -58,4 +58,64 @@ module Make (TraceDomain : Trace.S) = struct F.fprintf fmt "(%a, %a)" TaintDomain.pp access_tree IdMapDomain.pp id_map end + module TransferFunctions (CFG : ProcCfg.S) = struct + module CFG = CFG + module Domain = Domain + + type formal_list = AccessPath.base list + type extras = formal_list + + let add_source source ret_id ret_typ access_tree = + let trace = TraceDomain.of_source source in + let id_ap = AccessPath.Exact (AccessPath.of_id ret_id ret_typ) in + TaintDomain.add_trace id_ap trace access_tree + + let exec_instr ({ Domain.access_tree; } as astate) proc_data _ instr = + match instr with + | Sil.Call (ret_ids, Const (Cfun callee_pname), _, callee_loc, _) -> + let call_site = CallSite.make callee_pname callee_loc in + let ret_typ = + match callee_pname with + | Procname.Java java_pname -> + let ret_typ_str = Procname.java_get_return_type java_pname in + Option.default + Typ.Tvoid + (Tenv.lookup_java_typ_from_string (proc_data.ProcData.tenv) ret_typ_str) + | Procname.C _ -> + Typ.Tvoid (* for tests only, since tests use C-style procnames *) + | _ -> + failwith "Unimp: looking up return type for non-Java procedure" in + + let astate_with_source = + match TraceDomain.Source.get call_site, ret_ids with + | [(0, source)], [ret_id] -> + let access_tree' = add_source source ret_id ret_typ access_tree in + { astate with Domain.access_tree = access_tree'; } + | [], _ | _, [] -> + astate + | _ -> + (* this is allowed by SIL, but not currently used in any frontends *) + failwith "Unimp: handling multiple return ids" in + astate_with_source + | Sil.Call _ -> + failwith "Unimp: non-pname call expressions" + | Sil.Letderef _ | Set _ -> + failwith "Unimp: assignment, load, and store" + | Sil.Prune _ | Remove_temps _ | Nullify _ | Abstract _ | Stackop _ | Declare_locals _ -> + astate + end + + module Analyzer = AbstractInterpreter.Make + (ProcCfg.Normal) + (Scheduler.ReversePostorder) + (TransferFunctions) + + let checker { Callbacks.proc_name; proc_desc; tenv; } = + let formals = + let attrs = Cfg.Procdesc.get_attributes proc_desc in + IList.map + (fun (name, typ) -> AccessPath.base_of_pvar (Pvar.mk name proc_name) typ) + attrs.formals in + let proc_data = ProcData.make proc_desc tenv formals in + ignore (Analyzer.compute_post proc_data) end diff --git a/infer/src/unit/TaintTests.ml b/infer/src/unit/TaintTests.ml new file mode 100644 index 000000000..95d5746ae --- /dev/null +++ b/infer/src/unit/TaintTests.ml @@ -0,0 +1,128 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! Utils + +module F = Format + +module MockTrace = Trace.Make(struct + module MockTraceElem = struct + type kind = unit + type t = CallSite.t + let call_site t = t + let kind _ = () + let make _ site = site + let compare = CallSite.compare + let equal = CallSite.equal + let pp = CallSite.pp + + let to_callee t _ = t + + module Set = PrettyPrintable.MakePPSet(struct + type nonrec t = t + let compare = compare + let pp_element = pp + end) + + end + + module Source = struct + include MockTraceElem + + let get site = + if string_is_prefix "SOURCE" (Procname.to_string (CallSite.pname site)) + then [(0, site)] + else [] + + let is_footprint _ = assert false + let make_footprint _ = assert false + let get_footprint_access_path _ = assert false + let to_return _ _ = assert false + end + + module Sink = struct + include MockTraceElem + + let get site = + if string_is_prefix "SINK" (Procname.to_string (CallSite.pname site)) + then [(0, site)] + else [] + end + + let should_report _ _ = true + end) + +module MockTaintAnalysis = TaintAnalysis.Make(MockTrace) + +module TestInterpreter = AnalyzerTester.Make + (ProcCfg.Normal) + (Scheduler.ReversePostorder) + (MockTaintAnalysis.TransferFunctions) + +let tests = + let open OUnit2 in + let open AnalyzerTester.StructuredSil in + (* less verbose form of pretty-printing to make writing tests easy *) + let pp_sparse fmt astate = + let pp_call_site fmt call_site = + F.fprintf fmt "%a" Procname.pp (CallSite.pname call_site) in + let pp_sources fmt sources = + if MockTrace.Sources.is_empty sources + then F.fprintf fmt "?" + else + MockTrace.Sources.iter + (fun source -> pp_call_site fmt (MockTrace.Source.call_site source)) + sources in + let pp_sinks fmt sinks = + if MockTrace.Sinks.is_empty sinks + then F.fprintf fmt "?" + else + MockTrace.Sinks.iter + (fun sink -> + pp_call_site fmt (MockTrace.Sink.call_site sink)) + sinks in + (* just print source -> sink, no line nums or passthroughs *) + let pp_trace fmt trace = + F.fprintf + fmt + "(%a -> %a)" + pp_sources (MockTrace.sources trace) + pp_sinks (MockTrace.sinks trace) in + let pp_item fmt (ap, trace) = + F.fprintf fmt "%a => %a" AccessPath.pp ap pp_trace trace in + (* flatten access tree into list of access paths with associated traces *) + let trace_assocs = + MockTaintAnalysis.TaintDomain.fold + (fun acc ap t -> + if not (MockTrace.is_empty t) + then (ap, t) :: acc + else acc) + astate.MockTaintAnalysis.Domain.access_tree + [] in + PrettyPrintable.pp_collection ~pp_item fmt (IList.rev trace_assocs) in + let assign_to_source ret_str = + let procname = Procname.from_string_c_fun "SOURCE" in + make_call ~procname [ident_of_str ret_str] [] in + let assign_to_non_source ret_str = + let procname = Procname.from_string_c_fun "NON-SOURCE" in + make_call ~procname [ident_of_str ret_str] [] in + let assert_empty = invariant "{ }" in + let test_list = [ + "source recorded", + [ + assign_to_source "ret_id"; + invariant "{ ret_id$0 => (SOURCE -> ?) }"; + ]; + "non-source not recorded", + [ + assign_to_non_source "ret_id"; + assert_empty; + ]; + ] |> TestInterpreter.create_tests ~pp_opt:pp_sparse [] in + "taint_test_suite">:::test_list diff --git a/infer/src/unit/inferunit.ml b/infer/src/unit/inferunit.ml index 5a7a52525..afa9d9989 100644 --- a/infer/src/unit/inferunit.ml +++ b/infer/src/unit/inferunit.ml @@ -24,6 +24,7 @@ let () = LivenessTests.tests; SchedulerTests.tests; StacktraceTests.tests; + TaintTests.tests; TraceTests.tests; ] in let test_suite = "all" >::: tests in