Class: Arachni::Processes::Manager

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/arachni/processes/manager.rb

Overview

Helper for managing processes.

Author:

  • Tasos “Zapotek” Laskos <tasos.laskos@arachni-scanner.com>

Constant Summary collapse

RUNNER =
"#{File.dirname( __FILE__ )}/executables/base.rb"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeManager

Returns a new instance of Manager.



26
27
28
29
# File 'lib/arachni/processes/manager.rb', line 26

def initialize
    @pids           = []
    @discard_output = true
end

Instance Attribute Details

#pidsArray<Integer> (readonly)

Returns PIDs of all running processes.

Returns:

  • (Array<Integer>)

    PIDs of all running processes.



24
25
26
# File 'lib/arachni/processes/manager.rb', line 24

def pids
  @pids
end

Class Method Details

.method_missing(sym, *args, &block) ⇒ Object



224
225
226
227
228
229
230
# File 'lib/arachni/processes/manager.rb', line 224

def self.method_missing( sym, *args, &block )
    if instance.respond_to?( sym )
        instance.send( sym, *args, &block )
    else
        super( sym, *args, &block )
    end
end

.respond_to?(m) ⇒ Boolean

Returns:

  • (Boolean)


232
233
234
# File 'lib/arachni/processes/manager.rb', line 232

def self.respond_to?( m )
    super( m ) || instance.respond_to?( m )
end

Instance Method Details

#<<(pid) ⇒ Integer

Returns `pid`.

Parameters:

  • pid (Integer)

    Adds a PID to the #pids and detaches the process.

Returns:

  • (Integer)

    `pid`



35
36
37
38
39
# File 'lib/arachni/processes/manager.rb', line 35

def <<( pid )
    @pids << pid
    Process.detach pid
    pid
end

#alive?(pid) ⇒ Boolean

Returns `true` if the process is alive, `false` otherwise.

Parameters:

  • pid (Integer)

Returns:

  • (Boolean)

    `true` if the process is alive, `false` otherwise.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/arachni/processes/manager.rb', line 67

def alive?( pid )
    # Windows is not big on POSIX so try it its own way if possible.
    if Arachni.windows?
        begin
            alive = false
            wmi = WIN32OLE.connect( 'winmgmts://' )
            processes = wmi.ExecQuery( "select ProcessId from win32_process where ProcessID='#{pid}'" )
            processes.each do |proc|
                proc.ole_free
                alive = true
            end
            processes.ole_free
            wmi.ole_free

            return alive
        rescue WIN32OLERuntimeError
        end
    end

    !!(Process.kill( 0, pid ) rescue false)
end

#discard_outputObject



117
118
119
# File 'lib/arachni/processes/manager.rb', line 117

def discard_output
    @discard_output = true
end

#discard_output?Boolean

Returns:

  • (Boolean)


121
122
123
# File 'lib/arachni/processes/manager.rb', line 121

def discard_output?
    @discard_output
end

#kill(pid) ⇒ Object

Parameters:

  • pid (Integer)

    PID of the process to kill.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/arachni/processes/manager.rb', line 43

def kill( pid )
    Timeout.timeout 10 do
        while sleep 0.1 do
            begin
                Process.kill( Arachni.windows? ? 'KILL' : 'TERM', pid )

            # Either kill was successful or we don't have enough perms or
            # we hit a reused PID for someone else's process, either way,
            # consider the process gone.
            rescue Errno::ESRCH, Errno::EPERM,
                # Don't kill ourselves.
                SignalException

                @pids.delete pid
                return
            end
        end
    end
rescue Timeout::Error
end

#kill_many(pids) ⇒ Object

Parameters:

  • pids (Array<Integer>)

    PIDs of the process to #kill.



91
92
93
# File 'lib/arachni/processes/manager.rb', line 91

def kill_many( pids )
    pids.each { |pid| kill pid }
end

#kill_reactorObject

Stops the Reactor.



102
103
104
105
106
# File 'lib/arachni/processes/manager.rb', line 102

def kill_reactor
    Reactor.stop
rescue
    nil
end

#killallObject

Kills all processes.



96
97
98
99
# File 'lib/arachni/processes/manager.rb', line 96

def killall
    kill_many @pids.dup
    @pids.clear
end

#preserve_outputObject

Overrides the default setting of discarding process outputs.



109
110
111
# File 'lib/arachni/processes/manager.rb', line 109

def preserve_output
    @discard_output = false
end

#preserve_output?Boolean

Returns:

  • (Boolean)


113
114
115
# File 'lib/arachni/processes/manager.rb', line 113

def preserve_output?
    !discard_output?
end

#spawn(executable, options = {}) ⇒ Integer

Returns PID of the process.

Parameters:

  • executable (String)

    Name of the executable Ruby script found in OptionGroups::Paths#executables without the '.rb' extension.

  • options (Hash) (defaults to: {})

    Options to pass to the script – can be retrieved from `$options`.

Returns:

  • (Integer)

    PID of the process.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/arachni/processes/manager.rb', line 133

def spawn( executable, options = {} )
    fork = options.delete(:fork)
    fork = false if fork.nil?

    stdin      = options.delete(:stdin)
    stdout     = options.delete(:stdout)
    stderr     = options.delete(:stderr)
    new_pgroup = options.delete(:new_pgroup)

    spawn_options = {}

    if new_pgroup
        if Arachni.windows?
            spawn_options[:new_pgroup] = new_pgroup
        else
            spawn_options[:pgroup] = new_pgroup
        end
    end

    spawn_options[:in]     = stdin  if stdin
    spawn_options[:out]    = stdout if stdout
    spawn_options[:err]    = stderr if stderr

    options[:ppid]  = Process.pid

    arachni_options = Options.to_h.merge( options.delete(:options) || {} )
    # Paths are not included in RPC nor Hash representations as they're
    # considered local, in this case though they're necessary to provide
    # the same environment the processes.
    arachni_options[:paths] = Options.paths.to_h
    encoded_arachni_options = Base64.strict_encode64( Marshal.dump( arachni_options ) )

    executable      = "#{Options.paths.executables}/#{executable}.rb"
    encoded_options = Base64.strict_encode64( Marshal.dump( options ) )
    argv            = [executable, encoded_options]

    # Process.fork is faster, less stressful to the CPU and lets the parent
    # and child share the same RAM due to copy-on-write support on Ruby 2.0.0.
    # It is, however, not available when running on Windows nor JRuby so
    # have a fallback ready.
    if fork && Process.respond_to?( :fork )
        pid = Process.fork do
            $stdin = spawn_options[:in] if spawn_options[:in]

            if spawn_options[:out]
                $stdout = spawn_options[:out]
            elsif discard_output?
                $stdout.reopen( Arachni.null_device, 'w' )
            end

            if spawn_options[:err]
                $stderr = spawn_options[:err]
            elsif discard_output?
                $stderr.reopen( Arachni.null_device, 'w' )
            end

            # Careful, Framework.reset will remove objects from Data
            # structures which off-load to disk, those files however belong
            # to our parent and should not be touched, thus, we remove
            # any references to them.
            Data.framework.page_queue.disk.clear
            Data.framework.url_queue.disk.clear
            Data.framework.rpc.distributed_page_queue.disk.clear

            # Provide a clean slate.
            Framework.reset
            Reactor.stop

            ENV['arachni_options'] = encoded_arachni_options

            ARGV.replace( argv )
            load RUNNER
        end
    else
        # It's very, **VERY** important that we use this argument format as
        # it bypasses the OS shell and we can thus count on a 1-to-1 process
        # creation and that the PID we get will be for the actual process.
        pid = Process.spawn(
            {
                'arachni_options' => encoded_arachni_options
            },
            RbConfig.ruby,
            RUNNER,
            *(argv + [spawn_options])
        )
    end

    self << pid
    pid
end