I'm struggling a lot with multiprocessing/threading/subprocessing. What I'm basically trying to do is to execute every single binary available on my computer, I wrote a python script to do so. But I keep having zombie processes ("defunct"), which end up in a deadlock if all 4 of my workers are in this state. I tried lots of different things, but nothing seems to do it :(
Here's what the architecture looks like :
| \_ python -m dataset --generate
| \_ worker1
| | \_ [thread1] firejail bin1
| \_ worker2
| | \_ [thread1] firejail bin1
| | \_ [thread2] firejail bin2
| | \_ [thread3] firejail bin3
| \_ worker3
| | \_ [thread1] [firejail] <defunct>
| \_ worker4
| | \_ [thread1] [firejail] <defunct>
There are 4 workers that I create as such :
# spawn mode prevents deadlocks https://codewithoutrules.com/2018/09/04/python-multiprocessing/
with get_context("spawn").Pool() as pool:
results = []
for binary in binaries:
result = pool.apply_async(legit.analyse, args=(binary,),
callback=_binary_analysis_finished_callback,
error_callback=error_callback)
results.append(result)
(Note I use a "spawn" pool, but now I'm wondering if it's of any use...)
Each worker will create multiple threads like this :
threads = []
executions = []
def thread_wrapper(*args):
flows, output, returncode = _exec_using_firejail(*args)
executions.append(Execution(*args, flows, is_malware=False))
for command_line in potentially_working_command_lines:
thread = Thread(target=thread_wrapper, args=(command_line,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
And each thread will start a new process in the firejail sandbox :
process = subprocess.Popen(FIREJAIL_COMMAND +
["strace", "-o", output_filename, "-ff", "-xx", "-qq", "-s", "1000"] + command_line,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid)
try:
out, errs = process.communicate(timeout=5, input=b"Y\nY\nY\nY\nY\nY\nY\nY\nY\nY\nY\nY\nY\nY\nY\nY\n")
# print("stdout:", out)
# print("stderr:", errs)
except subprocess.TimeoutExpired:
# print(command_line, "timed out")
os.killpg(os.getpgid(process.pid), signal.SIGKILL)
out, errs = process.communicate()
I use os.killpg() and not process.kill() because for some reasons subprocesses of my Popen process are not killed...
This is possible thanks to preexec_fn=os.setsid which sets the gid of all descendants. But even with this method, some processes such as zsh will provoke a zombie process because it looks like zsh changes its gid and so my os.killpg doesn't work as expected...
I'm looking for a way to be a 100% percent sure all processes will be dead.
If you want to use the
subprocessmodule for this, you should use the.killmethod of theprocessobject directly instead of using theosmodule. Usingcommunicateis a blocking action; so Python will wait until for a response. Using thetimeoutparameter helps, but will be slow for lots of processes.If you want to run it in a non-blocking loop over a set of processes, that is when you use
poll. Here is an example. This assumes you have a list offilenamesand correspondingcommand_linesthat you want to feed to the process creation.