From 236db52d3d8ef2747ef767cb6fea8ea03f67a54c Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 6 Nov 2012 16:30:41 -0600 Subject: [PATCH 1/6] Add simplecov for code coverage Conflicts: Gemfile.lock --- .gitignore | 2 ++ Gemfile | 3 +++ Gemfile.lock | 5 +++++ spec/spec_helper.rb | 5 +++++ 4 files changed, 15 insertions(+) diff --git a/.gitignore b/.gitignore index fa85fdbfbf..5651548b7f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ .yardoc # Mac OS X files .DS_Store +# simplecov coverage data +coverage data/meterpreter/ext_server_pivot.dll data/meterpreter/ext_server_pivot.x64.dll doc diff --git a/Gemfile b/Gemfile index 825f532450..77d2123dae 100755 --- a/Gemfile +++ b/Gemfile @@ -24,4 +24,7 @@ end group :test do # testing framework gem 'rspec' + # code coverage for tests + # any version newer than 0.5.4 gives an Encoding error when trying to read the source files. + gem 'simplecov', '0.5.4', :require => false end diff --git a/Gemfile.lock b/Gemfile.lock index 10894e9139..a1fddbd358 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -45,6 +45,10 @@ GEM rspec-expectations (2.11.3) diff-lcs (~> 1.1.3) rspec-mocks (2.11.3) + simplecov (0.5.4) + multi_json (~> 1.0.3) + simplecov-html (~> 0.5.3) + simplecov-html (0.5.3) slop (3.3.3) tzinfo (0.3.33) yard (0.8.2.1) @@ -60,4 +64,5 @@ DEPENDENCIES rake redcarpet rspec + simplecov (= 0.5.4) yard diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f62338294f..6f8932bc27 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,6 +8,11 @@ root_pathname = spec_pathname.join('..').expand_path lib_pathname = root_pathname.join('lib') $LOAD_PATH.unshift(lib_pathname.to_s) +# must be first require and started before any other requires so that it can measure coverage of all following required +# code. It is after the rubygems and bundler only because Bundler.setup supplies the LOAD_PATH to simplecov. +require 'simplecov' +SimpleCov.start + require 'rspec/core' # Requires supporting ruby files with custom matchers and macros, etc, From 16407f91c892e6ae537d3192f1cd91561ca0e52e Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 6 Nov 2012 17:38:38 -0600 Subject: [PATCH 2/6] Rescue Errno::ENOENT from File.open in read_module_content [Fixes #38426061, #38097411] Msf::Modules::Loader::Directory#read_module_content may calculate a non-existent module_path that gets passed to File.open causing an Errno::ENOENT exception to be raised when using the module cache with a module that has been moved to a new path (as is the case that originally found this bug) or deleted. Now, the exception is rescued and read_module_content returns an empty string (''), which load_module detects with module_content.empty? and returns earlier without attempting to module eval the (empty) content. As having Msf::Modules::Loader::Directory#read_module_content rescue the exception, meant there was another place that needed to log and error and store an error in Msf::ModuleManager#module_load_error_by_path, I refactored the error reporting to call Msf::Modules::Loader::Base#load_error, which handles writing to the log and setting the Hash, so the error reporting is consistent across the loaders. The exception hierarchy was also refactored so that namespace_module.metasploit_class now has an error raising counter-part: namespace_module.metasploit_class! that can be used with Msf::Modules::Loader::Base#load_error as it requires an exception, and not just a string so the exception class, message, and backtrace can be logged. --- lib/msf/core/modules/error.rb | 38 +++ lib/msf/core/modules/loader/base.rb | 62 ++-- lib/msf/core/modules/loader/directory.rb | 18 +- .../metasploit_class_compatibility_error.rb | 13 + lib/msf/core/modules/namespace.rb | 15 +- .../modules/version_compatibility_error.rb | 33 ++- spec/lib/msf/core/modules/error_spec.rb | 101 +++++++ spec/lib/msf/core/modules/loader/base_spec.rb | 105 +++---- .../msf/core/modules/loader/directory_spec.rb | 122 +++++++- ...tasploit_class_compatibility_error_spec.rb | 7 + spec/lib/msf/core/modules/namespace_spec.rb | 267 ++++++++++++++++++ .../version_compatibility_error_spec.rb | 62 ++++ .../contexts/msf_modules_error_attributes.rb | 13 + .../contexts/msf_modules_loader_base.rb | 13 + .../msf_modules_error_subclass_initialize.rb | 26 ++ ...msf_modules_version_compatibility_error.rb | 32 +++ 16 files changed, 829 insertions(+), 98 deletions(-) create mode 100644 lib/msf/core/modules/error.rb create mode 100644 lib/msf/core/modules/metasploit_class_compatibility_error.rb create mode 100644 spec/lib/msf/core/modules/error_spec.rb create mode 100644 spec/lib/msf/core/modules/metasploit_class_compatibility_error_spec.rb create mode 100644 spec/lib/msf/core/modules/namespace_spec.rb create mode 100644 spec/lib/msf/core/modules/version_compatibility_error_spec.rb create mode 100644 spec/support/shared/contexts/msf_modules_error_attributes.rb create mode 100644 spec/support/shared/contexts/msf_modules_loader_base.rb create mode 100644 spec/support/shared/examples/msf_modules_error_subclass_initialize.rb create mode 100644 spec/support/shared/examples/msf_modules_version_compatibility_error.rb diff --git a/lib/msf/core/modules/error.rb b/lib/msf/core/modules/error.rb new file mode 100644 index 0000000000..aa5c2d5e40 --- /dev/null +++ b/lib/msf/core/modules/error.rb @@ -0,0 +1,38 @@ +# Base error class for all error under {Msf::Modules} +class Msf::Modules::Error < StandardError + def initialize(attributes={}) + @module_path = attributes[:module_path] + @module_reference_name = attributes[:module_reference_name] + + message_parts = [] + message_parts << "Failed to load module" + + if module_reference_name or module_path + clause_parts = [] + + if module_reference_name + clause_parts << module_reference_name + end + + if module_path + clause_parts << "from #{module_path}" + end + + clause = clause_parts.join(' ') + message_parts << "(#{clause})" + end + + causal_message = attributes[:causal_message] + + if causal_message + message_parts << "due to #{causal_message}" + end + + message = message_parts.join(' ') + + super(message) + end + + attr_reader :module_reference_name + attr_reader :module_path +end \ No newline at end of file diff --git a/lib/msf/core/modules/loader/base.rb b/lib/msf/core/modules/loader/base.rb index a7c304b6b0..7cf0a0563e 100644 --- a/lib/msf/core/modules/loader/base.rb +++ b/lib/msf/core/modules/loader/base.rb @@ -3,6 +3,7 @@ # require 'msf/core/modules/loader' require 'msf/core/modules/namespace' +require 'msf/core/modules/metasploit_class_compatibility_error' require 'msf/core/modules/version_compatibility_error' # Responsible for loading modules for {Msf::ModuleManager}. @@ -117,12 +118,17 @@ class Msf::Modules::Loader::Base metasploit_class = nil + module_content = read_module_content(parent_path, type, module_reference_name) + + if module_content.empty? + # read_module_content is responsible for calling {#load_error}, so just return here. + return false + end + loaded = namespace_module_transaction(type + "/" + module_reference_name, :reload => reload) { |namespace_module| # set the parent_path so that the module can be reloaded with #load_module namespace_module.parent_path = parent_path - module_content = read_module_content(parent_path, type, module_reference_name) - begin namespace_module.module_eval_with_lexical_scope(module_content, module_path) # handle interrupts as pass-throughs unlike other Exceptions so users can bail with Ctrl+C @@ -133,45 +139,33 @@ class Msf::Modules::Loader::Base begin namespace_module.version_compatible!(module_path, module_reference_name) rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error - error_message = "Failed to load module (#{module_path}) due to error and #{version_compatibility_error}" + load_error(module_path, version_compatibility_error) else - error_message = "#{error.class} #{error}" + load_error(module_path, error) end - # record the error message without the backtrace for the console - module_manager.module_load_error_by_path[module_path] = error_message - - error_message_with_backtrace = "#{error_message}:\n#{error.backtrace.join("\n")}" - elog(error_message_with_backtrace) - return false end begin namespace_module.version_compatible!(module_path, module_reference_name) rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error - error_message = version_compatibility_error.to_s - - elog(error_message) - module_manager.module_load_error_by_path[module_path] = error_message + load_error(module_path, version_compatibility_error) return false end - metasploit_class = namespace_module.metasploit_class + begin + metasploit_class = namespace_module.metasploit_class!(module_path, module_reference_name) + rescue Msf::Modules::MetasploitClassCompatibilityError => error + load_error(module_path, error) - unless metasploit_class - error_message = "Missing Metasploit class constant" - - elog(error_message) - module_manager.module_load_error_by_path[module_path] = error_message - - return false + return false end unless usable?(metasploit_class) ilog( - "Skipping module #{module_reference_name} under #{parent_path} because is_usable returned false.", + "Skipping module (#{module_reference_name} from #{module_path}) because is_usable returned false.", 'core', LEV_1 ) @@ -409,6 +403,28 @@ class Msf::Modules::Loader::Base raise ::NotImplementedError end + # Records the load error to {Msf::ModuleManager::Loading#module_load_error_by_path} and the log. + # + # @param [String] module_path Path to the module as returned by {#module_path}. + # @param [Exception, #class, #to_s, #backtrace] error the error that cause the module not to load. + # @return [void] + # + # @see #module_path + def load_error(module_path, error) + # module_load_error_by_path does not get the backtrace because the value is echoed to the msfconsole where + # backtraces should not appear. + module_manager.module_load_error_by_path[module_path] = "#{error.class} #{error}" + + log_lines = [] + log_lines << "#{module_path} failed to load due to the following error:" + log_lines << error.class.to_s + log_lines << error.to_s + log_lines += error.backtrace + + log_message = log_lines.join("\n") + elog(log_message) + end + # @return [Msf::ModuleManager] The module manager for which this loader is loading modules. attr_reader :module_manager diff --git a/lib/msf/core/modules/loader/directory.rb b/lib/msf/core/modules/loader/directory.rb index a549fdb188..420cbb109e 100644 --- a/lib/msf/core/modules/loader/directory.rb +++ b/lib/msf/core/modules/loader/directory.rb @@ -75,13 +75,17 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base module_content = '' - # force to read in binary mode so Pro modules won't be truncated on Windows - File.open(full_path, 'rb') do |f| - # Pass the size of the file as it leads to faster reads due to fewer buffer resizes. Greatest effect on Windows. - # @see http://www.ruby-forum.com/topic/209005 - # @see https://github.com/ruby/ruby/blob/ruby_1_8_7/io.c#L1205 - # @see https://github.com/ruby/ruby/blob/ruby_1_9_3/io.c#L2038 - module_content = f.read(f.stat.size) + begin + # force to read in binary mode so Pro modules won't be truncated on Windows + File.open(full_path, 'rb') do |f| + # Pass the size of the file as it leads to faster reads due to fewer buffer resizes. Greatest effect on Windows. + # @see http://www.ruby-forum.com/topic/209005 + # @see https://github.com/ruby/ruby/blob/ruby_1_8_7/io.c#L1205 + # @see https://github.com/ruby/ruby/blob/ruby_1_9_3/io.c#L2038 + module_content = f.read(f.stat.size) + end + rescue Errno::ENOENT => error + load_error(full_path, error) end module_content diff --git a/lib/msf/core/modules/metasploit_class_compatibility_error.rb b/lib/msf/core/modules/metasploit_class_compatibility_error.rb new file mode 100644 index 0000000000..e5d8193556 --- /dev/null +++ b/lib/msf/core/modules/metasploit_class_compatibility_error.rb @@ -0,0 +1,13 @@ +require 'msf/core/modules/error' + +# Error raised by {Msf::Modules::Namespace#metasploit_class!} if it cannot the namespace_module does not have a constant +# with {Msf::Framework::Major} or lower as a number after 'Metasploit', which indicates a compatible Msf::Module. +class Msf::Modules::MetasploitClassCompatibilityError < Msf::Modules::Error + def initialize(attributes={}) + super_attributes = { + :causal_message => 'Missing compatible Metasploit class constant', + }.merge(attributes) + + super(super_attributes) + end +end \ No newline at end of file diff --git a/lib/msf/core/modules/namespace.rb b/lib/msf/core/modules/namespace.rb index 0d5a62a0a0..7841952464 100644 --- a/lib/msf/core/modules/namespace.rb +++ b/lib/msf/core/modules/namespace.rb @@ -10,8 +10,6 @@ module Msf::Modules::Namespace # @return [nil] if such as class is not defined. def metasploit_class metasploit_class = nil - # don't search ancestors for the metasploit_class - #inherit = false ::Msf::Framework::Major.downto(1) do |major| # Since we really only care about the deepest namespace, we don't @@ -29,6 +27,19 @@ module Msf::Modules::Namespace metasploit_class end + def metasploit_class!(module_path, module_reference_name) + metasploit_class = self.metasploit_class + + unless metasploit_class + raise Msf::Modules::MetasploitClassCompatibilityError.new( + :module_path => module_path, + :module_reference_name => module_reference_name + ) + end + + metasploit_class + end + # Raises an error unless {Msf::Framework::VersionCore} and {Msf::Framework::VersionAPI} meet the minimum required # versions defined in RequiredVersions in the module content. # diff --git a/lib/msf/core/modules/version_compatibility_error.rb b/lib/msf/core/modules/version_compatibility_error.rb index 00c5341046..638f6d361d 100644 --- a/lib/msf/core/modules/version_compatibility_error.rb +++ b/lib/msf/core/modules/version_compatibility_error.rb @@ -1,20 +1,43 @@ +require 'msf/core/modules/error' + # Error raised by {Msf::Modules::Namespace#version_compatible!} on {Msf::Modules::Loader::Base#create_namespace_module} # if the API or Core version does not meet the minimum requirements defined in the RequiredVersions constant in the # {Msf::Modules::Loader::Base#read_module_content module content}. -class Msf::Modules::VersionCompatibilityError < StandardError +class Msf::Modules::VersionCompatibilityError < Msf::Modules::Error # @param [Hash{Symbol => Float}] attributes # @option attributes [Float] :minimum_api_version The minimum {Msf::Framework::VersionAPI} as defined in # RequiredVersions. # @option attributes [Float] :minimum_core_version The minimum {Msf::Framework::VersionCore} as defined in # RequiredVersions. def initialize(attributes={}) - @module_path = attributes[:module_path] - @module_reference_name = attributes[:module_reference_name] @minimum_api_version = attributes[:minimum_api_version] @minimum_core_version = attributes[:minimum_core_version] - super("Failed to reload module (#{module_reference_name} from #{module_path}) due to version check " \ - "(requires API:#{minimum_api_version} Core:#{minimum_core_version})") + message_parts = [] + message_parts << 'version check' + + if minimum_api_version or minimum_core_version + clause_parts = [] + + if minimum_api_version + clause_parts << "API >= #{minimum_api_version}" + end + + if minimum_core_version + clause_parts << "Core >= #{minimum_core_version}" + end + + clause = clause_parts.join(' and ') + message_parts << "(requires #{clause})" + end + + causal_message = message_parts.join(' ') + + super_attributes = { + :causal_message => causal_message + }.merge(attributes) + + super(super_attributes) end # @return [Float] The minimum value of {Msf::Framework::VersionAPI} for the module to be compatible. diff --git a/spec/lib/msf/core/modules/error_spec.rb b/spec/lib/msf/core/modules/error_spec.rb new file mode 100644 index 0000000000..144f485573 --- /dev/null +++ b/spec/lib/msf/core/modules/error_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe Msf::Modules::Error do + context 'instance methods' do + context '#initialize' do + include_context 'Msf::Modules::Error attributes' + + context 'with :causal_message' do + subject do + described_class.new(:causal_message => causal_message) + end + + it 'should include causal_message in error' do + subject.to_s.should == "Failed to load module due to #{causal_message}" + end + end + + context 'with :causal_message and :module_path' do + subject do + described_class.new( + :causal_message => causal_message, + :module_path => module_path + ) + end + + it 'should include causal_message and module_path in error' do + subject.to_s.should == "Failed to load module (from #{module_path}) due to #{causal_message}" + end + end + + context 'with :causal_message and :module_reference_name' do + subject do + described_class.new( + :causal_message => causal_message, + :module_reference_name => module_reference_name + ) + end + + it 'should include causal_message and module_reference_name in error' do + subject.to_s.should == "Failed to load module (#{module_reference_name}) due to #{causal_message}" + end + end + + context 'with :causal_message, :module_path, and :module_reference_nam' do + subject do + described_class.new( + :causal_message => causal_message, + :module_path => module_path, + :module_reference_name => module_reference_name + ) + end + + it 'should include causal_message, module_path, and module_reference_name in error' do + subject.to_s.should == "Failed to load module (#{module_reference_name} from #{module_path}) due to #{causal_message}" + end + end + + context 'with :module_path' do + subject do + described_class.new(:module_path => module_path) + end + + it 'should use :module_path for module_path' do + subject.module_path.should == module_path + end + + it 'should include module_path in error' do + subject.to_s.should == "Failed to load module (from #{module_path})" + end + end + + context 'with :module_path and :module_reference_name' do + subject do + described_class.new( + :module_path => module_path, + :module_reference_name => module_reference_name + ) + end + + it 'should include module_path and module_reference_name in error' do + subject.to_s.should == "Failed to load module (#{module_reference_name} from #{module_path})" + end + end + + context 'with :module_reference_name' do + subject do + described_class.new(:module_reference_name => module_reference_name) + end + + it 'should use :module_reference_name for module_reference_name' do + subject.module_reference_name.should == module_reference_name + end + + it 'should include module_reference_name in error' do + subject.to_s.should == "Failed to load module (#{module_reference_name})" + end + end + + end + end +end \ No newline at end of file diff --git a/spec/lib/msf/core/modules/loader/base_spec.rb b/spec/lib/msf/core/modules/loader/base_spec.rb index df0207d4c4..efce31a1a0 100644 --- a/spec/lib/msf/core/modules/loader/base_spec.rb +++ b/spec/lib/msf/core/modules/loader/base_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' require 'msf/core' describe Msf::Modules::Loader::Base do + include_context 'Msf::Modules::Loader::Base' + let(:described_class_pathname) do root_pathname.join('lib', 'msf', 'core', 'modules', 'loader', 'base.rb') end @@ -37,18 +39,6 @@ describe Msf::Modules::Loader::Base do 'rspec/mock' end - let(:parent_path) do - parent_pathname.to_s - end - - let(:parent_pathname) do - root_pathname.join('modules') - end - - let(:root_pathname) do - Pathname.new(Msf::Config.install_root) - end - let(:type) do Msf::MODULE_AUX end @@ -230,7 +220,7 @@ describe Msf::Modules::Loader::Base do context 'instance methods' do let(:module_manager) do - mock('Module Manager') + mock('Module Manager', :module_load_error_by_path => {}) end subject do @@ -323,13 +313,14 @@ describe Msf::Modules::Loader::Base do end it 'should call #namespace_module_transaction with the module full name and :reload => true' do + subject.stub(:read_module_content => module_content) + subject.should_receive(:namespace_module_transaction).with(module_full_name, hash_including(:reload => true)) subject.load_module(parent_path, type, module_reference_name) end it 'should set the parent_path on the namespace_module to match the parent_path passed to #load_module' do - module_manager.stub(:module_load_error_by_path => {}) module_manager.stub(:on_module_load) subject.stub(:read_module_content => module_content) @@ -339,7 +330,6 @@ describe Msf::Modules::Loader::Base do end it 'should call #read_module_content to get the module content so that #read_module_content can be overridden to change loading behavior' do - module_manager.stub(:module_load_error_by_path => {}) module_manager.stub(:on_module_load) subject.should_receive(:read_module_content).with(parent_path, type, module_reference_name).and_return(module_content) @@ -348,7 +338,6 @@ describe Msf::Modules::Loader::Base do it 'should call namespace_module.module_eval_with_lexical_scope with the module_path' do subject.stub(:read_module_content => malformed_module_content) - module_manager.stub(:module_load_error_by_path => {}) module_manager.stub(:on_module_load) # if the module eval error includes the module_path then the module_path was passed along correctly @@ -356,13 +345,29 @@ describe Msf::Modules::Loader::Base do subject.load_module(parent_path, type, module_reference_name, :reload => true).should be_false end + context 'with empty module content' do + before(:each) do + subject.stub(:read_module_content).with(parent_path, type, module_reference_name).and_return('') + end + + it 'should return false' do + subject.load_module(parent_path, type, module_reference_name).should be_false + end + + it 'should not attempt to make a new namespace_module' do + subject.should_not_receive(:namespace_module_transaction) + subject.load_module(parent_path, type, module_reference_name).should be_false + end + end + context 'with errors from namespace_module_eval_with_lexical_scope' do before(:each) do @namespace_module = mock('Namespace Module') @namespace_module.stub(:parent_path=) subject.stub(:namespace_module_transaction).and_yield(@namespace_module) - subject.stub(:read_module_content) + module_content = mock('Module Content', :empty? => false) + subject.stub(:read_module_content).and_return(module_content) end context 'with Interrupt' do @@ -409,16 +414,8 @@ describe Msf::Modules::Loader::Base do @namespace_module.stub(:version_compatible!).with(module_path, module_reference_name) end - it 'should report error class and string in module_manager.module_load_error_by_path' do - subject.load_module(parent_path, type, module_reference_name).should be_false - @module_load_error_by_path[module_path].should == "#{error_class} #{error}" - end - - it 'should report error class, string, and backtrace in the log' do - subject.should_receive(:elog).with( - # don't use join on backtrace as that will match implementation too closely - "#{error_class} #{error}:\n#{backtrace[0]}\n#{backtrace[1]}" - ) + it 'should record the load error using the original error' do + subject.should_receive(:load_error).with(module_path, error) subject.load_module(parent_path, type, module_reference_name).should be_false end end @@ -448,18 +445,8 @@ describe Msf::Modules::Loader::Base do ) end - it 'should report module_path and version compatibility error string in module_manager.module_load_error_by_path' do - subject.load_module(parent_path, type, module_reference_name).should be_false - - @module_load_error_by_path[module_path].should include(module_path) - @module_load_error_by_path[module_path].should include(version_compatibility_error.to_s) - end - - it 'should report backtrace of original error in the log' do - formatted_backtrace = "\n#{backtrace[0]}\n#{backtrace[1]}" - escaped_backtrace = Regexp.escape(formatted_backtrace) - - subject.should_receive(:elog).with(/#{escaped_backtrace}/) + it 'should record the load error using the Msf::Modules::VersionCompatibilityError' do + subject.should_receive(:load_error).with(module_path, version_compatibility_error) subject.load_module(parent_path, type, module_reference_name).should be_false end end @@ -479,7 +466,7 @@ describe Msf::Modules::Loader::Base do @namespace_module.stub(:module_eval_with_lexical_scope).with(module_content, module_path) metasploit_class = mock('Metasploit Class', :parent => @namespace_module) - @namespace_module.stub(:metasploit_class => metasploit_class) + @namespace_module.stub(:metasploit_class! => metasploit_class) subject.stub(:namespace_module_transaction).and_yield(@namespace_module) @@ -521,13 +508,8 @@ describe Msf::Modules::Loader::Base do ) end - it 'should report error in module_manage.module_load_error_by_path' do - subject.load_module(parent_path, type, module_reference_name).should be_false - @module_load_error_by_path[module_path].should == version_compatibility_error.to_s - end - - it 'should log error' do - subject.should_receive(:elog).with(version_compatibility_error.to_s) + it 'should record the load error' do + subject.should_receive(:load_error).with(module_path, version_compatibility_error) subject.load_module(parent_path, type, module_reference_name).should be_false end @@ -548,24 +530,27 @@ describe Msf::Modules::Loader::Base do end context 'without metasploit_class' do + let(:error) do + Msf::Modules::MetasploitClassCompatibilityError.new( + :module_path => module_path, + :module_reference_name => module_reference_name + ) + end + before(:each) do - @namespace_module.stub(:metasploit_class).and_return(nil) + @namespace_module.stub(:metasploit_class!).with(module_path, module_reference_name).and_raise(error) end - let(:error_message) do - 'Missing Metasploit class constant' - end - - it 'should log missing Metasploit class' do - subject.should_receive(:elog).with(error_message) + it 'should record load error' do + subject.should_receive( + :load_error + ).with( + module_path, + kind_of(Msf::Modules::MetasploitClassCompatibilityError) + ) subject.load_module(parent_path, type, module_reference_name).should be_false end - it 'should record error in module_manager.module_load_error_by_path' do - subject.load_module(parent_path, type, module_reference_name).should be_false - @module_load_error_by_path[module_path].should == error_message - end - it 'should return false' do subject.load_module(parent_path, type, module_reference_name).should be_false end @@ -583,7 +568,7 @@ describe Msf::Modules::Loader::Base do end before(:each) do - @namespace_module.stub(:metasploit_class => metasploit_class) + @namespace_module.stub(:metasploit_class! => metasploit_class) end it 'should check if it is usable' do diff --git a/spec/lib/msf/core/modules/loader/directory_spec.rb b/spec/lib/msf/core/modules/loader/directory_spec.rb index b371883f49..1a1728b21e 100644 --- a/spec/lib/msf/core/modules/loader/directory_spec.rb +++ b/spec/lib/msf/core/modules/loader/directory_spec.rb @@ -2,6 +2,126 @@ require 'spec_helper' require 'msf/core' require 'msf/core/modules/loader/directory' -describe Msf::Modules::Loader::Directory do +require 'msf/core' +describe Msf::Modules::Loader::Directory do + context 'instance methods' do + include_context 'Msf::Modules::Loader::Base' + + let(:module_manager) do + mock('Module Manager') + end + + let(:module_path) do + "#{parent_path}/exploits/#{module_reference_name}.rb" + end + + let(:type) do + 'exploit' + end + + subject do + described_class.new(module_manager) + end + + context '#load_module' do + context 'with existent module_path' do + let(:framework) do + framework = mock('Msf::Framework', :datastore => {}) + + events = mock('Events') + events.stub(:on_module_load) + events.stub(:on_module_created) + framework.stub(:events => events) + + framework + end + + let(:module_full_name) do + "#{type}/#{module_reference_name}" + end + + let(:module_manager) do + Msf::ModuleManager.new(framework) + end + + let(:module_reference_name) do + 'windows/smb/ms08_067_netapi' + end + + it 'should load a module that can be created' do + subject.load_module(parent_path, type, module_reference_name).should be_true + + created_module = module_manager.create(module_full_name) + + created_module.name.should == 'Microsoft Server Service Relative Path Stack Corruption' + end + end + + context 'without existent module_path' do + let(:module_reference_name) do + 'osx/armle/safari_libtiff' + end + + let(:error) do + Errno::ENOENT.new(module_path) + end + + before(:each) do + module_manager.stub(:file_changed? => true) + module_manager.stub(:module_load_error_by_path => {}) + end + + it 'should not raise an error' do + File.exist?(module_path).should be_false + + expect { + subject.load_module(parent_path, type, module_reference_name) + }.to_not raise_error + end + + it 'should return false' do + File.exist?(module_path).should be_false + + subject.load_module(parent_path, type, module_reference_name).should be_false + end + end + end + + context '#read_module_content' do + context 'with non-existent module_path' do + let(:module_reference_name) do + 'osx/armle/safari_libtiff' + end + + before(:each) do + subject.stub(:load_error).with(module_path, kind_of(Errno::ENOENT)) + end + + # this ensures that the File.exist?(module_path) checks are checking the same path as the code under test + it 'should attempt to open the expected module_path' do + File.should_receive(:open).with(module_path, 'rb') + File.exist?(module_path).should be_false + + subject.send(:read_module_content, parent_path, type, module_reference_name) + end + + it 'should not raise an error' do + expect { + subject.send(:read_module_content, parent_path, type, module_reference_name) + }.to_not raise_error + end + + it 'should return an empty string' do + subject.send(:read_module_content, parent_path, type, module_reference_name).should == '' + end + + it 'should record the load error' do + subject.should_receive(:load_error).with(module_path, kind_of(Errno::ENOENT)) + + subject.send(:read_module_content, parent_path, type, module_reference_name).should == '' + end + end + end + end end diff --git a/spec/lib/msf/core/modules/metasploit_class_compatibility_error_spec.rb b/spec/lib/msf/core/modules/metasploit_class_compatibility_error_spec.rb new file mode 100644 index 0000000000..b3a411a7d6 --- /dev/null +++ b/spec/lib/msf/core/modules/metasploit_class_compatibility_error_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +require 'msf/core/modules/metasploit_class_compatibility_error' + +describe Msf::Modules::MetasploitClassCompatibilityError do + it_should_behave_like 'Msf::Modules::Error subclass #initialize' +end \ No newline at end of file diff --git a/spec/lib/msf/core/modules/namespace_spec.rb b/spec/lib/msf/core/modules/namespace_spec.rb new file mode 100644 index 0000000000..10f10822f6 --- /dev/null +++ b/spec/lib/msf/core/modules/namespace_spec.rb @@ -0,0 +1,267 @@ +require 'spec_helper' + +require 'msf/core' +require 'msf/core/modules/namespace' + +describe Msf::Modules::Namespace do + let(:module_path) do + "parent/path/type_directory/#{module_reference_name}.rb" + end + + let(:module_reference_name) do + 'module/reference/name' + end + + subject do + mod = Module.new + mod.extend described_class + + mod + end + + context 'metasploit_class' do + before(:each) do + if major + subject.const_set("Metasploit#{major}", Class.new) + end + end + + context 'without Metasploit constant defined' do + let(:major) do + nil + end + + it 'should not be defined' do + metasploit_constants = subject.constants.select { |constant| + constant.to_s =~ /Metasploit/ + } + + metasploit_constants.should be_empty + end + end + + context 'with Metasploit1 constant defined' do + let(:major) do + 1 + end + + it 'should be defined' do + subject.const_defined?('Metasploit1').should be_true + end + + it 'should return the class' do + subject.metasploit_class.should be_a Class + end + end + + context 'with Metasploit2 constant defined' do + let(:major) do + 2 + end + + it 'should be defined' do + subject.const_defined?('Metasploit2').should be_true + end + + it 'should return the class' do + subject.metasploit_class.should be_a Class + end + end + + context 'with Metasploit3 constant defined' do + let(:major) do + 3 + end + + it 'should be defined' do + subject.const_defined?('Metasploit3').should be_true + end + + it 'should return the class' do + subject.metasploit_class.should be_a Class + end + end + + context 'with Metasploit4 constant defined' do + let(:major) do + 4 + end + + it 'should be defined' do + subject.const_defined?('Metasploit4').should be_true + end + + it 'should return the class' do + subject.metasploit_class.should be_a Class + end + end + + context 'with Metasploit5 constant defined' do + let(:major) do + 5 + end + + it 'should be defined' do + subject.const_defined?('Metasploit5').should be_true + end + + it 'should be newer than Msf::Framework::Major' do + major.should > Msf::Framework::Major + end + + it 'should return nil' do + subject.metasploit_class.should be_nil + end + end + end + + context 'metasploit_class!' do + it 'should call metasploit_class' do + subject.should_receive(:metasploit_class).and_return(Class.new) + + subject.metasploit_class!(module_path, module_reference_name) + end + + context 'with metasploit_class' do + let(:metasploit_class) do + Class.new + end + + before(:each) do + subject.stub(:metasploit_class => metasploit_class) + end + + it 'should return the metasploit_class' do + subject.metasploit_class!(module_path, module_reference_name).should == metasploit_class + end + end + + context 'without metasploit_class' do + before(:each) do + subject.stub(:metasploit_class => nil) + end + + it 'should raise a Msf::Modules::MetasploitClassCompatibilityError' do + expect { + subject.metasploit_class!(module_path, module_reference_name) + }.to raise_error(Msf::Modules::MetasploitClassCompatibilityError) + end + + context 'the Msf::Modules::MetasploitClassCompatibilityError' do + it 'should include the module path' do + error = nil + + begin + subject.metasploit_class!(module_path, module_reference_name) + rescue Msf::Modules::MetasploitClassCompatibilityError => error + end + + error.should_not be_nil + error.to_s.should include(module_path) + end + + it 'should include the module reference name' do + error = nil + + begin + subject.metasploit_class!(module_path, module_reference_name) + rescue Msf::Modules::MetasploitClassCompatibilityError => error + end + + error.should_not be_nil + error.to_s.should include(module_reference_name) + end + end + end + end + + context 'version_compatible!' do + context 'without RequiredVersions' do + it 'should not be defined' do + subject.const_defined?('RequiredVersions').should be_false + end + + it 'should not raise an error' do + expect { + subject.version_compatible!(module_path, module_reference_name) + }.to_not raise_error + end + end + + context 'with RequiredVersions defined' do + let(:minimum_api_version) do + 1 + end + + let(:minimum_core_version) do + 1 + end + + before(:each) do + subject.const_set( + :RequiredVersions, + [ + minimum_core_version, + minimum_api_version + ] + ) + end + + context 'with minimum Core version' do + it 'should be <= Msf::Framework::VersionCore' do + minimum_core_version.should <= Msf::Framework::VersionCore + end + + context 'without minimum API version' do + let(:minimum_api_version) do + 2 + end + + it 'should be > Msf::Framework::VersionAPI' do + minimum_api_version.should > Msf::Framework::VersionAPI + end + + it_should_behave_like 'Msf::Modules::VersionCompatibilityError' + end + + context 'with minimum API version' do + it 'should not raise an error' do + expect { + subject.version_compatible!(module_path, module_reference_name) + }.to_not raise_error(Msf::Modules::VersionCompatibilityError) + end + end + end + + context 'without minimum Core version' do + let(:minimum_core_version) do + 5 + end + + it 'should be > Msf::Framework::VersionCore' do + minimum_core_version.should > Msf::Framework::VersionCore + end + + context 'without minimum API version' do + let(:minimum_api_version) do + 2 + end + + it 'should be > Msf::Framework::VersionAPI' do + minimum_api_version.should > Msf::Framework::VersionAPI + end + + it_should_behave_like 'Msf::Modules::VersionCompatibilityError' + end + + context 'with minimum API version' do + it 'should be <= Msf::Framework::VersionAPI' do + minimum_api_version <= Msf::Framework::VersionAPI + end + + it_should_behave_like 'Msf::Modules::VersionCompatibilityError' + end + end + end + end +end \ No newline at end of file diff --git a/spec/lib/msf/core/modules/version_compatibility_error_spec.rb b/spec/lib/msf/core/modules/version_compatibility_error_spec.rb new file mode 100644 index 0000000000..9522f8bbf7 --- /dev/null +++ b/spec/lib/msf/core/modules/version_compatibility_error_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +describe Msf::Modules::VersionCompatibilityError do + it_should_behave_like 'Msf::Modules::Error subclass #initialize' do + let(:minimum_api_version) do + 1 + end + + let(:minimum_core_version) do + 2 + end + + it 'should say cause was version check' do + subject.to_s.should match(/due to version check/) + end + + context 'with :minimum_api_version' do + subject do + described_class.new( + :minimum_api_version => minimum_api_version + ) + end + + it 'should set minimum_api_version' do + subject.minimum_api_version.should == minimum_api_version + end + + it 'should include minimum_api_version in error' do + subject.to_s.should match(/due to version check \(requires API >= #{minimum_api_version}\)/) + end + end + + context 'with :minimum_api_version and :minimum_core_version' do + subject do + described_class.new( + :minimum_api_version => minimum_api_version, + :minimum_core_version => minimum_core_version + ) + end + + it 'should include minimum_api_version and minimum_core_version in error' do + subject.to_s.should match(/due to version check \(requires API >= #{minimum_api_version} and Core >= #{minimum_core_version}\)/) + end + end + + context 'with :minimum_core_version' do + subject do + described_class.new( + :minimum_core_version => minimum_core_version + ) + end + + it 'should set minimum_core_version' do + subject.minimum_core_version.should == minimum_core_version + end + + it 'should include minimum_core_version in error' do + subject.to_s.should match(/due to version check \(requires Core >= #{minimum_core_version}\)/) + end + end + end +end \ No newline at end of file diff --git a/spec/support/shared/contexts/msf_modules_error_attributes.rb b/spec/support/shared/contexts/msf_modules_error_attributes.rb new file mode 100644 index 0000000000..74d6366f58 --- /dev/null +++ b/spec/support/shared/contexts/msf_modules_error_attributes.rb @@ -0,0 +1,13 @@ +shared_context 'Msf::Modules::Error attributes' do + let(:causal_message) do + 'rspec' + end + + let(:module_path) do + "parent/path/type/#{module_reference_name}.rb" + end + + let(:module_reference_name) do + 'module/reference/name' + end +end \ No newline at end of file diff --git a/spec/support/shared/contexts/msf_modules_loader_base.rb b/spec/support/shared/contexts/msf_modules_loader_base.rb new file mode 100644 index 0000000000..27d4a4e56a --- /dev/null +++ b/spec/support/shared/contexts/msf_modules_loader_base.rb @@ -0,0 +1,13 @@ +shared_context "Msf::Modules::Loader::Base" do + let(:parent_path) do + parent_pathname.to_s + end + + let(:parent_pathname) do + root_pathname.join('modules') + end + + let(:root_pathname) do + Pathname.new(Msf::Config.install_root) + end +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf_modules_error_subclass_initialize.rb b/spec/support/shared/examples/msf_modules_error_subclass_initialize.rb new file mode 100644 index 0000000000..43a753a044 --- /dev/null +++ b/spec/support/shared/examples/msf_modules_error_subclass_initialize.rb @@ -0,0 +1,26 @@ +shared_examples_for 'Msf::Modules::Error subclass #initialize' do + context 'instance methods' do + context '#initialize' do + include_context 'Msf::Modules::Error attributes' + + subject do + described_class.new( + :module_path => module_path, + :module_reference_name => module_reference_name + ) + end + + it 'should include causal message in error' do + subject.to_s.should match(/due to .*/) + end + + it 'should set module_path' do + subject.module_path.should == module_path + end + + it 'should set module_reference_name' do + subject.module_reference_name.should == module_reference_name + end + end + end +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf_modules_version_compatibility_error.rb b/spec/support/shared/examples/msf_modules_version_compatibility_error.rb new file mode 100644 index 0000000000..a2f11c7924 --- /dev/null +++ b/spec/support/shared/examples/msf_modules_version_compatibility_error.rb @@ -0,0 +1,32 @@ +shared_examples_for 'Msf::Modules::VersionCompatibilityError' do + let(:error) do + begin + subject.version_compatible!(module_path, module_reference_name) + rescue Msf::Modules::VersionCompatibilityError => error + end + + error + end + + it 'should be raised' do + expect { + subject.version_compatible!(module_path, module_reference_name) + }.to raise_error(Msf::Modules::VersionCompatibilityError) + end + + it 'should include minimum API version' do + error.to_s.should include(minimum_api_version.to_s) + end + + it 'should include minimum Core version' do + error.to_s.should include(minimum_core_version.to_s) + end + + it 'should include module path' do + error.to_s.should include(module_path) + end + + it 'should include module reference name' do + error.to_s.should include(module_reference_name) + end +end \ No newline at end of file From 726cf8bb944ede042482ad4a8154420df818d5a6 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 7 Nov 2012 08:29:44 -0600 Subject: [PATCH 3/6] travis-ci.org integration Add the .travis.yml so that travis-ci.org knows which rubies (1.8.7 and 1.9.3 to run rake against. Add the build status button to the README.md so that people can see if the specs are passing from the main rapid7/metasploit-framework page on github just like other open source projects. --- .travis.yml | 4 ++++ README.md | 1 + 2 files changed, 5 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..5a1ec27ca2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: ruby +rvm: + - '1.8.7' + - '1.9.3' \ No newline at end of file diff --git a/README.md b/README.md index 33c3172a42..d784b6da01 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ Metasploit +[![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png)](https://travis-ci.org/rapid7/metasploit-framework) == The Metasploit Framework is released under a BSD-style license. See COPYING for more details. From baffd09ce16d42f88b19d4a26d54ea4a800efb00 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 7 Nov 2012 08:51:00 -0600 Subject: [PATCH 4/6] Fix alignment of button and header --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d784b6da01..fbf2c426ea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ -Metasploit -[![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png)](https://travis-ci.org/rapid7/metasploit-framework) +Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png)](https://travis-ci.org/rapid7/metasploit-framework) == The Metasploit Framework is released under a BSD-style license. See COPYING for more details. From aaa5a3c0bb1ec7a5287ced7619a5d563c30ebf0f Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 7 Nov 2012 12:48:55 -0600 Subject: [PATCH 5/6] Add "Call stack:" to the log when a module load fails --- lib/msf/core/module_manager/loading.rb | 2 +- lib/msf/core/modules/loader/base.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/module_manager/loading.rb b/lib/msf/core/module_manager/loading.rb index 6b344537f9..0137542c0e 100644 --- a/lib/msf/core/module_manager/loading.rb +++ b/lib/msf/core/module_manager/loading.rb @@ -109,4 +109,4 @@ module Msf::ModuleManager::Loading count_by_type end -end \ No newline at end of file +end diff --git a/lib/msf/core/modules/loader/base.rb b/lib/msf/core/modules/loader/base.rb index 7cf0a0563e..651e39e412 100644 --- a/lib/msf/core/modules/loader/base.rb +++ b/lib/msf/core/modules/loader/base.rb @@ -419,6 +419,7 @@ class Msf::Modules::Loader::Base log_lines << "#{module_path} failed to load due to the following error:" log_lines << error.class.to_s log_lines << error.to_s + log_lines << "Call stack:" log_lines += error.backtrace log_message = log_lines.join("\n") From 3df9dfcea259a9aed7addc0a497bdfd16c80e3c6 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 7 Nov 2012 20:53:55 -0600 Subject: [PATCH 6/6] Disable CI testing on 1.8.7 for now To stop the flood of everything-is-broken emails --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5a1ec27ca2..8c8562baa0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: ruby rvm: - - '1.8.7' - - '1.9.3' \ No newline at end of file +# - '1.8.7' + - '1.9.3'