# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/assess/policy/policy' require 'contrast/agent/patching/policy/patcher' require 'contrast/agent/patching/policy/method_policy' require 'contrast/agent/patching/policy/module_policy' require 'contrast/components/interface' module Contrast module Agent module Assess module Policy # This is how we patch into our customer's code. It provides a way to # track which classes we need to patch into and, once we've woven, # provides a map for which methods our renamed functions need to call # and how. module Patcher include Contrast::Components::Interface access_component :logging, :analysis, :agent, :scope class << self def policy Contrast::Agent::Assess::Policy::Policy.instance end def patcher Contrast::Agent::Patching::Policy::Patcher end # Some of the methods we care about, especially those used as dynamic # sources, are truly dynamic, in that they do not exist on class # load. These methods only exist once a module or class eval has been # called. This hook is provided so that patches to those methods can # pass us execution flow once a new method has been made available. def patch_assess_on_eval mod return unless ASSESS.enabled? return if in_contrast_scope? patcher.patch_specific_module(mod) rescue StandardError => e logger.warn( 'Unable to patch assess during eval', e, module: mod.cs__name) end # Exposed so that methods can be dynamically patched on creation at # runtime, like those generated by # ActiveRecord::AttributeMethods::Read::ClassMethods#define_method_attribute CLASS_TYPES = [ Contrast::Utils::ObjectShare::CLASS, Contrast::Utils::ObjectShare::MODULE ].cs__freeze def patch_assess_method clazz, method_name # Module.define_method is called a lot in Class and Module. We # currently do not expect these define_methods to result in methods # that require patching, so for the sake of performance, we're going # to skip evaluating them class_name = clazz.cs__name return if CLASS_TYPES.include?(class_name) return unless ASSESS.enabled? source_nodes = Contrast::Agent::Patching::Policy::ModulePolicy.nodes_for_module(policy.sources, class_name) return if source_nodes.empty? method_array = [] method_array << method_name source_nodes.each do |source_node| next unless source_node.method_name.to_s == method_name method_policy = Contrast::Agent::Patching::Policy::MethodPolicy.new(source_node: source_node, method_name: source_node.method_name, method_visibility: source_node.method_visibility, instance_method: true) patcher.patch_method(clazz, method_array, method_policy) end rescue StandardError => e logger.warn( 'Unable to patch assess during define_method_attribute', e, module: mod.cs__name) end end end end end end end