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


问题内容

我刚刚更新到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()

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


问题答案:

TL; DRsudo不命令的处理组中由过程向前发送信号自2014
5月28日提交
的释放sudo 1.8.11-蟒处理(须藤的父)和tcpdump的处理(孙子)是通过默认,因此相同的处理组中的sudo不将SIGTERM发送的信号转发.terminate()tcpdump进程。


在以root用户和普通用户+ sudo身份运行该代码时,它显示出相同的行为

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

以as身份运行会root重现该问题:sudotcpdump不会终止进程,.terminate()并且代码仍停留.communicate()在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

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

通常,.terminate()不会递归杀死整个进程树,因此可以预期孙进程可以生存。虽然sudo是一种特殊情况,但从sudo(8)手册页中可以得出

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

即,sudo应该传达SIGTERMtcpdumptcpdump应该停止抓包SIGTERM,tcpdump的距离(8)手册页

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

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

注意:原则上,可以从同一sudo(8)手册页运行命令而无需创建子进程:

在特殊情况下,如果策略插件未定义关闭函数且不需要pty,sudo则将直接执行命令,而不是先调用fork(2)

因此.terminate()可能会tcpdump直接将SIGTERM发送给该进程-尽管不是解释原因:sudo tcpdump在我的测试中,同时在Ubuntu 12.04和15.10上创建了两个进程。

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

它与流程组(作业控制)有关:传递preexec_fn=os.setpgrpsubprocess.Popen()这样,sudo它将进入一个新的流程组(作业),在该流程组中,它是领导者,如tcpdump_process.terminate()在这种情况下在shell中进行工作。

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

解释在sudo的源代码中

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

preexec_fn=os.setpgrp更改sudo的流程组。sudo的子孙(例如tcpdumpprocess)继承了该组。python并且tcpdump不再位于同一个进程组中,因此发送的信号.terminate()被中继sudotcpdump并退出。

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

Ubuntu 15.10使用Sudo version 1.8.12包含提交的内容

wily(15.10)中的sudo(8)手册页仍然只谈到子进程本身,而没有提及进程组:

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

应该改为:

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

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