Killing sudo-started subprocess in python
I think I figured it out, the issue was that if I did this
import subprocess, os
pr = subprocess.Popen(["sudo", "sleep", "100"])
print("Process spawned with PID: %s" % pr.pid)
pgid = os.getpgid(pr.pid)
subprocess.check_output("sudo kill {}".format(pgid))
it would kill the process that started the python interpreter
>>> Terminated
so instead, I set the preexec_fn
to os.setpgrp
import subprocess, os
pr = subprocess.Popen(["sudo", "sleep", "100"], preexec_fn=os.setpgrp)
print("Process spawned with PID: %s" % pr.pid)
pgid = os.getpgid(pr.pid)
subprocess.check_output("sudo kill {}".format(pgid))
in another shell, if I check
pgrep sleep
nothing shows up, so it is actually killed.
subprocces.Popen, kill process started with sudo
Okay, i can answer my own question here (which i found on https://izziswift.com/how-to-terminate-a-python-subprocess-launched-with-shelltrue/). The trick was to open the process with:
subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)
and then kill it:
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
This time i use a shell to open and use the os to kill all the processes in the process group.
Can't terminate a sudo process created with python, in Ubuntu 15.10
TL;DR: sudo
does not forward signals sent by a process in the command's process group since 28 May 2014 commit released in sudo 1.8.11
-- the python process (sudo's parent) and the tcpdump process (grandchild) are in the same process group by default and therefore sudo
does not forward SIGTERM
signal sent by .terminate()
to the tcpdump
process.
It shows the same behaviour when running that code while being the root user and while being a regular user + sudo
Running as a regular user raises OSError: [Errno 1] Operation not permitted
exception on .terminate()
(as expected).
Running as root
reproduces the issue: sudo
and tcpdump
processes are not killed on .terminate()
and the code is stuck on .communicate()
on Ubuntu 15.10.
The same code kills both processes on Ubuntu 12.04.
tcpdump_process
name is misleading because the variable refers to the sudo
process (the child process), not tcpdump
(grandchild):
python
└─ sudo tcpdump -w example.pcap -i eth0 -n icmp
└─ tcpdump -w example.pcap -i eth0 -n icmp
As @Mr.E pointed out in the comments, you don't need sudo
here: you're root already (though you shouldn't be -- you can sniff the network without root). If you drop sudo
; .terminate()
works.
In general, .terminate()
does not kill the whole process tree recursively and therefore it is expected that a grandchild process survives. Though sudo
is a special case, from sudo(8) man page:
When the command is run as a child of the
sudo
process,sudo
will
relay signals it receives to the command.emphasis is mine
i.e., sudo
should relay SIGTERM
to tcpdump
and tcpdump
should stop capturing packets on SIGTERM
, from tcpdump(8) man page:
Tcpdump will, ..., continue capturing packets until it is
interrupted by a SIGINT signal (generated, for example, by typing your
interrupt character, typically control-C) or a SIGTERM signal
(typically generated with the kill(1) command);
i.e., the expected behavior is: tcpdump_process.terminate()
sends SIGTERM to sudo
which relays the signal to tcpdump
which should stop capturing and both processes exit and .communicate()
returns tcpdump
's stderr output to the python script.
Note: in principle the command may be run without creating a child process, from the same sudo(8) man page:
As a special case, if the policy plugin does not define a close
function and no pty is required,sudo
will execute the command
directly instead of calling fork(2) first
and therefore .terminate()
may send SIGTERM to the tcpdump
process directly -- though it is not the explanation: sudo tcpdump
creates two processes on both Ubuntu 12.04 and 15.10 in my tests.
If I run sudo tcpdump -w example.pcap -i eth0 -n icmp
in the shell then kill -SIGTERM
terminates both processes. It does not look like Python issue (Python 2.7.3 (used on Ubuntu 12.04) behaves the same on Ubuntu 15.10. Python 3 also fails here).
It is related to process groups (job control): passing preexec_fn=os.setpgrp
to subprocess.Popen()
so that sudo
will be in a new process group (job) where it is the leader as in the shell makes tcpdump_process.terminate()
work in this case.
What happened? It works on previous versions.
The explanation is in the sudo's source code:
Do not forward signals sent by a process in the command's process
group, do not forward it as we don't want the child to indirectly kill
itself. For example, this can happen with some versions of reboot
that call kill(-1, SIGTERM) to kill all other processes.emphasis is mine
preexec_fn=os.setpgrp
changes sudo
's process group. sudo
's descendants such as tcpdump
process inherit the group. python
and tcpdump
are no longer in the same process group and therefore the signal sent by .terminate()
is relayed by sudo
to tcpdump
and it exits.
Ubuntu 15.04 uses Sudo version 1.8.9p5
where the code from the question works as is.
Ubuntu 15.10 uses Sudo version 1.8.12
that contains the commit.
sudo(8) man page in wily (15.10) still talks only about the child process itself -- no mention of the process group:
As a special case, sudo will not relay signals that were sent by the
command it is running.
It should be instead:
As a special case, sudo will not relay signals that were sent by a process in the process group of the command it is running.
You could open a documentation issue on Ubuntu's bug tracker and/or on the upstream bug tracker.
Related Topics
Where Does Python Tempfile Writes Its Files
How to Benchmark Part of Tensorflow Graph
Trying to Import Pypyodbc Module Gives Error 'Odbc Library Is Not Found. Is Ld_Library_Path Set'
Get File Creation Time with Python on Linux
Cannot Get Environment Variables in Django Settings File
List Nearby/Discoverable Bluetooth Devices, Including Already Paired, in Python, on Linux
Pip Error:'Module' Object Has No Attribute 'Cryptography_Has_Ssl_St'
Can You Fool Isatty and Log Stdout and Stderr Separately
/Usr/Bin/Ld: Cannot Find -Lpython2.7
Anaconda: Disable Prompt Change
Multiprocessing Module Showing Memory for Each Child Process Same as Main Process
How Would a Python Script Running on Linux Call a Routine in a Python Script Running Under Wine
Background Thread With Qthread in Pyqt
Typeerror: 'Str' Does Not Support the Buffer Interface