Skip to content

Commit d04441f

Browse files
committed
Merge branch 'landing/4207' into upstream-master
Land rapid7#4207 * Ensure that `rake spec` doesn't create too many threads
2 parents 267f93f + 33b4238 commit d04441f

File tree

7 files changed

+104
-23
lines changed

7 files changed

+104
-23
lines changed

lib/metasploit/framework.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ module Framework
3535
extend ActiveSupport::Autoload
3636

3737
autoload :Spec
38+
autoload :ThreadFactoryProvider
3839

3940
# Returns the root of the metasploit-framework project. Use in place of
4041
# `Rails.root`.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Wraps {Msf::Framework} so that {Msf::Framework#threads} is only created on the first call to {#spawn} by
2+
# {Rex::ThreadFactory#spawn}, which allows the threads used by {Msf::ThreadManager} to be created lazily.
3+
#
4+
# @example Setting Rex::ThreadFactory.provider and spawning threads
5+
# Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: framework)
6+
# # framework.threads created here
7+
# Rex::ThreadFactory.spawn("name", false) { ... }
8+
#
9+
class Metasploit::Framework::ThreadFactoryProvider < Metasploit::Model::Base
10+
#
11+
# Attributes
12+
#
13+
14+
# @!attribute framework
15+
# The framework managing the spawned threads.
16+
#
17+
# @return [Msf::Framework]
18+
attr_accessor :framework
19+
20+
# Spawns a thread monitored by {Msf::ThreadManager} in {Msf::Framework#threads}.
21+
#
22+
# (see Msf::ThreadManager#spawn)
23+
def spawn(name, critical, *args, &block)
24+
framework.threads.spawn(name, critical, *args, &block)
25+
end
26+
end

lib/msf/core/framework.rb

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
# -*- coding: binary -*-
2+
3+
#
4+
# Standard Library
5+
#
6+
7+
require 'monitor'
8+
9+
#
10+
# Project
11+
#
12+
213
require 'metasploit/framework/version'
314
require 'msf/core'
415
require 'msf/util'
@@ -12,6 +23,7 @@ module Msf
1223
#
1324
###
1425
class Framework
26+
include MonitorMixin
1527

1628
#
1729
# Versioning information
@@ -66,22 +78,22 @@ module Offspring
6678
#
6779
# Creates an instance of the framework context.
6880
#
69-
def initialize(opts={})
81+
def initialize(options={})
82+
self.options = options
83+
# call super to initialize MonitorMixin. #synchronize won't work without this.
84+
super()
7085

7186
# Allow specific module types to be loaded
72-
types = opts[:module_types] || Msf::MODULE_TYPES
87+
types = options[:module_types] || Msf::MODULE_TYPES
7388

74-
self.threads = ThreadManager.new(self)
7589
self.events = EventDispatcher.new(self)
7690
self.modules = ModuleManager.new(self,types)
77-
self.sessions = SessionManager.new(self)
7891
self.datastore = DataStore.new
7992
self.jobs = Rex::JobContainer.new
8093
self.plugins = PluginManager.new(self)
81-
self.db = DBManager.new(self, opts)
8294

8395
# Configure the thread factory
84-
Rex::ThreadFactory.provider = self.threads
96+
Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: self)
8597

8698
subscriber = FrameworkEventSubscriber.new(self)
8799
events.add_exploit_subscriber(subscriber)
@@ -155,11 +167,6 @@ def version
155167
#
156168
attr_reader :modules
157169
#
158-
# Session manager that tracks sessions associated with this framework
159-
# instance over the course of their lifetime.
160-
#
161-
attr_reader :sessions
162-
#
163170
# The global framework datastore that can be used by modules.
164171
#
165172
attr_reader :datastore
@@ -180,28 +187,62 @@ def version
180187
# unloading of plugins.
181188
#
182189
attr_reader :plugins
183-
#
190+
184191
# The framework instance's db manager. The db manager
185192
# maintains the database db and handles db events
186193
#
187-
attr_reader :db
194+
# @return [Msf::DBManager]
195+
def db
196+
synchronize {
197+
@db ||= Msf::DBManager.new(self, options)
198+
}
199+
end
200+
201+
# Session manager that tracks sessions associated with this framework
202+
# instance over the course of their lifetime.
188203
#
204+
# @return [Msf::SessionManager]
205+
def sessions
206+
synchronize {
207+
@sessions ||= Msf::SessionManager.new(self)
208+
}
209+
end
210+
189211
# The framework instance's thread manager. The thread manager
190212
# provides a cleaner way to manage spawned threads
191213
#
192-
attr_reader :threads
214+
# @return [Msf::ThreadManager]
215+
def threads
216+
synchronize {
217+
@threads ||= Msf::ThreadManager.new(self)
218+
}
219+
end
220+
221+
# Whether {#threads} has been initialized
222+
#
223+
# @return [true] if {#threads} has been initialized
224+
# @return [false] otherwise
225+
def threads?
226+
synchronize {
227+
instance_variable_defined? :@threads
228+
}
229+
end
193230

194231
protected
195232

233+
# @!attribute options
234+
# Options passed to {#initialize}
235+
#
236+
# @return [Hash]
237+
attr_accessor :options
238+
196239
attr_writer :events # :nodoc:
197240
attr_writer :modules # :nodoc:
198-
attr_writer :sessions # :nodoc:
199241
attr_writer :datastore # :nodoc:
200242
attr_writer :auxmgr # :nodoc:
201243
attr_writer :jobs # :nodoc:
202244
attr_writer :plugins # :nodoc:
203245
attr_writer :db # :nodoc:
204-
attr_writer :threads # :nodoc:
205246
end
206247

207248
class FrameworkEventSubscriber

spec/lib/msf/core/framework_spec.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@
44
require 'msf/core/framework'
55

66
describe Msf::Framework do
7-
include_context 'Msf::Framework#threads cleaner'
7+
context '#initialize' do
8+
subject(:framework) {
9+
described_class.new
10+
}
11+
12+
it 'creates no threads' do
13+
expect {
14+
framework
15+
}.not_to change { Thread.list.count }
16+
end
17+
end
818

919
describe "#version" do
1020
CURRENT_VERSION = "4.10.1-dev"

spec/msfcli_spec.rb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ def get_stdout(&block)
143143
#
144144
context "#dump_module_list" do
145145
include_context 'Metasploit::Framework::Spec::Constants cleaner'
146-
include_context 'Msf::Framework#threads cleaner'
147146

148147
let(:framework) {
149148
msfcli.framework
@@ -490,7 +489,6 @@ def get_stdout(&block)
490489

491490
context "#init_modules" do
492491
include_context 'Metasploit::Framework::Spec::Constants cleaner'
493-
include_context 'Msf::Framework#threads cleaner'
494492

495493
let(:args) {
496494
[
@@ -645,7 +643,6 @@ def get_stdout(&block)
645643

646644
context "#engage_mode" do
647645
include_context 'Metasploit::Framework::Spec::Constants cleaner'
648-
include_context 'Msf::Framework#threads cleaner'
649646

650647
subject(:engage_mode) {
651648
msfcli.engage_mode(modules)

spec/support/shared/contexts/msf/framework/threads/cleaner.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
shared_context 'Msf::Framework#threads cleaner' do
2-
after(:each) do
2+
after(:each) do |example|
3+
unless framework.threads?
4+
fail RuntimeError.new(
5+
"framework.threads was never initialized. There are no threads to clean up. " \
6+
"Remove `include_context Msf::Framework#threads cleaner` from context around " \
7+
"'#{example.metadata.full_description}'"
8+
)
9+
end
10+
311
# explicitly kill threads so that they don't exhaust connection pool
412
thread_manager = framework.threads
513

spec/support/shared/contexts/msf/simple/framework.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
require 'metasploit/framework'
44

55
shared_context 'Msf::Simple::Framework' do
6-
include_context 'Msf::Framework#threads cleaner'
7-
86
let(:dummy_pathname) do
97
Rails.root.join('spec', 'dummy')
108
end

0 commit comments

Comments
 (0)