[quandary] Allow several kinds for external sources/sinks

Reviewed By: ngorogiannis

Differential Revision: D13417113

fbshipit-source-id: 0838c5704
master
Mehdi Bouaziz 6 years ago committed by Facebook Github Bot
parent 00b052826a
commit 87b3907628

@ -9,8 +9,8 @@ open! IStd
module F = Format
module L = Logging
let parse_clang_procedure procedure kind index =
try Some (QualifiedCppName.Match.of_fuzzy_qual_names [procedure], kind, index)
let parse_clang_procedure procedure kinds index =
try Some (QualifiedCppName.Match.of_fuzzy_qual_names [procedure], kinds, index)
with QualifiedCppName.ParseError _ ->
(* Java and Clang sources/sinks live in the same inferconfig entry. If we try to parse a Java
procedure that happens to be an invalid Clang qualified name (e.g., MyClass.<init>),
@ -49,21 +49,21 @@ module SourceKind = struct
let external_sources =
List.filter_map
~f:(fun {QuandaryConfig.Source.procedure; kind; index} ->
parse_clang_procedure procedure kind index )
~f:(fun {QuandaryConfig.Source.procedure; kinds; index} ->
parse_clang_procedure procedure kinds 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 *)
(* return a list of source kinds if [procedure_name] is in the list of externally specified sources *)
let get_external_source qualified_pname =
let return = None in
List.filter_map external_sources ~f:(fun (qualifiers, kind, index) ->
List.concat_map external_sources ~f:(fun (qualifiers, kinds, 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 )
List.rev_map kinds ~f:(fun kind -> (of_string kind, source_index))
else [] )
let get pname actuals tenv =
@ -235,45 +235,50 @@ module SinkKind = struct
let external_sinks =
List.filter_map
~f:(fun {QuandaryConfig.Sink.procedure; kind; index} ->
parse_clang_procedure procedure kind index )
~f:(fun {QuandaryConfig.Sink.procedure; kinds; index} ->
parse_clang_procedure procedure kinds index )
(QuandaryConfig.Sink.of_json Config.quandary_sinks)
(* taint the nth parameter (0-indexed) *)
let taint_nth n kind actuals =
if n < List.length actuals then [(kind, IntSet.singleton n)] else []
let taint_nth n kinds actuals =
if n < List.length actuals then
let indexes = IntSet.singleton n in
List.rev_map kinds ~f:(fun kind -> (kind, indexes))
else []
(* taint all parameters after the nth (exclusive) *)
let taint_after_nth n kind actuals =
let taint_after_nth n kinds actuals =
match
List.filter_mapi ~f:(fun actual_num _ -> Option.some_if (actual_num > n) actual_num) actuals
with
| [] ->
[]
| to_taint ->
[(kind, IntSet.of_list to_taint)]
let indexes = IntSet.of_list to_taint in
List.rev_map kinds ~f:(fun kind -> (kind, indexes))
let taint_all kind actuals =
[(kind, IntSet.of_list (List.mapi ~f:(fun actual_num _ -> actual_num) actuals))]
let taint_all kinds actuals =
let indexes = IntSet.of_list (List.mapi ~f:(fun actual_num _ -> actual_num) actuals) in
List.rev_map kinds ~f:(fun kind -> (kind, indexes))
(* return Some(sink kind) if [procedure_name] is in the list of externally specified sinks *)
(* return Some(sink kinds) 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) ->
~f:(fun (qualifiers, kinds, index) ->
if QualifiedCppName.Match.match_qualifiers qualifiers qualified_pname then
let kind = of_string kind in
let kinds = List.rev_map ~f:of_string kinds in
try
let n = int_of_string index in
let taint = taint_nth n kind actuals in
let taint = taint_nth n kinds actuals in
Option.some_if (not (List.is_empty taint)) taint
with Failure _ ->
(* couldn't parse the index, just taint everything *)
Some (taint_all kind actuals)
Some (taint_all kinds actuals)
else None )
external_sinks
|> Option.value ~default:[]
@ -301,24 +306,24 @@ module SinkKind = struct
with
| ( ["std"; ("basic_fstream" | "basic_ifstream" | "basic_ofstream")]
, ("basic_fstream" | "basic_ifstream" | "basic_ofstream" | "open") ) ->
taint_nth 1 CreateFile actuals
taint_nth 1 [CreateFile] actuals
| _, "operator[]" when Config.developer_mode && is_buffer_like pname ->
taint_nth 1 BufferAccess actuals
taint_nth 1 [BufferAccess] actuals
| _ ->
get_external_sink pname actuals )
| Typ.Procname.C _
when String.is_substring ~substring:"SetCommandLineOption" (Typ.Procname.to_string pname) ->
taint_nth 1 EnvironmentChange actuals
taint_nth 1 [EnvironmentChange] actuals
| Typ.Procname.C _
when Config.developer_mode && Typ.Procname.equal pname BuiltinDecl.__array_access ->
taint_all BufferAccess actuals
taint_all [BufferAccess] actuals
| Typ.Procname.C _ when Typ.Procname.equal pname BuiltinDecl.__set_array_length ->
(* called when creating a stack-allocated array *)
taint_nth 1 StackAllocation actuals
taint_nth 1 [StackAllocation] actuals
| Typ.Procname.C _ -> (
match Typ.Procname.to_string pname with
| "creat" | "fopen" | "freopen" | "open" ->
taint_nth 0 CreateFile actuals
taint_nth 0 [CreateFile] actuals
| "curl_easy_setopt" -> (
(* magic constant for setting request URL *)
let controls_request = function
@ -335,31 +340,31 @@ module SinkKind = struct
(* check if the data kind might be CURLOPT_URL *)
IntLit.to_int i
|> Option.value_map ~default:[] ~f:(fun n ->
if controls_request n then taint_after_nth 1 URL actuals else [] )
if controls_request n then taint_after_nth 1 [URL] actuals else [] )
| _ ->
(* can't statically resolve data kind; taint it just in case *)
taint_after_nth 1 URL actuals )
taint_after_nth 1 [URL] actuals )
| None ->
[] )
| "execl" | "execlp" | "execle" | "execv" | "execve" | "execvp" | "system" ->
taint_all ShellExec actuals
taint_all [ShellExec] actuals
| "openat" ->
taint_nth 1 CreateFile actuals
taint_nth 1 [CreateFile] actuals
| "popen" ->
taint_nth 0 ShellExec actuals
taint_nth 0 [ShellExec] actuals
| "putenv" ->
taint_nth 0 EnvironmentChange actuals
taint_nth 0 [EnvironmentChange] actuals
| ("brk" | "calloc" | "malloc" | "realloc" | "sbrk") when Config.developer_mode ->
taint_all HeapAllocation actuals
taint_all [HeapAllocation] actuals
| "rename" ->
taint_all CreateFile actuals
taint_all [CreateFile] actuals
| "strcpy" when Config.developer_mode ->
(* warn if source array is tainted *)
taint_nth 1 BufferAccess actuals
taint_nth 1 [BufferAccess] actuals
| ("memcpy" | "memmove" | "memset" | "strncpy" | "wmemcpy" | "wmemmove")
when Config.developer_mode ->
(* warn if count argument is tainted *)
taint_nth 2 BufferAccess actuals
taint_nth 2 [BufferAccess] actuals
| _ ->
get_external_sink pname actuals )
| Typ.Procname.Block _ ->

@ -42,7 +42,7 @@ module SourceKind = struct
let external_sources =
List.map
~f:(fun {QuandaryConfig.Source.procedure; kind} -> (Str.regexp procedure, kind))
~f:(fun {QuandaryConfig.Source.procedure; kinds} -> (Str.regexp procedure, kinds))
(QuandaryConfig.Source.of_json Config.quandary_sources)
@ -65,9 +65,10 @@ module SourceKind = struct
(* check the list of externally specified sources *)
let procedure = class_name ^ "." ^ method_name in
let sources =
List.filter_map external_sources ~f:(fun (procedure_regex, kind) ->
if Str.string_match procedure_regex procedure 0 then Some (of_string kind, return)
else None )
List.concat_map external_sources ~f:(fun (procedure_regex, kinds) ->
if Str.string_match procedure_regex procedure 0 then
List.rev_map kinds ~f:(fun kind -> (of_string kind, return))
else [] )
in
Option.some_if (not (List.is_empty sources)) sources
in
@ -314,7 +315,7 @@ module SinkKind = struct
let external_sinks =
List.map
~f:(fun {QuandaryConfig.Sink.procedure; kind; index} -> (Str.regexp procedure, kind, index))
~f:(fun {QuandaryConfig.Sink.procedure; kinds; index} -> (Str.regexp procedure, kinds, index))
(QuandaryConfig.Sink.of_json Config.quandary_sinks)
@ -323,7 +324,7 @@ module SinkKind = struct
| Typ.Procname.Java java_pname ->
(* taint all the inputs of [pname]. for non-static procedures, taints the "this" parameter
only if [taint_this] is true. *)
let taint_all ?(taint_this = false) kind =
let taint_all ?(taint_this = false) kinds =
let actuals_to_taint, offset =
if Typ.Procname.Java.is_static java_pname || taint_this then (actuals, 0)
else (List.tl_exn actuals, 1)
@ -331,42 +332,48 @@ module SinkKind = struct
let indexes =
IntSet.of_list (List.mapi ~f:(fun param_num _ -> param_num + offset) actuals_to_taint)
in
Some [(kind, indexes)]
Some (List.rev_map kinds ~f:(fun kind -> (kind, indexes)))
in
(* taint the nth non-"this" parameter (0-indexed) *)
let taint_nth n kind =
let taint_nth n kinds =
let first_index = if Typ.Procname.Java.is_static java_pname then n else n + 1 in
if first_index < List.length actuals then Some [(kind, IntSet.singleton first_index)]
if first_index < List.length actuals then
let first_index = IntSet.singleton first_index in
Some (List.rev_map kinds ~f:(fun kind -> (kind, first_index)))
else None
in
let get_external_sink class_name method_name =
(* check the list of externally specified sinks *)
let procedure = class_name ^ "." ^ method_name in
List.find_map
~f:(fun (procedure_regex, kind, index) ->
let sinks =
List.concat_map external_sinks ~f:(fun (procedure_regex, kinds, index) ->
if Str.string_match procedure_regex procedure 0 then
let kind = of_string kind in
let kinds = List.rev_map ~f:of_string kinds in
let taints =
try
let n = int_of_string index in
taint_nth n kind
taint_nth n kinds
with Failure _ ->
(* couldn't parse the index, just taint everything *)
taint_all kind
else None )
external_sinks
taint_all kinds
in
Option.value taints ~default:[]
else [] )
in
Option.some_if (not (List.is_empty sinks)) sinks
in
let method_name = Typ.Procname.Java.get_method java_pname in
let taint_matching_supertype typename =
match (Typ.Name.name typename, method_name) with
| "android.app.Activity", ("startActivityFromChild" | "startActivityFromFragment") ->
taint_nth 1 StartComponent
taint_nth 1 [StartComponent]
| ( ( "android.app.Activity"
| "android.content.Context"
| "android.support.v4.app.Fragment" )
, "startIntentSenderForResult" ) ->
taint_nth 2 StartComponent
taint_nth 2 [StartComponent]
| "android.app.Activity", "startIntentSenderFromChild" ->
taint_nth 3 StartComponent
taint_nth 3 [StartComponent]
| ( ( "android.app.Fragment"
| "android.content.Context"
| "android.support.v4.app.Fragment" )
@ -386,9 +393,9 @@ module SinkKind = struct
| "startNextMatchingActivity"
| "startService"
| "stopService" ) ) ->
taint_nth 0 StartComponent
taint_nth 0 [StartComponent]
| "android.content.Context", "startIntentSender" ->
taint_nth 1 StartComponent
taint_nth 1 [StartComponent]
| ( "android.content.Intent"
, ( "parseUri"
| "getIntent"
@ -399,13 +406,13 @@ module SinkKind = struct
| "setDataAndType"
| "setDataAndTypeAndNormalize"
| "setPackage" ) ) ->
taint_nth 0 CreateIntent
taint_nth 0 [CreateIntent]
| "android.content.Intent", "setClassName" ->
taint_all CreateIntent
taint_all [CreateIntent]
| "android.text.Html", "fromHtml" ->
taint_nth 0 HTML
taint_nth 0 [HTML]
| "android.util.Log", ("e" | "println" | "w" | "wtf") ->
taint_all Logging
taint_all [Logging]
| ( "android.webkit.WebView"
, ( "evaluateJavascript"
| "loadData"
@ -413,32 +420,32 @@ module SinkKind = struct
| "loadUrl"
| "postUrl"
| "postWebMessage" ) ) ->
taint_all JavaScript
taint_all [JavaScript]
| "com.facebook.infer.builtins.InferTaint", "inferSensitiveSink" ->
taint_nth 0 Other
taint_nth 0 [Other]
| "java.io.File", "<init>"
| "java.nio.file.FileSystem", "getPath"
| "java.nio.file.Paths", "get" ->
taint_all CreateFile
taint_all [CreateFile]
| "java.io.ObjectInputStream", "<init>" ->
taint_all Deserialization
taint_all [Deserialization]
| "java.lang.Class", "forName" | "java.lang.ClassLoader", "loadClass" ->
taint_nth 0 ClassLoading
taint_nth 0 [ClassLoading]
| "java.lang.ClassLoader", "defineClass" ->
taint_nth 1 ClassLoading
taint_nth 1 [ClassLoading]
| "java.lang.ProcessBuilder", "<init>" ->
taint_all ShellExec
taint_all [ShellExec]
| "java.lang.ProcessBuilder", "command" ->
taint_all ShellExec
taint_all [ShellExec]
| "java.lang.Runtime", "exec" ->
taint_nth 0 ShellExec
taint_nth 0 [ShellExec]
(* TODO: separate non-injection sinks for PreparedStatement's *)
| "java.sql.Statement", ("addBatch" | "execute") ->
taint_nth 0 SQLInjection
taint_nth 0 [SQLInjection]
| "java.sql.Statement", "executeQuery" ->
taint_nth 0 SQLRead
taint_nth 0 [SQLRead]
| "java.sql.Statement", ("executeUpdate" | "executeLargeUpdate") ->
taint_nth 0 SQLWrite
taint_nth 0 [SQLWrite]
| class_name, method_name ->
get_external_sink class_name method_name
in

@ -9,19 +9,28 @@ open! IStd
(** utilities for importing JSON specifications of sources/sinks into Quandary *)
let get_kinds json =
let open Yojson.Basic in
match (Util.member "kinds" json, Util.member "kind" json) with
| `Null, kind ->
[Util.to_string kind]
| kinds, kind ->
(Util.to_string_option kind |> Option.to_list) @ Util.convert_each Util.to_string kinds
module Source = struct
type t = {procedure: string; kind: string; index: string}
type t = {procedure: string; kinds: string list; index: 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 in
let kind = Util.member "kind" json |> Util.to_string in
let kinds = get_kinds json in
let index =
Util.member "index" json |> Util.to_string_option |> Option.value ~default:"return"
in
{procedure; kind; index}
{procedure; kinds; index}
in
List.map ~f:parse_source sources
| _ ->
@ -29,18 +38,18 @@ module Source = struct
end
module Sink = struct
type t = {procedure: string; kind: string; index: string}
type t = {procedure: string; kinds: string list; 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 in
let kind = Util.member "kind" json |> Util.to_string in
let kinds = get_kinds json in
let index =
Util.member "index" json |> Util.to_string_option |> Option.value ~default:"all"
in
{procedure; kind; index}
{procedure; kinds; index}
in
List.map ~f:parse_sink sinks
| _ ->

@ -10,13 +10,13 @@ open! IStd
(** utilities for importing JSON specifications of sources/sinks into Quandary*)
module Source : sig
type t = {procedure: string; kind: string; index: string}
type t = {procedure: string; kinds: string list; index: string}
val of_json : [> `List of Yojson.Basic.json list] -> t list
end
module Sink : sig
type t = {procedure: string; kind: string; index: string}
type t = {procedure: string; kinds: string list; index: string}
val of_json : [> `List of Yojson.Basic.json list] -> t list
end

@ -7,7 +7,7 @@
},
{
"procedure": "codetoanalyze.java.quandary.InterfaceSpec.source",
"kind": "PrivateData"
"kinds": ["PrivateData", "Other"]
}
],
"quandary-sinks": [

@ -47,11 +47,16 @@ codetoanalyze/java/quandary/Exceptions.java, codetoanalyze.java.quandary.Excepti
codetoanalyze/java/quandary/Exceptions.java, codetoanalyze.java.quandary.Exceptions.sinkInFinallyBad3():void, 7, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object InferTaint.inferSecretSource(),Call to void InferTaint.inferSensitiveSink(Object) with tainted index 0]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ConstructorSink.constructorSinkBad():codetoanalyze.java.quandary.ConstructorSink, 2, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object InferTaint.inferSecretSource(),Call to ConstructorSink.<init>(Object) with tainted index 1]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.callExternalSink2Bad1():void, 1, LOGGING_PRIVATE_DATA, no_bucket, ERROR, [Return from Object ExternalSpecs.privateDataSource(),Call to void ExternalSpecs.loggingSink2(Object,Object) with tainted index 0]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.callExternalSink2Bad1():void, 1, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object ExternalSpecs.privateDataSource(),Call to void ExternalSpecs.loggingSink2(Object,Object) with tainted index 0]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.callExternalSink2Bad2():void, 1, LOGGING_PRIVATE_DATA, no_bucket, ERROR, [Return from Object ExternalSpecs.privateDataSource(),Call to void ExternalSpecs.loggingSink2(Object,Object) with tainted index 1]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.callExternalSink2Bad2():void, 1, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object ExternalSpecs.privateDataSource(),Call to void ExternalSpecs.loggingSink2(Object,Object) with tainted index 1]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.callExternalSinkBad():void, 1, LOGGING_PRIVATE_DATA, no_bucket, ERROR, [Return from Object ExternalSpecs.privateDataSource(),Call to void ExternalSpecs.loggingSink1(Object,Object) with tainted index 1]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.callExternalSinkBad():void, 1, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object ExternalSpecs.privateDataSource(),Call to void ExternalSpecs.loggingSink1(Object,Object) with tainted index 1]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.callSinkThatPropagatesBad():void, 2, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object InferTaint.inferSecretSource(),Call to Object ExternalSpecs.sinkThatPropagates(Object) with tainted index 0]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.callSinkThatPropagatesBad():void, 3, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object InferTaint.inferSecretSource(),Call to void ExternalSpecs.loggingSink1(Object,Object) with tainted index 1]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.externalSourceAsIntentOk(android.app.Activity):void, 1, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object ExternalSpecs.privateDataSource(),Call to void Activity.startActivity(Intent) with tainted index 1]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.logExternalSourceBad():void, 1, LOGGING_PRIVATE_DATA, no_bucket, ERROR, [Return from Object ExternalSpecs.privateDataSource(),Call to int Log.e(String,String) with tainted index 1]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.logExternalSourceBad():void, 1, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object ExternalSpecs.privateDataSource(),Call to int Log.e(String,String) with tainted index 1]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.ExternalSpecs.missedSanitizerBad():java.lang.Object, 3, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object InferTaint.inferSecretSource(),Call to void InferTaint.inferSensitiveSink(Object) with tainted index 0]
codetoanalyze/java/quandary/ExternalSpecs.java, codetoanalyze.java.quandary.InterfaceSpecImpl.externalSpecBad():void, 1, LOGGING_PRIVATE_DATA, no_bucket, ERROR, [Return from Object InterfaceSpecImpl.source(),Call to void InterfaceSpecImpl.sink(Object) with tainted index 1]
codetoanalyze/java/quandary/Fields.java, codetoanalyze.java.quandary.Fields.instanceFieldBad():void, 2, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object InferTaint.inferSecretSource(),Call to void InferTaint.inferSensitiveSink(Object) with tainted index 0]

Loading…
Cancel
Save