Land #13019, Revert "Land #12960, add ttl to job results instantiated from an RPC request"

This commit is contained in:
Pearce Barry 2020-03-02 16:40:34 -06:00
commit 98a6147403
No known key found for this signature in database
GPG Key ID: DFFA791397420E6C
9 changed files with 112 additions and 288 deletions

View File

@ -69,7 +69,7 @@ module Auxiliary
end
run_uuid = Rex::Text.rand_text_alphanumeric(24)
mod.framework.job_state_tracker.waiting run_uuid
mod.framework.ready << run_uuid
ctx = [mod, run_uuid]
if(mod.passive? or opts['RunAsJob'])
mod.job_id = mod.framework.jobs.start_bg_job(
@ -122,7 +122,7 @@ module Auxiliary
run_uuid = Rex::Text.rand_text_alphanumeric(24)
mod.framework.job_state_tracker.waiting run_uuid
mod.framework.ready << run_uuid
ctx = [mod, run_uuid]
if opts['RunAsJob']
@ -169,12 +169,15 @@ protected
mod.setup
mod.framework.events.on_module_run(mod)
begin
mod.framework.job_state_tracker.start run_uuid
mod.framework.running << run_uuid
mod.framework.ready.delete run_uuid
result = block.call(mod)
mod.framework.job_state_tracker.completed(run_uuid, result)
mod.framework.results[run_uuid] = {result: result}
rescue ::Exception => e
mod.framework.job_state_tracker.failed(run_uuid, e)
mod.framework.results[run_uuid] = {error: e.to_s}
raise
ensure
mod.framework.running.delete run_uuid
end
rescue Msf::Auxiliary::Complete
mod.cleanup
@ -191,7 +194,7 @@ protected
return
rescue ::Interrupt => e
mod.error = e
mod.print_error("Stopping running against current target...")
mod.print_error("Stopping running againest current target...")
mod.cleanup
mod.print_status("Control-C again to force quit all targets.")
begin

View File

@ -190,7 +190,7 @@ module Exploit
mod.validate
run_uuid = Rex::Text.rand_text_alphanumeric(24)
mod.framework.job_state_tracker.waiting run_uuid
mod.framework.ready << run_uuid
ctx = [mod, run_uuid]
if opts['RunAsJob']
@ -220,12 +220,15 @@ module Exploit
run_uuid = ctx[1]
begin
mod.setup
mod.framework.job_state_tracker.start run_uuid
mod.framework.running << run_uuid
mod.framework.ready.delete run_uuid
result = mod.has_check? ? mod.check : Msf::Exploit::CheckCode::Unsupported
mod.framework.job_state_tracker.completed(run_uuid, result)
mod.framework.results[run_uuid] = {result: result}
rescue => e
mod.framework.job_state_tracker.failed(run_uuid, e)
mod.framework.results[run_uuid] = {error: e.to_s}
mod.handle_exception e
ensure
mod.framework.running.delete run_uuid
end
return result

View File

@ -1,7 +1,6 @@
# -*- coding: binary -*-
require 'msf/base/simple'
require 'msf/base/simple/framework/module_paths'
require 'msf/base/simple/job_state_tracker'
module Msf
module Simple
@ -151,7 +150,9 @@ module Framework
#
def init_simplified
self.stats = Statistics.new(self)
self.job_state_tracker = JobStateTracker.new
self.ready = Set.new
self.running = Set.new
self.results = Hash.new
end
#
@ -173,12 +174,6 @@ module Framework
#
attr_reader :stats
#
# JobStateTracker
#
attr_reader :job_state_tracker
#
# Boolean indicating whether the cache is initialized yet
#
@ -188,14 +183,32 @@ module Framework
# Thread of the running rebuild operation
#
attr_reader :cache_thread
#
# {Set<String>} of module run/check UUIDs waiting to be kicked off
#
attr_reader :ready
#
# {Hash<String,Hash>} of module run/check results, by UUID. Successful runs
# look like `{result: check_code}` and errors like `{error: message}`.
#
attr_reader :results
#
# {Set<String>} of module run/check UUIDs currently in progress
#
attr_reader :running
attr_writer :cache_initialized # :nodoc:
attr_writer :cache_thread # :nodoc:
protected
attr_writer :ready # :nodoc:
attr_writer :results # :nodoc:
attr_writer :running # :nodoc:
attr_writer :stats # :nodoc:
attr_writer :job_state_tracker # :nodoc:
end

View File

@ -1,84 +0,0 @@
require 'monitor'
class JobStateTracker
include MonitorMixin
def initialize(result_ttl=nil)
self.ready = Set.new
self.running = Set.new
# Can be expanded upon later to allow the option of a MemCacheStore being backed by redis for example
self.results = ResultsMemoryStore.new(expires_in: result_ttl || 5.minutes)
end
def waiting(id)
ready << id
end
def start(id)
running << id
ready.delete(id)
end
def completed(id, result, ttl=nil)
begin
# ttl of nil means it will take the default expiry time
results.write(id, {result: result}, ttl)
ensure
running.delete(id)
end
end
def failed(id, error, ttl=nil)
begin
# ttl of nil means it will take the default expiry time
results.write(id, {error: error.to_s}, ttl)
ensure
running.delete(id)
end
end
def running?(id)
running.include? id
end
def waiting?(id)
ready.include? id
end
def finished?(id)
results.exist? id
end
def result(id)
results.fetch(id)
end
def delete(id)
results.delete(id)
end
def results_size
results.size
end
def waiting_size
ready.size
end
def running_size
running.size
end
alias :ack :delete
private
attr_accessor :ready, :running, :results
class ResultsMemoryStore < ActiveSupport::Cache::MemoryStore
def size
@data.size
end
end
end

View File

@ -9,10 +9,10 @@ class RPC_Base
#
# return [void]
def initialize(service)
self.service = service
self.framework = service.framework
self.tokens = service.tokens
self.users = service.users
self.service = service
self.framework = service.framework
self.tokens = service.tokens
self.users = service.users
end
# Raises an Msf::RPC Exception.

View File

@ -412,9 +412,9 @@ class RPC_Module < RPC_Base
# rpc.call('module.running_stats')
def rpc_running_stats
{
"waiting" => self.framework.job_state_tracker.waiting_size,
"running" => self.framework.job_state_tracker.running_size,
"results" => self.framework.job_state_tracker.results_size,
"ready" => self.framework.ready.size,
"running" => self.framework.running.size,
"results" => self.framework.results.size,
}
end
@ -523,7 +523,7 @@ class RPC_Module < RPC_Base
# TODO: expand these to take a list of UUIDs or stream with event data if
# required for performance
def rpc_results(uuid)
if (r = self.framework.job_state_tracker.result(uuid))
if r = self.framework.results[uuid]
if r[:error]
{"status" => "errored", "error" => r[:error]}
else
@ -537,9 +537,9 @@ class RPC_Module < RPC_Base
{"status" => "completed", "result" => r[:result]}
end
end
elsif self.framework.job_state_tracker.running? uuid
elsif self.framework.running.include? uuid
{"status" => "running"}
elsif self.framework.job_state_tracker.waiting uuid
elsif self.framework.ready.include? uuid
{"status" => "ready"}
else
error(404, "Results not found for module instance #{uuid}")
@ -547,7 +547,7 @@ class RPC_Module < RPC_Base
end
def rpc_ack(uuid)
{"success" => !!self.framework.job_state_tracker.ack(uuid)}
{"success" => !!self.framework.results.delete(uuid)}
end
# Returns a list of executable format names.
@ -740,7 +740,7 @@ private
end
def _run_auxiliary(mod, opts)
uuid, job = Msf::Simple::Auxiliary.run_simple(mod,{
uuid, job = Msf::Simple::Auxiliary.run_simple(mod, {
'Action' => opts['ACTION'],
'RunAsJob' => true,
'Options' => opts
@ -752,7 +752,7 @@ private
end
def _check_exploit(mod, opts)
uuid, job = Msf::Simple::Exploit.check_simple(mod,{
uuid, job = Msf::Simple::Exploit.check_simple(mod, {
'RunAsJob' => true,
'Options' => opts
})
@ -763,7 +763,7 @@ private
end
def _check_auxiliary(mod, opts)
uuid, job = Msf::Simple::Auxiliary.check_simple(mod,{
uuid, job = Msf::Simple::Auxiliary.check_simple(mod, {
'Action' => opts['ACTION'],
'RunAsJob' => true,
'Options' => opts

View File

@ -15,6 +15,7 @@ require 'msf/core/rpc/v10/rpc_plugin'
require 'msf/core/rpc/v10/rpc_job'
require 'msf/core/rpc/v10/rpc_db'
module Msf
module RPC

View File

@ -0,0 +1,59 @@
require 'spec_helper'
RSpec.describe Msf::Simple::Framework do
include_context 'Msf::Simple::Framework'
subject do
framework
end
it_should_behave_like 'Msf::Simple::Framework::ModulePaths'
describe "#ready" do
let(:run_uuid) { Rex::Text.rand_text_alphanumeric 24 }
it "should start out empty" do
expect(subject.ready).to_not include, run_uuid
end
it "should remember things that are ready to run" do
subject.ready << run_uuid
expect(subject.ready).to include, run_uuid
end
it "should forget things that are running" do
subject.ready << run_uuid
subject.ready.delete run_uuid
expect(subject.ready).to_not include, run_uuid
end
end
describe "#running" do
let(:run_uuid) { Rex::Text.rand_text_alphanumeric 24 }
it "should start out empty" do
expect(subject.running).to_not include, run_uuid
end
it "should remember things that are running" do
subject.running << run_uuid
expect(subject.running).to include, run_uuid
end
it "should forget things that are done" do
subject.running << run_uuid
subject.running.delete run_uuid
expect(subject.running).to_not include, run_uuid
end
end
describe "#results" do
let(:run_uuid) { Rex::Text.rand_text_alphanumeric 24 }
it "should start out empty" do
expect(subject.results.keys).to_not include, run_uuid
end
it "should remember results" do
subject.results[run_uuid] = {}
expect(subject.results.keys).to include, run_uuid
end
it "should forget things that have been acknowleged" do
subject.results[run_uuid] = {}
subject.results.delete run_uuid
expect(subject.results.keys).to_not include, run_uuid
end
end
end

View File

@ -1,171 +0,0 @@
require 'spec_helper'
require 'msf/base/simple/job_state_tracker.rb'
RSpec.describe JobStateTracker do
context "With a 1 second ttl " do
let(:job_state_tracker) { described_class.new(result_ttl=1) }
let(:job_id) { "super_random_job_id" }
before(:each) do
allow(ActiveSupport::Cache::MemoryStore).to receive(:new).and_call_original
end
context "A job has completed" do
before(:each) do
generic_result = "This is a job that has finished"
job_state_tracker.completed(job_id, generic_result)
end
it 'should show as finished' do
expect(job_state_tracker).to be_finished(job_id)
end
it { expect(ActiveSupport::Cache::MemoryStore).to have_received(:new).with(expires_in: 1) }
end
end
context "With default options" do
let(:job_state_tracker) { described_class.new }
let(:job_id) { "super_random_job_id" }
let(:good_result) { 'yay_success' }
let(:bad_result) { 'boo_fail' }
context 'A job is waiting' do
before(:each) do
job_id = "super_random_job_id"
job_state_tracker.waiting(job_id)
end
it 'should show as waiting' do
expect(job_state_tracker).to be_waiting(job_id)
expect(job_state_tracker.waiting_size).to be(1)
end
it 'should not show as running' do
expect(job_state_tracker).not_to be_running(job_id)
expect(job_state_tracker.running_size).to be(0)
end
it 'should not show as finished' do
expect(job_state_tracker).not_to be_finished(job_id)
expect(job_state_tracker.results_size).to be(0)
end
context "The job is started" do
before(:each) do
job_state_tracker.start(job_id)
end
it 'should no longer show as waiting' do
expect(job_state_tracker).not_to be_waiting(job_id)
expect(job_state_tracker.waiting_size).to be(0)
end
it 'should now show as running' do
expect(job_state_tracker).to be_running(job_id)
expect(job_state_tracker.running_size).to be(1)
end
it 'should not show as finished' do
expect(job_state_tracker).not_to be_finished(job_id)
expect(job_state_tracker.results_size).to be(0)
end
context "The job completes successfully" do
before(:each) do
job_state_tracker.completed(job_id, good_result)
end
it 'should not show as waiting' do
expect(job_state_tracker).not_to be_waiting(job_id)
expect(job_state_tracker.waiting_size).to be(0)
end
it 'should no longer show as running' do
expect(job_state_tracker).not_to be_running(job_id)
expect(job_state_tracker.running_size).to be(0)
end
it 'should show as finished' do
expect(job_state_tracker).to be_finished(job_id)
expect(job_state_tracker.results_size).to be(1)
end
it 'should have a retrievable result' do
expect(job_state_tracker.result job_id).to eq({result: good_result})
end
context "The job is acknowledged" do
before(:each) do
job_state_tracker.ack(job_id)
end
it 'should not show as waiting' do
expect(job_state_tracker).not_to be_waiting(job_id)
expect(job_state_tracker.waiting_size).to be(0)
end
it 'should not show as running' do
expect(job_state_tracker).not_to be_running(job_id)
expect(job_state_tracker.running_size).to be(0)
end
it 'should no longer show as finished' do
expect(job_state_tracker).not_to be_finished(job_id)
expect(job_state_tracker.results_size).to be(0)
end
end
end
context "The job fails" do
before(:each) do
job_state_tracker.failed(job_id, bad_result)
end
it 'should not show as waiting' do
expect(job_state_tracker).not_to be_waiting(job_id)
expect(job_state_tracker.waiting_size).to be(0)
end
it 'should no longer show as running' do
expect(job_state_tracker).not_to be_running(job_id)
expect(job_state_tracker.running_size).to be(0)
end
it 'should show as finished' do
expect(job_state_tracker).to be_finished(job_id)
expect(job_state_tracker.results_size).to be(1)
end
it 'should have a retrievable result' do
expect(job_state_tracker.result job_id).to eq({error: bad_result})
end
context "The job is acknowledged" do
before(:each) do
job_state_tracker.ack(job_id)
end
it 'should not show as waiting' do
expect(job_state_tracker).not_to be_waiting(job_id)
expect(job_state_tracker.waiting_size).to be(0)
end
it 'should not show as running' do
expect(job_state_tracker).not_to be_running(job_id)
expect(job_state_tracker.running_size).to be(0)
end
it 'should no longer show as finished' do
expect(job_state_tracker).not_to be_finished(job_id)
expect(job_state_tracker.results_size).to be(0)
end
end
end
end
end
end
end