You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

351 lines
14 KiB

(*
* 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 =
| Intent (** external Intent or a value read from one *)
| Other (** for testing or uncategorized sources *)
| PrivateData (** private user or device-specific data *)
| UserControlledString (** data read from a text box or the clipboard service *)
| UserControlledURI (** resource locator from the browser bar *)
[@@deriving compare]
let of_string = function
| "Intent"
-> Intent
| "PrivateData"
-> PrivateData
| "UserControlledURI"
-> UserControlledURI
| "UserControlledString"
-> UserControlledString
| _
-> Other
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 =
let return = None in
match pname with
| Typ.Procname.Java pname -> (
match (Typ.Procname.java_get_class_name pname, Typ.Procname.java_get_method pname) with
| ( "android.location.Location"
, ("getAltitude" | "getBearing" | "getLatitude" | "getLongitude" | "getSpeed") )
-> Some (PrivateData, return)
| ( "android.telephony.TelephonyManager"
, ( "getDeviceId" | "getLine1Number" | "getSimSerialNumber" | "getSubscriberId"
| "getVoiceMailNumber" ) )
-> Some (PrivateData, return)
| "com.facebook.infer.builtins.InferTaint", "inferSecretSource"
-> Some (Other, return)
| class_name, method_name
-> let taint_matching_supertype typename =
match (Typ.Name.name typename, method_name) with
| "android.app.Activity", "getIntent"
-> Some (Intent, return)
| "android.content.Intent", "getStringExtra"
-> Some (Intent, return)
| "android.content.SharedPreferences", "getString"
-> Some (PrivateData, return)
| ( ("android.content.ClipboardManager" | "android.text.ClipboardManager")
, ("getPrimaryClip" | "getText") )
-> Some (UserControlledString, return)
| "android.widget.EditText", "getText"
-> Some (UserControlledString, return)
| _
-> None
in
let kind_opt =
PatternMatch.supertype_find_map_opt tenv taint_matching_supertype
(Typ.Name.Java.from_string class_name)
in
match kind_opt with
| Some _
-> kind_opt
| None
-> (* check the list of externally specified sources *)
let procedure = class_name ^ "." ^ method_name in
List.find_map
~f:(fun (procedure_regex, kind) ->
if Str.string_match procedure_regex procedure 0 then Some (of_string kind, return)
else None)
external_sources )
| pname when BuiltinDecl.is_declared pname
-> None
| pname
-> L.(die InternalError) "Non-Java procname %a in Java analysis" Typ.Procname.pp pname
let get_tainted_formals pdesc tenv =
let make_untainted (name, typ) = (name, typ, None) in
let taint_formals_with_types type_strs kind formals =
let taint_formal_with_types (formal_name, formal_typ as formal) =
let matches_classname =
match formal_typ.Typ.desc with
| Tptr ({desc= Tstruct typename}, _)
-> List.mem ~equal:String.equal type_strs (Typ.Name.name typename)
| _
-> false
in
if matches_classname then (formal_name, formal_typ, Some kind) else make_untainted formal
in
List.map ~f:taint_formal_with_types formals
in
let formals = Procdesc.get_formals pdesc in
match Procdesc.get_proc_name pdesc with
| Typ.Procname.Java java_pname -> (
match
(Typ.Procname.java_get_class_name java_pname, Typ.Procname.java_get_method java_pname)
with
| "codetoanalyze.java.quandary.TaintedFormals", "taintedContextBad"
-> taint_formals_with_types ["java.lang.Integer"; "java.lang.String"] Other formals
| class_name, method_name
-> let taint_matching_supertype typename =
match (Typ.Name.name typename, method_name) with
| "android.app.Activity", ("onActivityResult" | "onNewIntent")
-> Some (taint_formals_with_types ["android.content.Intent"] Intent formals)
| ( "android.app.Service"
, ( "onBind" | "onRebind" | "onStart" | "onStartCommand" | "onTaskRemoved"
| "onUnbind" ) )
-> Some (taint_formals_with_types ["android.content.Intent"] Intent formals)
| "android.content.BroadcastReceiver", "onReceive"
-> Some (taint_formals_with_types ["android.content.Intent"] Intent formals)
| ( "android.content.ContentProvider"
, ( "bulkInsert" | "call" | "delete" | "insert" | "getType" | "openAssetFile"
| "openFile" | "openPipeHelper" | "openTypedAssetFile" | "query" | "refresh"
| "update" ) )
-> Some
(taint_formals_with_types ["android.net.Uri"; "java.lang.String"]
UserControlledURI formals)
| ( "android.webkit.WebViewClient"
, ("onLoadResource" | "shouldInterceptRequest" | "shouldOverrideUrlLoading") )
-> Some
(taint_formals_with_types
["android.webkit.WebResourceRequest"; "java.lang.String"] UserControlledURI
formals)
| ( "android.webkit.WebChromeClient"
, ("onJsAlert" | "onJsBeforeUnload" | "onJsConfirm" | "onJsPrompt") )
-> Some (taint_formals_with_types ["java.lang.String"] UserControlledURI formals)
| _
-> None
in
match
PatternMatch.supertype_find_map_opt tenv taint_matching_supertype
(Typ.Name.Java.from_string class_name)
with
| Some tainted_formals
-> tainted_formals
| None
-> Source.all_formals_untainted pdesc )
| procname
-> L.(die InternalError)
"Non-Java procedure %a where only Java procedures are expected" Typ.Procname.pp procname
let pp fmt kind =
F.fprintf fmt
( match kind with
| Intent
-> "Intent"
| Other
-> "Other"
| PrivateData
-> "PrivateData"
| UserControlledString
-> "UserControlledString"
| UserControlledURI
-> "UserControlledURI" )
end
module JavaSource = Source.Make (SourceKind)
module SinkKind = struct
type t =
| CreateFile (** sink that creates a file *)
| CreateIntent (** sink that creates an Intent *)
| Deserialization (** sink that deserializes a Java object *)
| HTML (** sink that creates HTML *)
| JavaScript (** sink that passes its arguments to untrusted JS code *)
| Logging (** sink that logs one or more of its arguments *)
| StartComponent (** sink that launches an Activity, Service, etc. *)
| Other (** for testing or uncategorized sinks *)
[@@deriving compare]
let of_string = function
| "CreateFile"
-> CreateFile
| "CreateIntent"
-> CreateIntent
| "Deserialization"
-> Deserialization
| "HTML"
-> HTML
| "JavaScript"
-> JavaScript
| "Logging"
-> Logging
| "StartComponent"
-> StartComponent
| _
-> Other
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
if [taint_this] is true. *)
let taint_all ?(taint_this= false) kind =
let actuals_to_taint, offset =
if Typ.Procname.java_is_static pname || taint_this then (actuals, 0)
else (List.tl_exn actuals, 1)
in
let indexes =
IntSet.of_list (List.mapi ~f:(fun param_num _ -> param_num + offset) actuals_to_taint)
in
Some (kind, indexes)
in
(* taint the nth non-"this" parameter (0-indexed) *)
let taint_nth n kind =
let first_index = if Typ.Procname.java_is_static pname then n else n + 1 in
if first_index < List.length actuals then Some (kind, IntSet.singleton first_index) else None
in
match pname with
| Typ.Procname.Java java_pname -> (
match
(Typ.Procname.java_get_class_name java_pname, Typ.Procname.java_get_method java_pname)
with
| "android.text.Html", "fromHtml"
-> taint_nth 0 HTML
| "android.util.Log", ("e" | "println" | "w" | "wtf")
-> taint_all Logging
| "java.io.File", "<init>"
| "java.nio.file.FileSystem", "getPath"
| "java.nio.file.Paths", "get"
-> taint_all CreateFile
| "java.io.ObjectInputStream", "<init>"
-> taint_all Deserialization
| "com.facebook.infer.builtins.InferTaint", "inferSensitiveSink"
-> taint_nth 0 Other
| class_name, method_name
-> let taint_matching_supertype typename =
match (Typ.Name.name typename, method_name) with
| "android.app.Activity", ("startActivityFromChild" | "startActivityFromFragment")
-> taint_nth 1 StartComponent
| "android.app.Activity", "startIntentSenderForResult"
-> taint_nth 2 StartComponent
| "android.app.Activity", "startIntentSenderFromChild"
-> taint_nth 3 StartComponent
| ( "android.content.Context"
, ( "bindService" | "sendBroadcast" | "sendBroadcastAsUser" | "sendOrderedBroadcast"
| "sendOrderedBroadcastAsUser" | "sendStickyBroadcast"
| "sendStickyBroadcastAsUser" | "sendStickyOrderedBroadcast"
| "sendStickyOrderedBroadcastAsUser" | "startActivities" | "startActivity"
| "startActivityForResult" | "startActivityIfNeeded" | "startNextMatchingActivity"
| "startService" | "stopService" ) )
-> taint_nth 0 StartComponent
| "android.content.Context", "startIntentSender"
-> taint_nth 1 StartComponent
| ( "android.content.Intent"
, ( "parseUri" | "getIntent" | "getIntentOld" | "setComponent" | "setData"
| "setDataAndNormalize" | "setDataAndType" | "setDataAndTypeAndNormalize"
| "setPackage" ) )
-> taint_nth 0 CreateIntent
| "android.content.Intent", "setClassName"
-> taint_all CreateIntent
| ( "android.webkit.WebView"
, ( "evaluateJavascript" | "loadData" | "loadDataWithBaseURL" | "loadUrl" | "postUrl"
| "postWebMessage" ) )
-> taint_all JavaScript
| 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) ->
if Str.string_match procedure_regex procedure 0 then
let kind = of_string kind in
try
let n = int_of_string index in
taint_nth n kind
with Failure _ ->
(* couldn't parse the index, just taint everything *)
taint_all kind
else None)
external_sinks
in
PatternMatch.supertype_find_map_opt tenv taint_matching_supertype
(Typ.Name.Java.from_string class_name) )
| pname when BuiltinDecl.is_declared pname
-> None
| pname
-> L.(die InternalError) "Non-Java procname %a in Java analysis" Typ.Procname.pp pname
let pp fmt kind =
F.fprintf fmt
( match kind with
| CreateFile
-> "CreateFile"
| CreateIntent
-> "CreateIntent"
| Deserialization
-> "Deserialization"
| HTML
-> "HTML"
| JavaScript
-> "JavaScript"
| Logging
-> "Logging"
| StartComponent
-> "StartComponent"
| Other
-> "Other" )
end
module JavaSink = Sink.Make (SinkKind)
include Trace.Make (struct
module Source = JavaSource
module Sink = JavaSink
let should_report source sink =
match (Source.kind source, Sink.kind sink) with
| PrivateData, Logging
(* logging private data issue *)
| Intent, StartComponent
(* intent reuse issue *)
| Intent, CreateIntent
(* intent configured with external values issue *)
| Intent, JavaScript
(* external data flows into JS: remote code execution risk *)
| PrivateData, JavaScript
(* leaking private data into JS *)
| UserControlledURI, (CreateIntent | StartComponent)
(* create intent/launch component from user-controlled URI *)
| UserControlledURI, CreateFile
(* create file from user-controller URI; potential path-traversal vulnerability *)
| UserControlledString, (StartComponent | CreateIntent | JavaScript | CreateFile | HTML)
-> (* do something sensitive with a user-controlled string *)
true
| (Intent | UserControlledURI | UserControlledString), Deserialization
-> (* shouldn't let anyone external control what we deserialize *)
true
| Other, _ | _, Other
-> (* for testing purposes, Other matches everything *)
true
| _
-> false
end)