Skip to content

Commit 69a8739

Browse files
committed
Pass module_path instead of parent_path to file_changed?
[Fixes #37630057] Modules were always being detected as having file changes because the parent_path directory, instead of the actual module_path, was being passed to module_manager.file_changed?, which caused the modification times to not match. To ensure this change fixes the ambiguous module warnings, a full spec for Msf::Core::Modules::Loader::Base has been written. spec/msf has moved to spec/lib/msf to match conventional spec layout and allow for the spec/support directory to not be confused as a lib subdirectory being tested.
1 parent 7d848c7 commit 69a8739

File tree

8 files changed

+1598
-26
lines changed

8 files changed

+1598
-26
lines changed

lib/msf/core.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@
1919

2020
module Msf
2121
LogSource = "core"
22+
23+
# Returns Pathname of the root of the Metasploit Framework source
24+
#
25+
# @return [Pathname] root directory above lib, data, modules, etc.
26+
def self.root
27+
unless instance_variable_defined? :@root
28+
msf_pathname = Pathname.new(__FILE__).dirname
29+
lib_pathname = msf_pathname.parent
30+
31+
@root = lib_pathname.parent
32+
end
33+
34+
@root
35+
end
2236
end
2337

2438
# General

lib/msf/core/module_manager/loading.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ module Msf::ModuleManager::Loading
2626
def file_changed?(path)
2727
changed = false
2828

29-
module_details = self.module_info_by_path[path]
29+
module_info = self.module_info_by_path[path]
3030

3131
# if uncached then it counts as changed
3232
# Payloads can't be cached due to stage/stager matching
33-
if module_details.nil? or module_details[:mtype] == Msf::MODULE_PAYLOAD
33+
if module_info.nil? or module_info[:modification_time] == Msf::MODULE_PAYLOAD
3434
changed = true
3535
else
3636
begin
@@ -39,7 +39,7 @@ def file_changed?(path)
3939
# if the file does not exist now, that's a change
4040
changed = true
4141
else
42-
cached_modification_time = module_details[:mtime].to_i
42+
cached_modification_time = module_info[:modification_time].to_i
4343

4444
# if the file's modification time's different from the cache, then it's changed
4545
if current_modification_time != cached_modification_time

lib/msf/core/modules/loader/base.rb

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#
44
require 'msf/core/modules/loader'
55
require 'msf/core/modules/namespace'
6+
require 'msf/core/modules/version_compatibility_error'
67

78
# Responsible for loading modules for {Msf::ModuleManager}.
89
#
@@ -77,7 +78,6 @@ def loadable?(path)
7778
raise ::NotImplementedError
7879
end
7980

80-
8181
# Loads a module from the supplied path and module_reference_name.
8282
#
8383
# @param [String] parent_path The path under which the module exists. This is not necessarily the same path as passed
@@ -104,14 +104,18 @@ def load_module(parent_path, type, module_reference_name, options={})
104104
force = options[:force] || false
105105
reload = options[:reload] || false
106106

107-
unless force or module_manager.file_changed?(parent_path)
108-
dlog("Cached module from #{parent_path} has not changed.", 'core', LEV_2)
107+
module_path = self.module_path(parent_path, type, module_reference_name)
108+
file_changed = module_manager.file_changed?(module_path)
109+
110+
unless force or file_changed
111+
dlog("Cached module from #{module_path} has not changed.", 'core', LEV_2)
109112

110113
return false
111114
end
112115

116+
reload ||= force || file_changed
117+
113118
metasploit_class = nil
114-
module_path = self.module_path(parent_path, type, module_reference_name)
115119

116120
loaded = namespace_module_transaction(type + "/" + module_reference_name, :reload => reload) { |namespace_module|
117121
# set the parent_path so that the module can be reloaded with #load_module
@@ -121,7 +125,7 @@ def load_module(parent_path, type, module_reference_name, options={})
121125

122126
begin
123127
namespace_module.module_eval_with_lexical_scope(module_content, module_path)
124-
# handle interrupts as pass-throughs unlike other Exceptions
128+
# handle interrupts as pass-throughs unlike other Exceptions so users can bail with Ctrl+C
125129
rescue ::Interrupt
126130
raise
127131
rescue ::Exception => error
@@ -191,6 +195,11 @@ def load_module(parent_path, type, module_reference_name, options={})
191195
# not trigger an ambiguous name warning, which would cause the reloaded module to not be stored in the
192196
# ModuleManager.
193197
module_manager.delete(module_reference_name)
198+
199+
# Delete the original copy of the module in the type-specific module set stores the reloaded module and doesn't
200+
# trigger an ambiguous name warning
201+
module_set = module_manager.module_set(type)
202+
module_set.delete(module_reference_name)
194203
end
195204

196205
# Do some processing on the loaded module to get it into the right associations
@@ -290,7 +299,6 @@ def reload_module(original_metasploit_class_or_instance)
290299

291300
dlog("Reloading module #{module_reference_name}...", 'core')
292301

293-
294302
if load_module(parent_path, type, module_reference_name, :force => true, :reload => true)
295303
# Create a new instance of the module
296304
reloaded_module_instance = module_manager.create(module_reference_name)
@@ -486,20 +494,18 @@ def namespace_module_transaction(uniq_module_reference_name, options={}, &block)
486494
elog("Reloading namespace_module #{previous_namespace_module} when :reload => false")
487495
end
488496

497+
relative_name = namespace_module_names.last
498+
489499
if previous_namespace_module
490500
parent_module = previous_namespace_module.parent
491-
relative_name = namespace_module_names.last
492-
493-
# remove_const is private, so invoke in instance context
494-
parent_module.instance_eval do
495-
remove_const relative_name
496-
end
497-
else
498-
parent_module = nil
499-
relative_name = namespace_module_names.last
501+
# remove_const is private, so use send to bypass
502+
parent_module.send(:remove_const, relative_name)
500503
end
501504

502505
namespace_module = create_namespace_module(namespace_module_names)
506+
# Get the parent module from the created module so that restore_namespace_module can remove namespace_module's
507+
# constant if needed.
508+
parent_module = namespace_module.parent
503509

504510
begin
505511
loaded = block.call(namespace_module)
@@ -534,17 +540,29 @@ def read_module_content(parent_path, type, module_reference_name)
534540
#
535541
# @param [Module] parent_module The .parent of namespace_module before it was removed from the constant tree.
536542
# @param [String] relative_name The name of the constant under parent_module where namespace_module was attached.
537-
# @param [Module] namespace_module The previous namespace module containing the old module content.
543+
# @param [Module, nil] namespace_module The previous namespace module containing the old module content. If `nil`,
544+
# then the relative_name constant is removed from parent_module, but nothing is set as the new constant.
538545
# @return [void]
539546
def restore_namespace_module(parent_module, relative_name, namespace_module)
540-
if parent_module and namespace_module
541-
# the const may have been redefined by {#create_namespace_module}, in which case that new namespace_module needs
542-
# to be removed so the original can replace it.
543-
if parent_module.const_defined? relative_name
544-
remove_const relative_name
547+
if parent_module
548+
# If there is a current module with relative_name
549+
if parent_module.const_defined?(relative_name)
550+
# if the current value isn't the value to be restored.
551+
if parent_module.const_get(relative_name) != namespace_module
552+
# remove_const is private, so use send to bypass
553+
parent_module.send(:remove_const, relative_name)
554+
555+
# if there was a previous module, not set it to the name
556+
if namespace_module
557+
parent_module.const_set(relative_name, namespace_module)
558+
end
559+
end
560+
else
561+
# if there was a previous module, but there isn't a current module, then restore the previous module
562+
if namespace_module
563+
parent_module.const_set(relative_name, namespace_module)
564+
end
545565
end
546-
547-
parent_module.const_set(relative_name, namespace_module)
548566
end
549567
end
550568

spec/msf/core/module_manager_spec.rb renamed to spec/lib/msf/core/module_manager_spec.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,78 @@
107107
end
108108
end
109109
end
110+
111+
context '#file_changed?' do
112+
let(:module_basename) do
113+
[basename_prefix, '.rb']
114+
end
115+
116+
it 'should return true if module info is not cached' do
117+
Tempfile.open(module_basename) do |tempfile|
118+
module_path = tempfile.path
119+
120+
subject.send(:module_info_by_path)[module_path].should be_nil
121+
subject.file_changed?(module_path).should be_true
122+
end
123+
end
124+
125+
it 'should return true if the cached modification time is Msf::MODULE_PAYLOAD' do
126+
Tempfile.open(module_basename) do |tempfile|
127+
module_path = tempfile.path
128+
129+
subject.send(:module_info_by_path)[module_path] = {
130+
:modification_time => Msf::MODULE_PAYLOAD
131+
}
132+
133+
subject.file_changed?(module_path).should be_true
134+
end
135+
end
136+
137+
context 'with cache module info and not a payload module' do
138+
it 'should return true if the file does not exist on the file system' do
139+
tempfile = Tempfile.new(module_basename)
140+
module_path = tempfile.path
141+
modification_time = File.mtime(module_path).to_i
142+
143+
subject.send(:module_info_by_path)[module_path] = {
144+
:modification_time => modification_time
145+
}
146+
147+
tempfile.unlink
148+
149+
File.exist?(module_path).should be_false
150+
subject.file_changed?(module_path).should be_true
151+
end
152+
153+
it 'should return true if modification time does not match the cached modification time' do
154+
Tempfile.open(module_basename) do |tempfile|
155+
module_path = tempfile.path
156+
modification_time = File.mtime(module_path).to_i
157+
cached_modification_time = (modification_time * rand).to_i
158+
159+
subject.send(:module_info_by_path)[module_path] = {
160+
:modification_time => cached_modification_time
161+
}
162+
163+
cached_modification_time.should_not == modification_time
164+
subject.file_changed?(module_path).should be_true
165+
end
166+
end
167+
168+
it 'should return false if modification time does match the cached modification time' do
169+
Tempfile.open(module_basename) do |tempfile|
170+
module_path = tempfile.path
171+
modification_time = File.mtime(module_path).to_i
172+
cached_modification_time = modification_time
173+
174+
subject.send(:module_info_by_path)[module_path] = {
175+
:modification_time => cached_modification_time
176+
}
177+
178+
cached_modification_time.should == modification_time
179+
subject.file_changed?(module_path).should be_false
180+
end
181+
end
182+
end
183+
end
110184
end

0 commit comments

Comments
 (0)