6

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

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

4

1 回答 1

10

TL;DR自 2014 年 5 月 28 日提交发布以来sudo,不转发命令进程组中的进程发送的信号——python 进程(sudo 的父进程)和 tcpdump 进程(孙子进程)默认位于同一进程组中,因此不将发送的信号转发给进程。sudo 1.8.11sudoSIGTERM.terminate()tcpdump


当作为 root 用户和作为普通用户 + sudo 运行该代码时,它显示相同的行为

以普通用户身份运行会引发OSError: [Errno 1] Operation not permitted异常.terminate()(如预期的那样)。

运行方式root重现了该问题:进程没有被终止,sudo并且代码卡在 Ubuntu 15.10 上。tcpdump.terminate().communicate()

相同的代码会杀死 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          

正如@Mr.E 在评论中指出的那样,您不需要sudo在这里:您已经是 root (尽管您不应该是 - 您可以在没有 root 的情况下嗅探网络)。如果你掉线sudo.terminate()作品。

一般来说,.terminate()不会递归地杀死整个进程树,因此预计孙进程会存活下来。虽然sudo是一个特殊情况,来自 sudo(8) 手册页

当命令作为进程的子sudo进程运行时,sudo会将 其接收到的信号中继给命令。重点是我的

即,sudo应该中继SIGTERMtcpdumptcpdump停止在SIGTERMtcpdump(8) 手册页上捕获数据包:

Tcpdump 将,...,继续捕获数据包,直到它被 SIGINT 信号(例如,通过键入中断字符,通常是 control-C 生成)或 SIGTERM 信号(通常由 kill(1) 命令生成)中断;

即,预期的行为是tcpdump_process.terminate()发送 SIGTERM 到sudo哪个中继tcpdump应该停止捕获的信号,两个进程都退出并将stderr 输出.communicate()返回到 python 脚本。tcpdump

注意:原则上该命令可以在不创建子进程的情况下运行,来自同一个 sudo(8) 手册页

作为一种特殊情况,如果策略插件没有定义关闭函数并且不需要 pty,sudo将直接执行命令而不是先调用 fork(2)

因此.terminate()可以将 SIGTERMtcpdump直接发送到进程——尽管这不是解释:sudo tcpdump在我的测试中在 Ubuntu 12.04 和 15.10 上创建两个进程。

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

它与进程组(作业控制)有关:传递preexec_fn=os.setpgrpsubprocess.Popen()以便sudo将在一个新的进程组(作业)中作为领导者,因为tcpdump_process.terminate()在这种情况下,shell 会起作用。

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

解释在sudo 的源代码中:

不要转发命令进程组中的进程发送的信号,不要转发它,因为我们不希望孩子间接杀死自己。例如,这可能发生在某些调用 kill(-1, SIGTERM) 以杀死所有其他进程的重启版本中。重点是我的

preexec_fn=os.setpgrp更改sudo的进程组。sudo的后代如tcpdump进程继承该组。python并且tcpdump不再在同一个进程组中,因此发送的信号由to.terminate()中继并退出。sudotcpdump

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

Ubuntu 15.10 使用Sudo version 1.8.12包含commit

wily (15.10) 中的 sudo(8) 手册页仍然只讨论子进程本身——没有提到进程组:

作为一种特殊情况,sudo 不会中继它正在运行的命令发送的信号。

它应该是:

作为一种特殊情况,sudo 不会中继正在运行的命令的进程组中的进程发送的信号。

您可以在Ubuntu 的错误跟踪器和/或上游错误跟踪器上打开文档问题。

于 2015-12-19T22:47:04.360 回答