Skip to content

Commit 2ed8b93

Browse files
Land rapid7#18370, Fix msfrpc hanging when updating saved command history
2 parents 1378bfb + a60e048 commit 2ed8b93

File tree

7 files changed

+49
-52
lines changed

7 files changed

+49
-52
lines changed

lib/metasploit/framework/spec/threads/suite.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def self.configure!
9696

9797
thread_list.each do |thread|
9898
thread_uuid = thread[Metasploit::Framework::Spec::Threads::Suite::UUID_THREAD_LOCAL_VARIABLE]
99+
thread_name = thread[:tm_name]
99100

100101
# unmanaged thread, such as the main VM thread
101102
unless thread_uuid
@@ -104,10 +105,10 @@ def self.configure!
104105

105106
caller = caller_by_thread_uuid[thread_uuid]
106107

107-
error_lines << "Thread #{thread_uuid}'s status is #{thread.status.inspect} " \
108+
error_lines << "Thread #{thread_uuid}'s (name=#{thread_name} status is #{thread.status.inspect} " \
108109
"and was started here:\n"
109-
110110
error_lines.concat(caller)
111+
error_lines << "The thread backtrace was:\n#{thread.backtrace ? thread.backtrace.join("\n") : 'nil (no backtrace)'}\n"
111112
end
112113
else
113114
error_lines << "Run `rake spec` to log where Thread.new is called."

lib/msf/base/sessions/command_shell.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ def cmd_irb(*args)
546546
if expressions.empty?
547547
print_status('Starting IRB shell...')
548548
print_status("You are in the \"self\" (session) object\n")
549-
Rex::Ui::Text::Shell::HistoryManager.instance.with_context(name: :irb) do
549+
framework.history_manager.with_context(name: :irb) do
550550
Rex::Ui::Text::IrbShell.new(self).run
551551
end
552552
else
@@ -585,7 +585,7 @@ def cmd_pry(*args)
585585
print_status('Starting Pry shell...')
586586
print_status("You are in the \"self\" (session) object\n")
587587
Pry.config.history_load = false
588-
Rex::Ui::Text::Shell::HistoryManager.instance.with_context(history_file: Msf::Config.pry_history, name: :pry) do
588+
framework.history_manager.with_context(history_file: Msf::Config.pry_history, name: :pry) do
589589
self.pry
590590
end
591591
end
@@ -746,7 +746,7 @@ def process_autoruns(datastore)
746746
# shell_write instead of operating on rstream directly.
747747
def _interact
748748
framework.events.on_session_interact(self)
749-
Rex::Ui::Text::Shell::HistoryManager.instance.with_context(name: self.type.to_sym) {
749+
framework.history_manager.with_context(name: self.type.to_sym) {
750750
_interact_stream
751751
}
752752
end

lib/msf/core/framework.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ def initialize(options={})
6262
# Allow specific module types to be loaded
6363
types = options[:module_types] || Msf::MODULE_TYPES
6464

65+
self.history_manager = Rex::Ui::Text::Shell::HistoryManager.new
66+
6567
self.features = FeatureManager.instance
6668
self.features.load_config
6769

@@ -190,9 +192,13 @@ def version
190192
#
191193
# The framework instance's feature manager. The feature manager is responsible
192194
# for configuring feature flags that can change characteristics of framework.
193-
#
195+
# @return [Msf::FeatureManager]
194196
attr_reader :features
195197

198+
# The framework instance's history manager, responsible for managing command history
199+
# in different contexts
200+
# @return [Rex::Ui::Text::Shell::HistoryManager]
201+
attr_reader :history_manager
196202

197203
#
198204
# The framework instance's data service proxy
@@ -281,7 +287,8 @@ def eicar_corrupted?
281287
attr_writer :db # :nodoc:
282288
attr_writer :browser_profiles # :nodoc:
283289
attr_writer :analyze # :nodoc:
284-
attr_writer :features # :nodoc:
290+
attr_writer :features # :nodoc:
291+
attr_writer :history_manager # :nodoc:
285292

286293
private
287294

lib/msf/ui/console/command_dispatcher/developer.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def cmd_irb(*args)
129129
if expressions.empty?
130130
print_status('Starting IRB shell...')
131131

132-
Rex::Ui::Text::Shell::HistoryManager.instance.with_context(name: :irb) do
132+
framework.history_manager.with_context(name: :irb) do
133133
begin
134134
if active_module
135135
print_status("You are in #{active_module.fullname}\n")
@@ -192,7 +192,7 @@ def cmd_pry(*args)
192192
print_status('Starting Pry shell...')
193193

194194
Pry.config.history_load = false
195-
Rex::Ui::Text::Shell::HistoryManager.instance.with_context(history_file: Msf::Config.pry_history, name: :pry) do
195+
framework.history_manager.with_context(history_file: Msf::Config.pry_history, name: :pry) do
196196
if active_module
197197
print_status("You are in the \"#{active_module.fullname}\" module object\n")
198198
active_module.pry
@@ -420,7 +420,7 @@ def cmd__historymanager(*args)
420420
end
421421

422422
if opts.key?(:debug)
423-
Rex::Ui::Text::Shell::HistoryManager.instance._debug = opts[:debug]
423+
framework.history_manager._debug = opts[:debug]
424424
print_status("HistoryManager debugging is now #{opts[:debug] ? 'on' : 'off'}")
425425
end
426426

@@ -430,7 +430,7 @@ def cmd__historymanager(*args)
430430
'Indent' => 1,
431431
'Columns' => ['Id', 'File', 'Name']
432432
)
433-
Rex::Ui::Text::Shell::HistoryManager.instance._contexts.each.with_index do |context, id|
433+
framework.history_manager._contexts.each.with_index do |context, id|
434434
table << [id, context[:history_file], context[:name]]
435435
end
436436

lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ def cmd_irb(*args)
599599
if expressions.empty?
600600
print_status('Starting IRB shell...')
601601
print_status("You are in the \"client\" (session) object\n")
602-
Rex::Ui::Text::Shell::HistoryManager.instance.with_context(name: :irb) do
602+
framework.history_manager.with_context(name: :irb) do
603603
Rex::Ui::Text::IrbShell.new(client).run
604604
end
605605
else
@@ -639,7 +639,7 @@ def cmd_pry(*args)
639639
print_status("You are in the \"client\" (session) object\n")
640640

641641
Pry.config.history_load = false
642-
Rex::Ui::Text::Shell::HistoryManager.instance.with_context(history_file: Msf::Config.pry_history, name: :pry) do
642+
client.framework.history_manager.with_context(history_file: Msf::Config.pry_history, name: :pry) do
643643
client.pry
644644
end
645645
end

lib/rex/ui/text/shell.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def run(&block)
130130
# Pry is a development dependency, if not available suppressing history_load can be safely ignored.
131131
end
132132

133-
HistoryManager.instance.with_context(history_file: histfile, name: name) do
133+
framework.history_manager.with_context(history_file: histfile, name: name) do
134134
self.hist_last_saved = Readline::HISTORY.length
135135

136136
begin
@@ -177,7 +177,7 @@ def run(&block)
177177
end
178178
end
179179
ensure
180-
HistoryManager.instance.flush
180+
framework.history_manager.flush
181181
self.hist_last_saved = Readline::HISTORY.length
182182
end
183183

lib/rex/ui/text/shell/history_manager.rb

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@ module Shell
99

1010
class HistoryManager
1111

12-
include Singleton
13-
1412
MAX_HISTORY = 2000
1513

1614
def initialize
1715
@contexts = []
18-
@write_mutex = Mutex.new
19-
@write_queue = {}
2016
@debug = false
17+
@write_queue = ::Queue.new
18+
@currently_processing = ::Queue.new
2119
end
2220

2321
# Create a new history command context when executing the given block
@@ -40,7 +38,9 @@ def with_context(history_file: nil, name: nil, &block)
4038

4139
# Flush the contents of the write queue to disk. Blocks synchronously.
4240
def flush
43-
sleep 0.1 until @write_queue.empty?
41+
until @write_queue.empty? && @currently_processing.empty?
42+
sleep 0.1
43+
end
4444

4545
nil
4646
end
@@ -99,16 +99,9 @@ def load_history_file(history_file)
9999
return unless readline_available?
100100

101101
clear_readline
102-
commands = from_storage_queue(history_file)
103-
if commands
104-
commands.reverse.each do |c|
105-
::Readline::HISTORY << c
106-
end
107-
else
108-
if File.exist?(history_file)
109-
File.readlines(history_file).each do |e|
110-
::Readline::HISTORY << e.chomp
111-
end
102+
if File.exist?(history_file)
103+
File.readlines(history_file).each do |e|
104+
::Readline::HISTORY << e.chomp
112105
end
113106
end
114107
end
@@ -140,32 +133,28 @@ def switch_context(new_context, old_context=nil)
140133
end
141134

142135
def write_history_file(history_file, cmds)
143-
entry_added = false
144-
until entry_added
145-
@write_mutex.synchronize do
146-
if @write_queue[history_file].nil?
147-
@write_queue[history_file] = cmds
148-
entry_added = true
136+
write_queue_ref = @write_queue
137+
currently_processing_ref = @currently_processing
138+
@write_thread ||= Rex::ThreadFactory.spawn("HistoryManagerWriter", false) do
139+
while (event = write_queue_ref.pop)
140+
begin
141+
currently_processing_ref << event
142+
143+
history_file = event[:history_file]
144+
cmds = event[:cmds]
145+
146+
File.open(history_file, 'wb+') do |f|
147+
f.puts(cmds.reverse)
148+
end
149+
rescue => e
150+
elog(e)
151+
ensure
152+
currently_processing_ref.pop
149153
end
150154
end
151-
sleep 0.1 if !entry_added
152-
end
153-
154-
Rex::ThreadFactory.spawn("#{history_file} Writer", false) do
155-
File.open(history_file, 'wb+') do |f|
156-
f.puts(cmds.reverse)
157-
end
158-
159-
@write_mutex.synchronize do
160-
@write_queue.delete(history_file)
161-
end
162155
end
163-
end
164156

165-
def from_storage_queue(history_file)
166-
@write_mutex.synchronize do
167-
@write_queue[history_file]
168-
end
157+
write_queue_ref << { type: :write, history_file: history_file, cmds: cmds }
169158
end
170159
end
171160

0 commit comments

Comments
 (0)