Class: Contrast::Agent::Assess::Policy::Propagator::Split

Inherits:
Base
  • Object
show all
Includes:
Components::Interface
Defined in:
lib/contrast/agent/assess/policy/propagator/split.rb,
ext/cs__assess_yield_track/cs__assess_yield_track.c

Overview

This class is specifically for String#split & String#grapheme_clusters propagation it propagates tag ranges from a string to elements within an untracked array

Constant Summary collapse

SPLIT_TRACKER =
Contrast::Utils::ThreadTracker.new

Class Method Summary collapse

Methods included from Components::Interface

included

Methods inherited from Base

find_source, tracked_value?

Class Method Details

.begin_split(string, args) ⇒ Object

Marks the point in which the String#split method is called. Responsible for building the context required to propagate when the results of #split are yielded directly to a block

Parameters:

  • string (String)

    the String on which split is invoked

  • args (Array<Object>)

    the arguments passed to the original split call



81
82
83
84
85
86
87
88
89
90
91
# File 'lib/contrast/agent/assess/policy/propagator/split.rb', line 81

def begin_split string, args
  save_split_depth!
  depth = SPLIT_TRACKER.get(:split_depth)
  save_split_index!(depth)
  save_split_value!(depth, string, args)
rescue Exception => e # rubocop:disable Lint/RescueException
  # don't let our errors propagate and disable String#split for
  # this since we're in an error state
  logger.warn('Unable to record split context', e)
  end_split
end

.end_splitObject

Marks the point in which the String#split method is exited. Responsible for removing the context required to propagate when the results of #split are yielded directly to a block



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/contrast/agent/assess/policy/propagator/split.rb', line 96

def end_split
  depth = SPLIT_TRACKER.get(:split_depth)
  return unless depth

  depth -= 1
  if depth.negative?
    SPLIT_TRACKER.delete(:split_depth)
    SPLIT_TRACKER.delete(:split_index)
    SPLIT_TRACKER.delete(:split_value)
  else
    SPLIT_TRACKER.set(:split_depth, depth)
  end
rescue StandardError => e
  logger.warn('Unable to remove split context', e)
end

.instrument_string_splitObject



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/contrast/agent/assess/policy/propagator/split.rb', line 144

def instrument_string_split
  if @_instrument_string_split.nil?
    @_instrument_string_split = begin
      require 'cs__assess_yield_track/cs__assess_yield_track' if AGENT.patch_yield? && Funchook.available?
      true
    rescue StandardError => e
      logger.error('Error loading split rb_yield patch', e)
      false
    end
  end
  @_instrument_string_split
end

.propagate(propagation_node, preshift, target) ⇒ nil

Propagate taint from a source as it is split into composite sections. This method MUST return nil, otherwise it risks changing the result of of the propagation.

Parameters:

Returns:

  • (nil)


33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/contrast/agent/assess/policy/propagator/split.rb', line 33

def propagate propagation_node, preshift, target
  logger.trace('Propagation detected',
               node_id: propagation_node.id,
               target_id: target.__id__)
  unless target.is_a?(Array)
    Contrast::Agent::Assess::Policy::Propagator::Keep.propagate(propagation_node, preshift, target)
    properties = Contrast::Agent::Assess::Tracker.properties(target)
    properties.build_event(propagation_node, target, object, ret, args)
    return
  end

  source = find_source(propagation_node.sources[0], preshift)

  separator_length = if propagation_node.method_name == :grapheme_clusters
                       # grapheme_clusters break the string apart based on each "user-perceived" character
                       0
                     else
                       # The default for String#split is to use a single whitespace
                       preshift&.args&.first&.to_s&.length || $FIELD_SEPARATOR&.to_s&.length || 1
                     end

  current_index = 0
  target.each do |elem|
    elem_length = elem.length
    range = current_index...(current_index + elem_length)
    elem_properties = Contrast::Agent::Assess::Tracker.properties(elem)
    next unless elem_properties

    source_properties = Contrast::Agent::Assess::Tracker.properties(source)
    tags = source_properties.tags_at_range(range)
    elem_properties.clear_tags
    tags.each_pair do |key, value|
      elem_properties.set_tags(key, value)
    end
    elem_properties.build_event(propagation_node, elem, preshift.object, target, preshift.args, 0)
    elem_properties.add_properties(propagation_node.properties)
    current_index = current_index + elem_length + separator_length
  end
  nil
end

.propagate_yield(target) ⇒ Object

This method is called whenever an rb_yield is called. We need to leave it as soon as possible with as little work as possible.

Parameters:

  • target (String)

    the entity being passed to the yield block



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/contrast/agent/assess/policy/propagator/split.rb', line 118

def propagate_yield target
  depth, index = nil

  depth = SPLIT_TRACKER.get(:split_depth)
  return unless depth

  source = SPLIT_TRACKER.get(:split_value)&.fetch(depth)
  return unless source

  index = SPLIT_TRACKER.get(:split_index)&.fetch(depth)
  return unless index

  properties = Contrast::Agent::Assess::Tracker.properties(target)
  return unless properties

  true_source = source[index]
  properties.copy_from(true_source, target)
rescue StandardError => e
  logger.warn('Unable to track within split context', e)
ensure
  if depth && index
    idx = SPLIT_TRACKER.get(:split_index)
    idx[depth] = index + 1 if defined?(idx) && idx.is_a?(Array)
  end
end