# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/utils/duck_utils' module Contrast module Agent module Assess module Property # This module serves to hold the functionality required for the # update of properties as they go through dataflow. module Updated # copy tags and info from source's properties to self # @param source [Object] the object from which existing properties # should be copied. # @param owner [Object] the object to which these properties apply. # @param shift [Integer] (0) how far to shift the tags during copy, # useful for insert and append operations. # @param skip_tags [Set] (nil) the tags to not copy over, # useful for propagation events that have 'untags'. def copy_from source, owner, shift = 0, skip_tags = nil return if owner.equal?(source) return unless Contrast::Agent::Assess::Tracker.tracked?(source) original = Contrast::Agent::Assess::Tracker.properties(source) return unless original adjust_duplicate(original) original.tag_keys.each do |key| next if skip_tags&.include?(key) existing = tags[key] had_existing = existing.any? value = original.fetch_tag(key) value.each do |tag| existing << tag.copy_modified(shift) end Contrast::Utils::TagUtil.size_aware_merge(owner, existing) if had_existing end end # Some propagation occurred, but we're not sure what the # exact transformation was. To be safe, we just explode # all the tags from the source to the return. # # If the return already had that tag, the existing tag # range is recycled to save us an object. # # @param source [Object] the object from which existing properties # should be copied. # @param owner [Object] the object to which these properties apply. def splat_from source, owner splat_length = Contrast::Utils::StringUtils.ret_length(owner) return if splat_length.zero? splat_from_ret(splat_length) splat_from_source(source, splat_length) cleanup_tags end private # Because of how our tracking works now, sometimes the Source and # Target are the same, but their IDs in our map will be different due # to PreShift duplication. To account for this, we have to ensure that # the Object we're copying from does not have the same Properties # that the Object we're copying to does. If they are the same, wipe the # Target so that the copy method can update events and ranges as # necessary. # DO NOT TAKE THIS OUT! def adjust_duplicate original reset_properties if original == self reset_properties if original.__id__ == dupped_from reset_properties if original.dupped_from == __id__ end # Wipe out the instance variables on this Properties instance, # allowing them to be rebuilt. def reset_properties @_tags = nil @_events = nil @_properties = nil end # Splat all the tags from the source to this set of Properties # # @param source [Object] the object from which tags will be copied # and splatted. # @param splat_length [Integer] the length to which to to set all # tags. def splat_from_source source, splat_length properties = Contrast::Agent::Assess::Tracker.properties(source) return unless properties properties.tag_keys.each do |key| existing = fetch_tag(key) # if the tag already exists, drop all but the first range # then change that range to cover the entire return if existing existing.drop(existing.length - 1) range = existing[0] range.repurpose(0, splat_length) else add_tag(key, 0...splat_length) end end end # Splat all the tags existing on this set of Properties # # @param splat_length [Integer] the length to which to to set all # tags. def splat_from_ret splat_length return unless tracked? tag_keys.each do |key| next unless key existing = fetch_tag(key) next unless existing existing.each do |range| range.repurpose(0, splat_length) end end end end end end end end