I just updated to Ubuntu 15.10 and suddenly in Python 2.7 I am not able to terminate a process I created when being root. For example, this doesn't terminate tcpdump:
import subprocess, shlex, time
tcpdump_command = "sudo tcpdump -w example.pcap -i eth0 -n icmp"
tcpdump_process = subprocess.Popen(
shlex.split(tcpdump_command),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
time.sleep(1)
tcpdump_process.terminate()
tcpdump_out, tcpdump_err = tcpdump_process.communicate()
What happened? It works on previous versions.
TL;DR:
sudo
does not forward signals sent by a process in the command's process group since 28 May 2014 commit released insudo 1.8.11
-- the python process (sudo's parent) and the tcpdump process (grandchild) are in the same process group by default and thereforesudo
does not forwardSIGTERM
signal sent by.terminate()
to thetcpdump
process.Running as a regular user raises
OSError: [Errno 1] Operation not permitted
exception on.terminate()
(as expected).Running as
root
reproduces the issue:sudo
andtcpdump
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 thesudo
process (the child process), nottcpdump
(grandchild):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 dropsudo
;.terminate()
works.In general,
.terminate()
does not kill the whole process tree recursively and therefore it is expected that a grandchild process survives. Thoughsudo
is a special case, from sudo(8) man page:i.e.,
sudo
should relaySIGTERM
totcpdump
andtcpdump
should stop capturing packets onSIGTERM
, from tcpdump(8) man page:i.e., the expected behavior is:
tcpdump_process.terminate()
sends SIGTERM tosudo
which relays the signal totcpdump
which should stop capturing and both processes exit and.communicate()
returnstcpdump
'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:
and therefore
.terminate()
may send SIGTERM to thetcpdump
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 thenkill -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
tosubprocess.Popen()
so thatsudo
will be in a new process group (job) where it is the leader as in the shell makestcpdump_process.terminate()
work in this case.The explanation is in the sudo's source code:
preexec_fn=os.setpgrp
changessudo
's process group.sudo
's descendants such astcpdump
process inherit the group.python
andtcpdump
are no longer in the same process group and therefore the signal sent by.terminate()
is relayed bysudo
totcpdump
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:
It should be instead:
You could open a documentation issue on Ubuntu's bug tracker and/or on the upstream bug tracker.