|
|
|
(*
|
|
|
|
* 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! IStd
|
|
|
|
|
|
|
|
module F = Format
|
|
|
|
module L = Logging
|
|
|
|
|
|
|
|
module SourceKind = struct
|
|
|
|
type t =
|
|
|
|
| Endpoint (** source originating from an endpoint *)
|
|
|
|
| EnvironmentVariable (** source that was read from an environment variable *)
|
|
|
|
| File (** source that was read from a file *)
|
|
|
|
| Other (** for testing or uncategorized sources *)
|
|
|
|
| Unknown
|
|
|
|
[@@deriving compare]
|
|
|
|
|
|
|
|
let unknown = Unknown
|
|
|
|
|
|
|
|
let of_string = function
|
|
|
|
| "Endpoint" -> Endpoint
|
|
|
|
| "EnvironmentVariable" -> EnvironmentVariable
|
|
|
|
| "File" -> File
|
|
|
|
| _ -> Other
|
|
|
|
|
|
|
|
let external_sources =
|
|
|
|
List.map
|
|
|
|
~f:(fun { QuandaryConfig.Source.procedure; kind; index; } ->
|
|
|
|
QualifiedCppName.Match.of_fuzzy_qual_names [procedure], kind, index)
|
|
|
|
(QuandaryConfig.Source.of_json Config.quandary_sources)
|
|
|
|
|
|
|
|
let endpoints = String.Set.of_list (QuandaryConfig.Endpoint.of_json Config.quandary_endpoints)
|
|
|
|
|
|
|
|
(* return Some(source kind) if [procedure_name] is in the list of externally specified sources *)
|
|
|
|
let get_external_source qualified_pname =
|
|
|
|
let return = None in
|
|
|
|
List.find_map
|
|
|
|
~f:(fun (qualifiers, kind, index) ->
|
|
|
|
if QualifiedCppName.Match.match_qualifiers qualifiers qualified_pname
|
|
|
|
then
|
|
|
|
let source_index =
|
|
|
|
try Some (int_of_string index)
|
|
|
|
with Failure _ -> return in
|
|
|
|
Some (of_string kind, source_index)
|
|
|
|
else None)
|
|
|
|
external_sources
|
|
|
|
|
|
|
|
let get pname _ =
|
|
|
|
let return = None in
|
|
|
|
match pname with
|
|
|
|
| Typ.Procname.ObjC_Cpp cpp_name ->
|
|
|
|
let qualified_pname = Typ.Procname.get_qualifiers pname in
|
|
|
|
begin
|
|
|
|
match
|
|
|
|
(QualifiedCppName.to_list
|
|
|
|
(Typ.Name.unqualified_name (Typ.Procname.objc_cpp_get_class_type_name cpp_name))),
|
|
|
|
Typ.Procname.get_method pname with
|
|
|
|
| ["std"; ("basic_istream" | "basic_iostream")],
|
|
|
|
("getline" | "read" | "readsome" | "operator>>") ->
|
|
|
|
Some (File, Some 1)
|
|
|
|
| _ ->
|
|
|
|
get_external_source qualified_pname
|
|
|
|
end
|
|
|
|
| Typ.Procname.C _ ->
|
|
|
|
begin
|
|
|
|
match Typ.Procname.to_string pname with
|
|
|
|
| "getenv" ->
|
|
|
|
Some (EnvironmentVariable, return)
|
|
|
|
| _ ->
|
|
|
|
get_external_source (Typ.Procname.get_qualifiers pname)
|
|
|
|
end
|
|
|
|
| Typ.Procname.Block _ ->
|
|
|
|
None
|
|
|
|
| pname when BuiltinDecl.is_declared pname ->
|
|
|
|
None
|
|
|
|
| pname ->
|
|
|
|
failwithf "Non-C++ procname %a in C++ analysis@." Typ.Procname.pp pname
|
|
|
|
|
|
|
|
let get_tainted_formals pdesc _ =
|
|
|
|
match Procdesc.get_proc_name pdesc with
|
|
|
|
| (Typ.Procname.ObjC_Cpp objc) as pname ->
|
|
|
|
let qualified_pname =
|
|
|
|
F.sprintf "%s::%s"
|
|
|
|
(Typ.Procname.objc_cpp_get_class_name objc)
|
|
|
|
(Typ.Procname.get_method pname) in
|
|
|
|
if String.Set.mem endpoints qualified_pname
|
|
|
|
then List.map ~f:(fun (name, typ) -> name, typ, Some Endpoint) (Procdesc.get_formals pdesc)
|
|
|
|
else Source.all_formals_untainted pdesc
|
|
|
|
| _ ->
|
|
|
|
Source.all_formals_untainted pdesc
|
|
|
|
|
|
|
|
let pp fmt kind =
|
|
|
|
F.fprintf fmt
|
|
|
|
(match kind with
|
|
|
|
| Endpoint -> "Endpoint"
|
|
|
|
| EnvironmentVariable -> "EnvironmentVariable"
|
|
|
|
| File -> "File"
|
|
|
|
| Other -> "Other"
|
|
|
|
| Unknown -> "Unknown")
|
|
|
|
end
|
|
|
|
|
|
|
|
module CppSource = Source.Make(SourceKind)
|
|
|
|
|
|
|
|
module SinkKind = struct
|
|
|
|
|
|
|
|
type t =
|
|
|
|
| Allocation (** memory allocation *)
|
|
|
|
| ShellExec (** shell exec function *)
|
|
|
|
| Other (** for testing or uncategorized sinks *)
|
|
|
|
[@@deriving compare]
|
|
|
|
|
|
|
|
let of_string = function
|
|
|
|
| "Allocation" -> Allocation
|
|
|
|
| "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 _ =
|
|
|
|
match pname with
|
|
|
|
| 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" | "execve" | "execvp" | "system" ->
|
|
|
|
taint_all actuals ShellExec ~report_reachable:false
|
|
|
|
| "brk" | "calloc" | "malloc" | "realloc" | "sbrk" ->
|
|
|
|
taint_all actuals Allocation ~report_reachable:false
|
|
|
|
| _ ->
|
|
|
|
Option.value (get_external_sink pname actuals) ~default:[]
|
|
|
|
end
|
|
|
|
| Typ.Procname.Block _ ->
|
|
|
|
[]
|
|
|
|
| pname when BuiltinDecl.is_declared pname ->
|
|
|
|
[]
|
|
|
|
| pname ->
|
|
|
|
failwithf "Non-C++ procname %a in C++ analysis@." Typ.Procname.pp pname
|
|
|
|
|
|
|
|
let pp fmt kind =
|
|
|
|
F.fprintf fmt
|
|
|
|
(match kind with
|
|
|
|
| Allocation -> "Allocation"
|
|
|
|
| ShellExec -> "ShellExec"
|
|
|
|
| Other -> "Other")
|
|
|
|
end
|
|
|
|
|
|
|
|
module CppSink = Sink.Make(SinkKind)
|
|
|
|
|
|
|
|
include
|
|
|
|
Trace.Make(struct
|
|
|
|
module Source = CppSource
|
|
|
|
module Sink = CppSink
|
|
|
|
|
|
|
|
let should_report source sink =
|
|
|
|
match Source.kind source, Sink.kind sink with
|
|
|
|
| (Endpoint | EnvironmentVariable | File), ShellExec ->
|
|
|
|
(* untrusted data flowing to exec *)
|
|
|
|
true
|
|
|
|
| (Endpoint | EnvironmentVariable | File), Allocation ->
|
|
|
|
(* untrusted data flowing to memory allocation *)
|
|
|
|
true
|
|
|
|
| _, (Allocation | Other | ShellExec) when Source.is_footprint source ->
|
|
|
|
(* is this var a command line flag created by the popular gflags library? *)
|
|
|
|
let is_gflag pvar =
|
|
|
|
String.is_substring ~substring:"FLAGS_" (Pvar.get_simplified_name pvar) in
|
|
|
|
begin
|
|
|
|
match Option.map ~f:AccessPath.extract (Source.get_footprint_access_path source) with
|
|
|
|
| Some ((Var.ProgramVar pvar, _), _) when Pvar.is_global pvar && is_gflag pvar ->
|
|
|
|
(* gflags globals come from the environment; treat them as sources *)
|
|
|
|
true
|
|
|
|
| _ ->
|
|
|
|
false
|
|
|
|
end
|
|
|
|
| Other, _ ->
|
|
|
|
(* Other matches everything *)
|
|
|
|
true
|
|
|
|
| _, Other ->
|
|
|
|
true
|
|
|
|
| Unknown, (Allocation | ShellExec) ->
|
|
|
|
false
|
|
|
|
end)
|