# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'contrast/components/interface' cs__scoped_require 'contrast/extensions/ruby_core/assess/exec_trigger' # This module provides us with a way to invoke Kernel propagation for those # methods which are too complex to fit into one of the standard # Contrast::Agent::Assess::Policy::Propagator molds without cluttering up the # Kernel Module or exposing our methods there. module KernelPropagator class << self include Contrast::Components::Interface include Contrast::CoreExtensions::Assess::ExecTrigger access_component :logging # We're 'tracking' sprintf now, meaning if anything is tracked on the way # in, the entire result will be tracked out. We're going to take this # approach for now b/c it's fast and easy. I don't super love it, and by # that I mean I hate it. # # To actually track this, we'd have to find the index of the new things # being added, then remove the tags at the range of the format marker, # which is some arbitrary length thing, and add the new tags from the # inserted string, shifted down by the length of the aforementioned # marker. # # marker is in the format %[flags][width][.precision]type, type being a # single character. We could regexp this with %.+[bBdiouxXeEfgGaAcps%] # # also, b/c Ruby hates us, there are things called absolute markers, # (digit)$, that go in the flags section. These cannot be mixed w/ the # order assumed type # # oh, and there's also %type and %{name}... b/c of course there is # -HM def sprintf_tagger patcher, preshift, ret, _block format_string = preshift.args[0] args = preshift.args[1] track_sprintf(ret, format_string, args) ret.cs__properties.build_event( patcher, ret, preshift.object, ret, preshift.args, 1) ret end def track_sprintf result, format_string, args tracked_inputs = [] tracked_inputs << format_string if format_string.cs__tracked? if args.is_a?(String) tracked_inputs << args if args.cs__tracked? elsif args.is_a?(Hash) args.each_value do |value| next unless Contrast::Utils::DuckUtils.quacks_to?( value, :cs__tracked?) next unless value.cs__tracked? tracked_inputs << value end elsif args.is_a?(Array) args.each do |value| next unless Contrast::Utils::DuckUtils.quacks_to?(value, :cs__tracked?) next unless value.cs__tracked? tracked_inputs << value end end return result if tracked_inputs.empty? tracked_inputs.each do |input| input.cs__properties.events.each do |event| result.cs__properties.events << event end input.cs__splat_tags(result) end result rescue StandardError => e logger.error( e, 'Unable to track dataflow through sprintf') result end end end cs__scoped_require 'cs__assess_kernel/cs__assess_kernel'