在 Ubuntu 15.10 中无法终止使用 python 创建的 sudo 进程

Can't terminate a sudo process created with python, in Ubuntu 15.10

我刚刚更新到 Ubuntu 15.10,突然在 Python 2.7 中,我无法 终止 我在 时创建的进程]根。 例如,这不会终止 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()

发生了什么事?它适用于以前的版本。

TL;DR: sudo 不转发命令进程组 since 28 May 2014 commit 中进程发送的信号,已在 sudo 1.8.11 中发布 - - 默认情况下,python 进程(sudo 的父进程)和 tcpdump 进程(孙进程)在同一个进程组中,因此 sudo 不会转发 .terminate() 发送的 SIGTERM 信号到 tcpdump 过程。


It shows the same behaviour when running that code while being the root user and while being a regular user + sudo

运行 作为普通用户在 .terminate() 上引发 OSError: [Errno 1] Operation not permitted 异常(如预期)。

运行 因为 root 重现了这个问题:sudotcpdump 进程没有在 .terminate() 上终止,代码卡在 [=23 上=] 在 Ubuntu 15.10。

相同的代码在 Ubuntu 12.04.

上终止了两个进程

tcpdump_process 名称具有误导性,因为变量指的是 sudo 进程(子进程),而不是 tcpdump(孙进程):

python
└─ sudo tcpdump -w example.pcap -i eth0 -n icmp
   └─ tcpdump -w example.pcap -i eth0 -n icmp          

, you don't need sudo here: you're root already (though you shouldn't be -- you can sniff the network without root)。如果你放弃 sudo; .terminate() 有效。

一般来说,.terminate() 不会递归地杀死整个进程树,因此预计孙进程会存活下来。虽然 sudo 是一个特例,但 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

即,sudo 应该将 SIGTERM 中继到 tcpdumptcpdump 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);

即,预期行为是tcpdump_process.terminate() 将 SIGTERM 发送到 sudo,后者将信号中继到 tcpdump,后者应停止捕获并两个进程都退出并且 .communicate() returns tcpdump 的标准错误输出到 python 脚本。

注意:原则上命令可以运行不创建子进程,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

因此 .terminate() 可能会直接将 SIGTERM 发送到 tcpdump 进程——尽管这不是解释:sudo tcpdump 在 Ubuntu 12.04 和15.10 在我的测试中。

如果我在 shell 中 运行 sudo tcpdump -w example.pcap -i eth0 -n icmp 然后 kill -SIGTERM 终止两个进程。它看起来不像 Python 问题(Python 2.7.3(用于 Ubuntu 12.04)在 Ubuntu 15.10 上表现相同。Python 3 在这里也失败).

它与进程组(job control)有关:将preexec_fn=os.setpgrp传递给subprocess.Popen(),这样sudo就会在一个新的进程组(作业)中是 shell 中的领导者使得 tcpdump_process.terminate() 在这种情况下工作。

What happened? It works on previous versions.

解释在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 更改 sudo 的进程组。 sudo 的后代,例如 tcpdump 进程继承了该组。 pythontcpdump 不再在同一个进程组中,因此 .terminate() 发送的信号由 sudo 中继到 tcpdump 并退出。

Ubuntu 15.04 使用 Sudo version 1.8.9p5 问题中的代码按原样工作。

Ubuntu 15.10 使用包含 the commit.

Sudo version 1.8.12

sudo(8) man page in wily (15.10) 还是只讲子进程本身 -- 没有提到进程组:

As a special case, sudo will not relay signals that were sent by the command it is running.

应该改为:

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.

您可以在 Ubuntu's bug tracker and/or on the upstream bug tracker 上打开文档问题。