require_relative '../context' module Immunio module IOHooks def self.paused old_paused = Thread.current["immunio.file_io_paused"] Thread.current["immunio.file_io_paused"] = true begin yield ensure Thread.current["immunio.file_io_paused"] = old_paused end end def self.paused? !!Thread.current["immunio.file_io_paused"] end def self.inject(mod, name, methods) mod.class_eval <<-EOF def self.extended(base) #{methods.inspect}.each do |method| Immunio::Utils.alias_method_chain base.singleton_class, method, :immunio end end def self.included(base) #{methods.inspect}.each do |method| Immunio::Utils.alias_method_chain base, method, :immunio end end EOF Immunio.logger.debug { "IO: successfully chained #{name} #{methods}" } methods.each do |method| mod.class_eval <<-EOF def #{method}_with_immunio(*args, &block) # def read_with_immunio(*args, &block) if Immunio::IOHooks.paused? #{method}_without_immunio(*args, &block) else Request.time "plugin", "IO::#{method}" do # strict_context, loose_context, stack = Immunio::Context.context() Immunio.run_hook! "io", "file_io", # Immunio.run_hook! "io", "open", method: "#{name}#{method}", # open_method: "IO.read", parameters: args, # parameters: args stack: stack, # context_key: loose_context, # cwd: Dir.pwd Request.pause "plugin", "IO::#{method}" do # #{method}_without_immunio(*args, &block) # read_without_immunio(*args, &block) end end end end # end EOF Immunio.logger.debug { "IO: successfully created hook for #{name} #{method}" } end end end module IOClassHooks IOHooks.inject self, "IO.", %w( read write binread binwrite readlines sysopen copy_stream popen ) end Immunio.logger.debug { "IO: IOClassHooks created: #{IOClassHooks}" } module KernelModuleHooks # exec() is not included currently as it replaces the running process # and would never get primed correctly IOHooks.inject self, "Kernel.", %w( open system spawn ) # Special handling for hooking the backtick character # In *theory* this could be rolled into self.inject above. # In practice it's fugly and breaks the world. Kernel.class_eval <<-EOF define_method :backtick_with_immunio do |cmd| Request.time "plugin", "Shell::backtick" do strict_context, loose_context, stack = Immunio::Context.context() Immunio.run_hook! "io", "file_io", method: "Kernel.backtick", parameters: [cmd], stack: stack, context_key: loose_context, cwd: Dir.pwd Request.pause "plugin", "Shell::backtick" do Kernel.send(:backtick_without_immunio, cmd) end end end EOF Kernel.send :alias_method, :backtick_without_immunio, :` Kernel.send :alias_method, :`, :backtick_with_immunio end Immunio.logger.debug { "Shell: KernelModuleHooks created: #{KernelModuleHooks}" } module FileClassHooks IOHooks.inject self, "File.", %w( new open ) end Immunio.logger.debug { "IO: FileClassHooks created: #{FileClassHooks}" } end # Add FileIO hooks if enabled Immunio::Plugin.load 'IO', feature: 'file_io', hooks: %w( file_io ) do |plugin| IO.extend Immunio::IOClassHooks File.extend Immunio::FileClassHooks plugin.loaded! RUBY_VERSION end # Add Kernel hooks if enabled Immunio::Plugin.load 'Kernel (shell_command)', feature: 'shell_command', hooks: %w( shell_io ) do |plugin| # Both are necessary to hook calling both Kernel.open() and open() etc. Kernel.send :include, Immunio::KernelModuleHooks Kernel.extend Immunio::KernelModuleHooks plugin.loaded! RUBY_VERSION end