diff --git a/infer/src/pulse/Pulse.ml b/infer/src/pulse/Pulse.ml index e985a2bb4b4..2c50b470f65 100644 --- a/infer/src/pulse/Pulse.ml +++ b/infer/src/pulse/Pulse.ml @@ -954,6 +954,11 @@ module PulseTransferFunctions = struct List.concat_map ls_astate_lhs_addr_hist ~f:(fun result -> let<*> astate, lhs_addr_hist = result in let<**> astate = and_is_int_if_integer_type typ rhs_addr astate in + let<*> astate = + PulseTaintOperations.store tenv path loc ~lhs:lhs_exp + ~rhs:(rhs_exp, (rhs_addr, hist), typ) + astate + in [ PulseOperations.write_deref path loc ~ref:lhs_addr_hist ~obj:(rhs_addr, hist) astate ] ) in diff --git a/infer/src/pulse/PulseTaintItem.mli b/infer/src/pulse/PulseTaintItem.mli index b183f2fd861..4e9448dbbf7 100644 --- a/infer/src/pulse/PulseTaintItem.mli +++ b/infer/src/pulse/PulseTaintItem.mli @@ -25,6 +25,8 @@ type value = type t = {kinds: TaintConfig.Kind.t list; value: value; origin: origin} [@@deriving compare, equal] +val pp_value : F.formatter -> value -> unit + val pp_value_plain : F.formatter -> value -> unit val pp : F.formatter -> t -> unit diff --git a/infer/src/pulse/PulseTaintItemMatcher.ml b/infer/src/pulse/PulseTaintItemMatcher.ml index 916a47be160..be6180ef3fb 100644 --- a/infer/src/pulse/PulseTaintItemMatcher.ml +++ b/infer/src/pulse/PulseTaintItemMatcher.ml @@ -251,7 +251,21 @@ let type_matches tenv actual_typ types = false -let taint_target_matches tenv taint_target actual_index actual_typ = +let class_names_match tenv class_names class_name = + Option.exists class_name ~f:(fun class_name -> + let class_name = + if Language.curr_language_is Hack && Typ.Name.Hack.is_static class_name then + (* in Hack, instance and static method are dispatched in the class and in its static + companion, but they can not appear in both *) + Typ.Name.Hack.static_companion_origin class_name + else class_name + in + PatternMatch.supertype_exists tenv + (fun class_name _ -> List.mem ~equal:String.equal class_names (Typ.Name.name class_name)) + class_name ) + + +let taint_procedure_target_matches tenv taint_target actual_index actual_typ = let open TaintConfig.Target in match taint_target with | AllArguments -> @@ -269,20 +283,7 @@ let taint_target_matches tenv taint_target actual_index actual_typ = let procedure_matches tenv matchers ?block_passed_to proc_name actuals = let open TaintConfig.Unit in List.filter_map matchers ~f:(fun matcher -> - let class_names_match class_names = - Option.exists (Procname.get_class_type_name proc_name) ~f:(fun class_name -> - let class_name = - if Language.curr_language_is Hack && Typ.Name.Hack.is_static class_name then - (* in Hack, instance and static method are dispatched in the class and in its static - companion, but they can not appear in both *) - Typ.Name.Hack.static_companion_origin class_name - else class_name - in - PatternMatch.supertype_exists tenv - (fun class_name _ -> - List.mem ~equal:String.equal class_names (Typ.Name.name class_name) ) - class_name ) - in + let class_name = Procname.get_class_type_name proc_name in let procedure_name_matches = match matcher.procedure_matcher with | ProcedureName {name} -> @@ -309,14 +310,15 @@ let procedure_matches tenv matchers ?block_passed_to proc_name actuals = false ) class_name ) | ClassAndMethodNames {class_names; method_names} -> - class_names_match class_names + class_names_match tenv class_names class_name && List.mem ~equal:String.equal method_names (Procname.get_method proc_name) | ClassAndMethodReturnTypeNames {class_names; method_return_type_names} -> let procedure_return_type_match method_return_type_names = Option.exists (IRAttributes.load proc_name) ~f:(fun attrs -> type_matches tenv attrs.ProcAttributes.ret_type method_return_type_names ) in - class_names_match class_names && procedure_return_type_match method_return_type_names + class_names_match tenv class_names class_name + && procedure_return_type_match method_return_type_names | OverridesOfClassWithAnnotation {annotation} -> Option.exists (Procname.get_class_type_name proc_name) ~f:(fun procedure_class_name -> let method_name = Procname.get_method proc_name in @@ -366,134 +368,189 @@ let procedure_matches tenv matchers ?block_passed_to proc_name actuals = else None ) -let get_tainted tenv path location matchers return_opt ~has_added_return_param ?block_passed_to - proc_name actuals astate = +let match_field_target matches actual potential_taint_value = let open TaintConfig.Target in + let actual = + match actual with + | {ProcnameDispatcher.Call.FuncArg.arg_payload; typ; exp} -> + (arg_payload, typ, Some exp) + in + let match_target acc (matcher : TaintConfig.Unit.field_unit) = + match matcher.field_target with + | SetField -> + let taint = + {TaintItem.value= potential_taint_value; origin= SetField; kinds= matcher.kinds} + in + (taint, actual) :: acc + in + List.fold matches ~init:[] ~f:(fun acc (matcher : TaintConfig.Unit.field_unit) -> + match_target acc matcher ) + + +let field_matches tenv matchers field_name = let open TaintConfig.Unit in - let matches = procedure_matches tenv matchers ?block_passed_to proc_name actuals in - if not (List.is_empty matches) then L.d_printfln "taint matches" ; + List.filter_map matchers ~f:(fun matcher -> + match matcher.field_matcher with + | FieldRegex {name_regex} -> ( + let field_name_s = Fieldname.get_field_name field_name in + L.d_printfln "Matching regex wrt %s" field_name_s ; + match Str.search_forward name_regex field_name_s 0 with + | _ -> + Some matcher + | exception Caml.Not_found -> + None ) + | ClassAndFieldNames {class_names; field_names} -> + let class_name = Fieldname.get_class_name field_name in + if + class_names_match tenv class_names (Some class_name) + && List.mem ~equal:String.equal field_names (Fieldname.get_field_name field_name) + then Some matcher + else None ) + + +let match_procedure_target tenv astate matches path location return_opt ~has_added_return_param + actuals potential_taint_value = + let open TaintConfig.Target in let actuals = List.map actuals ~f:(fun {ProcnameDispatcher.Call.FuncArg.arg_payload; typ; exp} -> (arg_payload, typ, Some exp) ) in - let value = - match block_passed_to with - | Some proc_name -> - TaintItem.TaintBlockPassedTo proc_name - | None -> - TaintItem.TaintProcedure proc_name - in - List.fold matches ~init:(astate, []) ~f:(fun acc (matcher : TaintConfig.Unit.procedure_unit) -> - let ({kinds}) = (matcher : TaintConfig.Unit.procedure_unit) in - let rec match_target acc = function - | ReturnValue -> ( - L.d_printf "matching return value... " ; - match return_opt with + let rec match_target kinds acc procedure_target = + match procedure_target with + | ReturnValue -> ( + L.d_printf "matching return value... " ; + match return_opt with + | None -> + L.d_printfln "no match" ; + acc + | Some (return, return_typ) -> ( + L.d_printfln "match! tainting return value" ; + let return_as_actual = if has_added_return_param then List.last actuals else None in + match return_as_actual with + | Some actual -> + let astate, acc = acc in + let taint = {TaintItem.value= potential_taint_value; origin= ReturnValue; kinds} in + (astate, (taint, actual) :: acc) | None -> - L.d_printfln "no match" ; - acc - | Some (return, return_typ) -> ( - L.d_printfln "match! tainting return value" ; - let return_as_actual = if has_added_return_param then List.last actuals else None in - match return_as_actual with - | Some actual -> - let astate, acc = acc in - let taint = {TaintItem.value; origin= ReturnValue; kinds} in - (astate, (taint, actual) :: acc) - | None -> - let return = Var.of_id return in - let astate = - if Stack.mem return astate then astate - else - let ret_val = AbstractValue.mk_fresh () in - Stack.add return (ret_val, ValueHistory.epoch) astate + let return = Var.of_id return in + let astate = + if Stack.mem return astate then astate + else + let ret_val = AbstractValue.mk_fresh () in + Stack.add return (ret_val, ValueHistory.epoch) astate + in + Stack.find_opt return astate + |> Option.fold ~init:acc ~f:(fun (_, tainted) return_value -> + let taint = + {TaintItem.value= potential_taint_value; origin= ReturnValue; kinds} + in + (astate, (taint, (return_value, return_typ, None)) :: tainted) ) ) ) + | (AllArguments | ArgumentPositions _ | AllArgumentsButPositions _ | ArgumentsMatchingTypes _) + as taint_target -> + L.d_printf "matching actuals... " ; + List.foldi actuals ~init:acc + ~f:(fun i ((astate, tainted) as acc) ((_, actual_typ, exp) as actual_hist_and_typ) -> + let is_const_exp = match exp with Some exp -> Exp.is_const exp | None -> false in + if taint_procedure_target_matches tenv taint_target i actual_typ && not is_const_exp + then ( + L.d_printfln_escaped "match! tainting actual #%d with type %a" i (Typ.pp_full Pp.text) + actual_typ ; + let taint = + {TaintItem.value= potential_taint_value; origin= Argument {index= i}; kinds} + in + (astate, (taint, actual_hist_and_typ) :: tainted) ) + else ( + L.d_printfln_escaped "no match for #%d with type %a" i (Typ.pp_full Pp.text) + actual_typ ; + acc ) ) + | Fields fields -> + let type_check astate value typ fieldname = + (* Dereference the value as much as possible and verify the result + is of structure type holding the expected fieldname. *) + let open IOption.Let_syntax in + let rec get_val_and_typ_name astate value typ = + match typ.Typ.desc with + | Tstruct typ_name | Tptr ({desc= Tstruct typ_name}, _) -> + Some (astate, value, typ_name) + | Tptr (typ', _) -> + let* astate, value = + PulseOperations.eval_access path Read location value Dereference astate + |> PulseResult.ok + in + get_val_and_typ_name astate value typ' + | _ -> + None + in + let* astate, value, typ_name = get_val_and_typ_name astate value typ in + let+ field_typ = + let* {Struct.fields} = Tenv.lookup tenv typ_name in + List.find_map fields ~f:(fun (field, typ, _) -> + if String.equal fieldname (Fieldname.get_field_name field) then Some typ else None ) + in + (astate, value, typ_name, field_typ) + in + let move_taint_to_field ((astate, tainted) as acc) (taint, (value, typ, _)) fieldname = + (* Move the taint from [value] to [value]'s field [fieldname] *) + match type_check astate value typ fieldname with + | None -> + (* The value cannot hold the expected field. + This is a type mismatch and we need to inform the user. *) + L.die UserError + "Type error in taint configuration: Model for `%a`:Type `%a` does not have a field \ + `%s`" + TaintItem.pp_value potential_taint_value (Typ.pp_full Pp.text) typ fieldname + | Some (astate, value, typ_name, field_typ) -> + Option.value ~default:acc + (PulseResult.ok + (let open PulseResult.Let_syntax in + let* astate, ret_value = + PulseOperations.eval_access path Read location value + (FieldAccess (Fieldname.make typ_name fieldname)) + astate in - Stack.find_opt return astate - |> Option.fold ~init:acc ~f:(fun (_, tainted) return_value -> - let taint = {TaintItem.value; origin= ReturnValue; kinds} in - (astate, (taint, (return_value, return_typ, None)) :: tainted) ) ) ) - | ( AllArguments - | ArgumentPositions _ - | AllArgumentsButPositions _ - | ArgumentsMatchingTypes _ ) as taint_target -> - L.d_printf "matching actuals... " ; - List.foldi actuals ~init:acc - ~f:(fun i ((astate, tainted) as acc) ((_, actual_typ, exp) as actual_hist_and_typ) -> - let is_const_exp = match exp with Some exp -> Exp.is_const exp | None -> false in - if taint_target_matches tenv taint_target i actual_typ && not is_const_exp then ( - L.d_printfln_escaped "match! tainting actual #%d with type %a" i - (Typ.pp_full Pp.text) actual_typ ; - let taint = {TaintItem.value; origin= Argument {index= i}; kinds} in - (astate, (taint, actual_hist_and_typ) :: tainted) ) - else ( - L.d_printfln_escaped "no match for #%d with type %a" i (Typ.pp_full Pp.text) - actual_typ ; - acc ) ) - | Fields fields -> - let type_check astate value typ fieldname = - (* Dereference the value as much as possible and verify the result - is of structure type holding the expected fieldname. *) - let open IOption.Let_syntax in - let rec get_val_and_typ_name astate value typ = - match typ.Typ.desc with - | Tstruct typ_name | Tptr ({desc= Tstruct typ_name}, _) -> - Some (astate, value, typ_name) - | Tptr (typ', _) -> - let* astate, value = - PulseOperations.eval_access path Read location value Dereference astate - |> PulseResult.ok + let+ astate, ret_value = + PulseOperations.eval_access path Read location ret_value Dereference astate in - get_val_and_typ_name astate value typ' - | _ -> - None - in - let* astate, value, typ_name = get_val_and_typ_name astate value typ in - let+ field_typ = - let* {Struct.fields} = Tenv.lookup tenv typ_name in - List.find_map fields ~f:(fun (field, typ, _) -> - if String.equal fieldname (Fieldname.get_field_name field) then Some typ - else None ) - in - (astate, value, typ_name, field_typ) - in - let move_taint_to_field ((astate, tainted) as acc) (taint, (value, typ, _)) fieldname = - (* Move the taint from [value] to [value]'s field [fieldname] *) - match type_check astate value typ fieldname with - | None -> - (* The value cannot hold the expected field. - This is a type mismatch and we need to inform the user. *) - L.die UserError - "Type error in taint configuration: Model for `%a`:Type `%a` does not have a \ - field `%s`" - Procname.pp proc_name (Typ.pp_full Pp.text) typ fieldname - | Some (astate, value, typ_name, field_typ) -> - Option.value ~default:acc - (PulseResult.ok - (let open PulseResult.Let_syntax in - let* astate, ret_value = - PulseOperations.eval_access path Read location value - (FieldAccess (Fieldname.make typ_name fieldname)) - astate - in - let+ astate, ret_value = - PulseOperations.eval_access path Read location ret_value Dereference - astate - in - L.d_printfln "match! tainting field %s with type %a" fieldname - (Typ.pp_full Pp.text) field_typ ; - ( astate - , ( TaintItem. - {taint with origin= Field {name= fieldname; origin= taint.origin}} - , (ret_value, field_typ, None) ) - :: tainted ) ) ) + L.d_printfln "match! tainting field %s with type %a" fieldname + (Typ.pp_full Pp.text) field_typ ; + ( astate + , ( TaintItem.{taint with origin= Field {name= fieldname; origin= taint.origin}} + , (ret_value, field_typ, None) ) + :: tainted ) ) ) + in + List.fold fields ~init:acc ~f:(fun ((astate, tainted) as acc) (fieldname, origin) -> + let astate', tainted' = match_target kinds acc origin in + let new_taints, _ = + List.split_n tainted' (List.length tainted' - List.length tainted) in - List.fold fields ~init:acc ~f:(fun ((astate, tainted) as acc) (fieldname, origin) -> - let astate', tainted' = match_target acc origin in - let new_taints, _ = - List.split_n tainted' (List.length tainted' - List.length tainted) - in - let acc = if phys_equal astate' astate then acc else (astate', tainted) in - List.fold new_taints ~init:acc ~f:(fun acc taint -> - move_taint_to_field acc taint fieldname ) ) - in - match_target acc matcher.procedure_target ) + let acc = if phys_equal astate' astate then acc else (astate', tainted) in + List.fold new_taints ~init:acc ~f:(fun acc taint -> + move_taint_to_field acc taint fieldname ) ) + in + List.fold matches ~init:(astate, []) ~f:(fun acc (matcher : TaintConfig.Unit.procedure_unit) -> + match_target matcher.kinds acc matcher.procedure_target ) + + +let get_tainted tenv path location ~procedure_matchers ~field_matchers return_opt + ~has_added_return_param potential_taint_value actuals astate = + let block_passed_to = + match potential_taint_value with + | TaintItem.TaintBlockPassedTo proc_name -> + Some proc_name + | _ -> + None + in + match potential_taint_value with + | TaintItem.TaintProcedure proc_name | TaintItem.TaintBlockPassedTo proc_name -> + let matches = procedure_matches tenv procedure_matchers ?block_passed_to proc_name actuals in + if not (List.is_empty matches) then L.d_printfln "taint matches" ; + match_procedure_target tenv astate matches path location return_opt ~has_added_return_param + actuals potential_taint_value + | TaintItem.TaintField field_name -> ( + let matches = field_matches tenv field_matchers field_name in + if not (List.is_empty matches) then L.d_printfln "taint matches" ; + match actuals with + | [actual] -> + (astate, match_field_target matches actual potential_taint_value) + | _ -> + (astate, []) ) diff --git a/infer/src/pulse/PulseTaintItemMatcher.mli b/infer/src/pulse/PulseTaintItemMatcher.mli index e764d78be38..1b8ff0f4c36 100644 --- a/infer/src/pulse/PulseTaintItemMatcher.mli +++ b/infer/src/pulse/PulseTaintItemMatcher.mli @@ -27,11 +27,11 @@ val get_tainted : Tenv.t -> PathContext.t -> Location.t - -> TaintConfig.Unit.procedure_unit list + -> procedure_matchers:TaintConfig.Unit.procedure_unit list + -> field_matchers:TaintConfig.Unit.field_unit list -> (Ident.t * Typ.t) option -> has_added_return_param:bool - -> ?block_passed_to:Procname.t - -> Procname.t + -> TaintItem.value -> BaseStack.value ProcnameDispatcher.Call.FuncArg.t list -> AbductiveDomain.t -> AbductiveDomain.t * (TaintItem.t * (BaseStack.value * Typ.t * Exp.t option)) list diff --git a/infer/src/pulse/PulseTaintOperations.ml b/infer/src/pulse/PulseTaintOperations.ml index 6986537376e..d8ce66a6182 100644 --- a/infer/src/pulse/PulseTaintOperations.ml +++ b/infer/src/pulse/PulseTaintOperations.ml @@ -164,7 +164,7 @@ let taint_allocation tenv path location ~typ_desc ~alloc_desc ~allocator v astat List.filter allocation_sources ~f:(fun (class_name, _) -> String.equal class_name type_name ) in - (* Micro-optimisation: do not allocate `alloc_desc` when no matching taint sources are found *) + (* Micro-optimisation: do not allocate `alloc_desc` when no matching taint sources are found *) if List.is_empty matching_allocations then None else let alloc_desc = @@ -214,10 +214,10 @@ let taint_and_explore ~taint v astate = let taint_sources tenv path location ~intra_procedural_only return ~has_added_return_param - ?block_passed_to proc_name actuals astate = + potential_taint_value actuals astate = let astate, tainted = - TaintItemMatcher.get_tainted tenv path location source_matchers return ~has_added_return_param - ?block_passed_to proc_name actuals astate + TaintItemMatcher.get_tainted tenv path location ~procedure_matchers:source_matchers + ~field_matchers:[] return ~has_added_return_param potential_taint_value actuals astate in List.fold tainted ~init:astate ~f:(fun astate (source, ((v, _), _, _)) -> let hist = @@ -243,10 +243,11 @@ let taint_sources tenv path location ~intra_procedural_only return ~has_added_re astate ) ) ) -let taint_sanitizers tenv path return ~has_added_return_param ~location proc_name actuals astate = +let taint_sanitizers tenv path return ~has_added_return_param ~location potential_taint_value + actuals astate = let astate, tainted = - TaintItemMatcher.get_tainted tenv path location sanitizer_matchers return - ~has_added_return_param proc_name actuals astate + TaintItemMatcher.get_tainted tenv path location ~procedure_matchers:sanitizer_matchers + ~field_matchers:[] return ~has_added_return_param potential_taint_value actuals astate in let astate = List.fold tainted ~init:astate ~f:(fun astate (sanitizer, ((v, history), _, _)) -> @@ -409,17 +410,23 @@ let check_flows_wrt_sink ?(policy_violations_reported = IntSet.empty) path locat ((policy_violations_reported, astate), sanitizers) ) -let should_ignore_all_flows_to proc_name = - Procname.is_objc_dealloc proc_name || BuiltinDecl.is_declared proc_name +let should_ignore_all_flows_to potential_taint_value = + match potential_taint_value with + | TaintItem.TaintProcedure proc_name -> + Procname.is_objc_dealloc proc_name || BuiltinDecl.is_declared proc_name + | _ -> + false -let taint_sinks tenv path location return ~has_added_return_param proc_name actuals astate = +let taint_sinks tenv path location return ~has_added_return_param potential_taint_value actuals + astate = let astate, tainted = - TaintItemMatcher.get_tainted tenv path location sink_procedure_matchers return - ~has_added_return_param proc_name actuals astate + TaintItemMatcher.get_tainted tenv path location ~procedure_matchers:sink_procedure_matchers + ~field_matchers:sink_field_matchers return ~has_added_return_param potential_taint_value + actuals astate in PulseResult.list_fold tainted ~init:astate ~f:(fun astate (sink, ((v, history), _typ, _)) -> - if should_ignore_all_flows_to proc_name then Ok astate + if should_ignore_all_flows_to potential_taint_value then Ok astate else let sink_trace = Trace.Immediate {location; history} in let visited = ref AbstractValue.Set.empty in @@ -514,8 +521,9 @@ let propagate_to path location v values call astate = let taint_propagators tenv path location return ~has_added_return_param proc_name actuals astate = let astate, tainted = - TaintItemMatcher.get_tainted tenv path location propagator_matchers return - ~has_added_return_param proc_name actuals astate + let potential_taint_value = TaintItem.TaintProcedure proc_name in + TaintItemMatcher.get_tainted tenv path location ~procedure_matchers:propagator_matchers + ~field_matchers:[] return ~has_added_return_param potential_taint_value actuals astate in List.fold tainted ~init:astate ~f:(fun astate (_propagator, ((v, _history), _, _)) -> let other_actuals = @@ -691,6 +699,7 @@ let call tenv path location return ~call_was_unknown (call : _ Either.t) actuals (SkippedUnknownCall call_exp) None actuals astate ) else Ok astate | Second proc_name -> + let potential_taint_value = TaintItem.TaintProcedure proc_name in let call_was_unknown = call_was_unknown || should_treat_as_unknown_for_taint tenv proc_name in let has_added_return_param = match IRAttributes.load proc_name with @@ -700,16 +709,16 @@ let call tenv path location return ~call_was_unknown (call : _ Either.t) actuals false in let astate = - taint_sanitizers tenv path (Some return) ~has_added_return_param ~location proc_name actuals - astate + taint_sanitizers tenv path (Some return) ~has_added_return_param ~location + potential_taint_value actuals astate in let astate = taint_sources tenv path location ~intra_procedural_only:false (Some return) - ~has_added_return_param proc_name actuals astate + ~has_added_return_param potential_taint_value actuals astate in let+ astate = - taint_sinks tenv path location (Some return) ~has_added_return_param proc_name actuals - astate + taint_sinks tenv path location (Some return) ~has_added_return_param potential_taint_value + actuals astate in let astate, call_was_unknown = let new_astate = @@ -724,6 +733,17 @@ let call tenv path location return ~call_was_unknown (call : _ Either.t) actuals else astate +let store tenv path location ~lhs:lhs_exp ~rhs:(rhs_exp, rhs_addr, typ) astate = + match lhs_exp with + | Exp.Lfield (_, field_name, _) -> + let potential_taint_value = TaintItem.TaintField field_name in + let rhs = {ProcnameDispatcher.Call.FuncArg.exp= rhs_exp; typ; arg_payload= rhs_addr} in + taint_sinks tenv path location None ~has_added_return_param:false potential_taint_value [rhs] + astate + | _ -> + Ok astate + + let taint_initial tenv proc_name (proc_attrs : ProcAttributes.t) astate0 = let result = let++ astate, rev_actuals = @@ -744,8 +764,15 @@ let taint_initial tenv proc_name (proc_attrs : ProcAttributes.t) astate0 = ~f:(fun ({passed_to} : ProcAttributes.block_as_arg_attributes) -> passed_to) proc_attrs.ProcAttributes.block_as_arg_attributes in + let potential_taint_value = + match block_passed_to with + | Some proc_name -> + TaintItem.TaintBlockPassedTo proc_name + | None -> + TaintItem.TaintProcedure proc_name + in taint_sources tenv PathContext.initial proc_attrs.loc ~intra_procedural_only:true None - ~has_added_return_param:false ?block_passed_to proc_name (List.rev rev_actuals) astate + ~has_added_return_param:false potential_taint_value (List.rev rev_actuals) astate in match PulseOperationResult.sat_ok result with | Some astate_tainted -> diff --git a/infer/src/pulse/PulseTaintOperations.mli b/infer/src/pulse/PulseTaintOperations.mli index af6096c2b55..a3ef5bb0ea1 100644 --- a/infer/src/pulse/PulseTaintOperations.mli +++ b/infer/src/pulse/PulseTaintOperations.mli @@ -21,6 +21,15 @@ val call : -> AbductiveDomain.t AccessResult.t (** add sources and sinks coming from a particular call site *) +val store : + Tenv.t + -> PathContext.t + -> Location.t + -> lhs:Exp.t + -> rhs:Exp.t * BaseStack.value * Typ.t + -> AbductiveDomain.t + -> AbductiveDomain.t AccessResult.t + val taint_allocation : Tenv.t -> PathContext.t diff --git a/infer/tests/codetoanalyze/objc/.infertaintconfig b/infer/tests/codetoanalyze/objc/.infertaintconfig index f2eddd7fc2f..56fe782fa19 100644 --- a/infer/tests/codetoanalyze/objc/.infertaintconfig +++ b/infer/tests/codetoanalyze/objc/.infertaintconfig @@ -14,6 +14,20 @@ "sink_kinds": ["Logger"] } ] + }, + { "short_description": "Token stored in ivar", + "taint_flows": [ + { "source_kinds": ["Token"], + "sink_kinds": ["StoreIvar"] + } + ] + }, + { "short_description": "Token stored in ivar", + "taint_flows": [ + { "source_kinds": ["SimpleSource"], + "sink_kinds": ["StoreOtherIvar"] + } + ] } ], "pulse-taint-sources": [ @@ -40,6 +54,16 @@ "class_names": ["AccessLibrary"], "method_names": ["fetchResult"], "kinds": ["SimpleSource"] + }, + { + "block_passed_to": "Library.fetchWithCompletion:", + "taint_target": ["ArgumentPositions", [0]], + "kinds": ["Token"] + }, + { + "class_names": ["OtherClass"], + "method_names": ["getSource"], + "kinds": ["SimpleSource"] } ], "pulse-taint-sanitizers": [ @@ -70,11 +94,23 @@ "taint_target": ["ArgumentPositions", [0]], "kinds": ["SimpleSink"] }, - { "class_names": ["Builder"], - "method_names": ["setValue:"], - "kinds": ["SimpleSink"], - "taint_target": ["ArgumentPositions", [1]] - } + { + "class_names": ["Builder"], + "method_names": ["setValue:"], + "kinds": ["SimpleSink"], + "taint_target": ["ArgumentPositions", [1]] + }, + { + "field_regex": ".*", + "taint_target": "SetField", + "kinds": ["StoreIvar"] + }, + { + "class_names": ["OtherClass"], + "field_names": ["_item"], + "taint_target": "SetField", + "kinds": ["StoreOtherIvar"] + } ], "pulse-taint-propagators": [ { "procedure": "URLCreate", diff --git a/infer/tests/codetoanalyze/objc/pulse/issues.exp b/infer/tests/codetoanalyze/objc/pulse/issues.exp index cbcc1f845ac..a06fe1b296d 100644 --- a/infer/tests/codetoanalyze/objc/pulse/issues.exp +++ b/infer/tests/codetoanalyze/objc/pulse/issues.exp @@ -160,6 +160,8 @@ codetoanalyze/objc/pulse/taint/InterProcTaintWithMock.m, taintInterprocWithMockB codetoanalyze/objc/pulse/taint/NSMutableArrayTaint.m, add_object_propagator_bad, 7, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `SensitiveData.getSensitiveData` with kind `SimpleSource`,in call to `NSArray.objectEnumerator`,in call to function `NSEnumerator.nextObject` with no summary,in call to `NSMutableArray.addObject:`,value passed as argument `#0` to `my_log` with kind `SimpleSink`], source: SensitiveData.getSensitiveData, sink: my_log, tainted expression: targetAccounts codetoanalyze/objc/pulse/taint/NilSourceTest.m, NilSourceTest.taintDataBad, 3, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `Data.data` with kind `SimpleSource`,value passed as argument `#0` to `taint_data` with kind `SimpleSink`], source: Data.data, sink: taint_data, tainted expression: item codetoanalyze/objc/pulse/taint/SpecializedWithBlocksTaint.m, AccessLibrary.fetchWithCompletion:[specialized with blocks], 2, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `AccessLibrary.fetchResult` with kind `SimpleSource`,when calling `objc_block_fetchAndRunHandler_1[specialized with blocks]` here,when calling `objc_block_callSpecializedWithBlocksBad_2` here,value passed as argument `#1` to `Builder.setValue:` with kind `SimpleSink`], source: AccessLibrary.fetchResult, sink: Builder.setValue:, tainted expression: result +codetoanalyze/objc/pulse/taint/StoreIvarTaint.m, objc_block_StoreIvarTaint._fetchToken_bad_2, 4, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value passed as argument `#0` to a block passed to `Library.fetchWithCompletion:` with kind `Token`,in call to `NSArray.firstObject`,in call to function `Item.token` with no summary,in call to function `NSObject.copy` with no summary,when calling `StoreIvarTaint.setToken:` here,set `_token` with kind `StoreIvar`], source: a block passed to Library.fetchWithCompletion:, sink: _token, tainted expression: token +codetoanalyze/objc/pulse/taint/StoreIvarTaint.m, storeIvarBad, 3, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `OtherClass.getSource` with kind `SimpleSource`,when calling `OtherClass.setItem:` here,set `_item` with kind `StoreOtherIvar`], source: OtherClass.getSource, sink: _item, tainted expression: source codetoanalyze/objc/pulse/taint/StringFormatTaint.m, stringPropagatesTaintBad, 4, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `NSURL.initWithString:` with kind `SensitiveURLSource`,in call to function `NSURL.scheme` with no summary,in call to `NSString.stringWithFormat:`,value passed as argument `#0` to `logEvent` with kind `Logger`], source: NSURL.initWithString:, sink: logEvent, tainted expression: urlInfo codetoanalyze/objc/pulse/taint/URLPropagator.m, propagate_taint_url_bad1, 9, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `NSURL.initWithString:` with kind `SensitiveURLSource`,in call to `URLCreate`,value passed as argument `#0` to `logEvent` with kind `Logger`], source: NSURL.initWithString:, sink: logEvent, tainted expression: url codetoanalyze/objc/pulse/taint/URLPropagator.m, propagate_taint_url_bad2, 9, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `NSURL.initWithString:` with kind `SensitiveURLSource`,in call to `URLCreate1`,value passed as argument `#0` to `logEvent` with kind `Logger`], source: NSURL.initWithString:, sink: logEvent, tainted expression: url diff --git a/infer/tests/codetoanalyze/objc/pulse/taint/StoreIvarTaint.m b/infer/tests/codetoanalyze/objc/pulse/taint/StoreIvarTaint.m new file mode 100644 index 00000000000..41ae1e1b5cd --- /dev/null +++ b/infer/tests/codetoanalyze/objc/pulse/taint/StoreIvarTaint.m @@ -0,0 +1,88 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@interface Item : NSObject +@property(nonatomic, copy) NSString* token; +@end + +typedef void (^LibraryFetchCompletionBlock)(NSArray* items, + NSArray* errors); + +@interface Library : NSObject ++ (instancetype)sharedInstance; +@end + +@implementation Library + ++ (instancetype)sharedInstance { + static dispatch_once_t dispatchOnceToken; + static Library* sharedLibrary = nil; + dispatch_once(&dispatchOnceToken, ^{ + sharedLibrary = [Library new]; + }); + return sharedLibrary; +} + +- (void)fetchWithCompletion:(LibraryFetchCompletionBlock)completion { +} + +@end + +@interface StoreIvarTaint : NSObject + +@end + +@implementation StoreIvarTaint { + NSString* _token; +} + +- (void)_fetchToken_bad { + __weak __typeof(self) weakSelf = self; + [[Library sharedInstance] + fetchWithCompletion:^(NSArray* accounts, + NSArray* _Nonnull errors) { + NSString* const accessToken = [accounts firstObject].token; + NSString* token = [accessToken copy]; + [self setToken:token]; + }]; +} + +- (void)setToken:(NSString*)token { + _token = token; +} + +@end + +@interface OtherClass : NSObject +- (NSString*)getSource; +@end + +@implementation OtherClass { + NSString* _item; + @public + NSString* _name; +} + +- (void)setItem:(NSString*)item { + _item = item; +} + +@end + +void storeIvarBad() { + OtherClass* c = [OtherClass new]; + NSString* source = [c getSource]; + [c setItem:source]; +} + +void storeIvarGood() { + OtherClass* c = [OtherClass new]; + NSString* source = [c getSource]; + c->_name = source; +}