lib/contrast/agent/assess/policy/trigger_node.rb in contrast-agent-3.8.5 vs lib/contrast/agent/assess/policy/trigger_node.rb in contrast-agent-3.9.0

- old
+ new

@@ -99,17 +99,17 @@ # this condition may not hold true forever, but for now it's # a nice optimization return false unless source.cs__tracked? # find the ranges that violate the rule (untrusted, etc) - vulnerable_ranges = find_ranges_by_tag(source.cs__properties, required_tags) + vulnerable_ranges = find_ranges_by_all_tags(Contrast::Utils::StringUtils.ret_length(source), source.cs__properties, required_tags) # if there aren't any vulnerable ranges, nope out return false if vulnerable_ranges.empty? # find the ranges that are exempt from the rule # (validated, sanitized, etc) - secure_ranges = find_ranges_by_tag(source.cs__properties, disallowed_tags) + secure_ranges = find_ranges_by_any_tag(source.cs__properties, disallowed_tags) # if there are vulnerable ranges and no secure, report return true if secure_ranges.empty? # figure out if there are any vulnerable ranges that aren't # covered by a secure one. if there are, the rule was violated @@ -133,11 +133,11 @@ UNTRUSTED = 'UNTRUSTED' def populate_tags required_tags return unless dataflow? validate_rule_tags(required_tags) - @required_tags = required_tags || [] + @required_tags = Set.new(required_tags) @required_tags << UNTRUSTED end ENCODER_START = 'CUSTOM_ENCODED_' VALIDATOR_START = 'CUSTOM_VALIDATED_' @@ -151,11 +151,11 @@ CUSTOM_VALIDATED = 'CUSTOM_VALIDATED' def populate_disallowed disallowed_tags return unless dataflow? validate_rule_tags(disallowed_tags) - @disallowed_tags = disallowed_tags || [] + @disallowed_tags = Set.new(disallowed_tags) @disallowed_tags << LIMITED_CHARS @disallowed_tags << CUSTOM_ENCODED @disallowed_tags << CUSTOM_VALIDATED @disallowed_tags << ENCODER_START + loud_name @disallowed_tags << VALIDATOR_START + loud_name @@ -170,26 +170,71 @@ Contrast::Agent::Assess::Policy::PolicyNode::VALID_TAGS.include?(tag) || Contrast::Agent::Assess::Policy::PolicyNode::VALID_SOURCE_TAGS.include?(tag) end end - def find_ranges_by_tag cs__properties, tags + # Find the ranges that satisfy all of the given tags. + # + # @param length [Integer] the length of the object which may have the + # given tags -- used as the maximum index to search for all of the + # tags. + # @param cs__properties [Contrast::Agent::Assess::Properties] the + # properties to check for the tags + # @param tags [Set<String>] the list of tags on which to match + # @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied + # by the given conditions + def find_ranges_by_all_tags length, cs__properties, tags + # if there aren't any all_tags or tags, break early + return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless cs__properties.tracked? + return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags&.any? + + # :zap: faster to treat all as any if there's only one tag + return find_ranges_by_any_tag(cs__properties, tags) if tags.length == 1 + ranges = [] + # find all the indicies on the source that have all the given tags + (0..length).each do |idx| + tags_at = cs__properties.tag_names_at(idx) + ranges << idx if tags.all? { |tag| tags_at.include?(tag) } + end + # break early if no indicies satisfy all the tags + return Contrast::Utils::ObjectShare::EMPTY_ARRAY if ranges.empty? + # chunk all the adjacent ranges + chunked = ranges.chunk_while { |i, j| i + 1 == j } + tag_ranges = [] + # and convert them into Tags + chunked.each do |join| + start = join[0] + stop = join[-1] + # add the 1 to account for end index being exclusive + tag_length = stop - start + 1 + tag_ranges = Contrast::Utils::TagUtil.ordered_merge(tag_ranges, Tag.new(tag_length, start)) + end + tag_ranges + end + + # Find the ranges that satisfy any of the given tags. + # + # @param cs__properties [Contrast::Agent::Assess::Properties] the + # properties to check for the tags + # @param tags [Set<String>] the list of tags on which to match + # @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied + # by the given conditions + def find_ranges_by_any_tag cs__properties, tags # if there aren't any all_tags or tags, break early - return ranges unless cs__properties.tracked? - return ranges unless tags&.any? + return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless cs__properties.tracked? + return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags&.any? - # find all tags that match the desired ones + ranges = [] tags.each do |desired| found = cs__properties.fetch_tag(desired) next unless found # we need to dup here so that we don't change the tags if target is # used in another trace ranges = Contrast::Utils::TagUtil.ordered_merge(ranges, found.dup) end - ranges end end end end