我有一个运行bash脚本的Python脚本。我需要能够杀死bash脚本,如果它似乎是无限的,它也必须在chroot jail中运行,因为脚本可能是危险的。我用psutil.Popen()
运行它并让它运行两秒钟。如果它没有自然结束,我会向它及其所有可能的孩子发送SIGKILL
。
问题是,如果由于超时执行而导致一个脚本被杀死并运行另一个脚本,那么主(Python)脚本会收到SIGSTOP
。在我的本地机器上,我做了一个非常愚蠢的解决方案:Python脚本在启动时将其PID写入文件,然后我运行另一个脚本,该脚本每秒发送SIGCONT
到存储在文件中的PID。这有两个问题:它真的很愚蠢,但更糟糕的是它拒绝在服务器上运行 - SIGCONT
只是在那里什么也没做。
顺序是:Python脚本运行响应jail的bash脚本,并且bash脚本运行可能危险和/或无限的脚本。这个脚本也可能有一些孩子。
代码的相关部分:
主要python脚本
p = psutil.Popen(["bash", mode, script_path, self.TESTENV_ROOT])
start = time.time()
while True:
if p.status() == psutil.STATUS_ZOMBIE:
# process ended naturally
duration = time.time() - start
self.stdout.write("Script finished, execution time: {}s".format(duration))
break
if time.time() > start + run_limit:
children = p.children(recursive=True)
for child in children:
child.kill()
p.kill()
duration = None
self.stdout.write("Script exceeded maximum time ({}s) and was killed.".format(run_limit))
break
time.sleep(0.01)
os.kill(os.getpid(), 17) # SIGCHLD
return duration
在chroot中运行脚本($ 1是要在chroot jail中运行的脚本,$ 2是jail路径)
#!/usr/bin/env bash
# copy script to chroot environment
cp "$1" "$2/prepare.sh"
# run script
chmod u+x "$2/prepare.sh"
echo './prepare.sh' | chroot "$2"
rm "$2/prepare.sh"
示例prepare.sh脚本
#!/bin/bash
echo asdf > file
我花了一些时间试图解决这个问题。我发现这个脚本(不使用chroot jail来运行bash脚本)运行得很完美:
import psutil
import os
import time
while True:
if os.path.exists("infinite.sh"):
p = psutil.Popen(["bash","infinite.sh"])
start = time.time()
while True:
if p.status() == psutil.STATUS_ZOMBIE:
# process ended naturally
break
if time.time() > start + 2:
# process needs too much time and has to be killed
children = p.children(recursive=True)
for child in children:
child.kill()
p.kill()
break
os.remove("infinite.sh")
os.kill(os.getpid(), 17)
我的问题是:
SIGSTOP
?是由于chroot监狱?感谢您的想法。
编辑:我发现在我杀死加时赛之后,当我运行第一个脚本的那一刻,我感到心悸。无论我使用os.system
还是psutil.Popen
。
EDIT2:我在控制chroot jail的bash脚本中做了更多调查,关键行是echo './prepare.sh' | chroot "$2"
。现在的问题是,它到底出了什么问题?
EDIT3: This可能是一个相关问题,如果有人帮助的话。
答案 0 :(得分:2)
我很确定你在Mac OS而不是Linux上运行它。为什么?您正在向主python进程发送信号17
,而不是使用:
import signal
signal.SIGCHLD
我相信你有一个信号17
的处理程序,它应该重新生成被监禁的进程以响应这个信号。
但是Linux上的signal.SIGCHLD == 17
和Mac OS上的signal.SIGCHLD == 20
。
现在你的问题的答案是:
Mac OS signal.SIGSTOP == 17
是的,您的流程通过SIGSTOP
发送os.kill(os.getpid(), 17)
给自己
Mac OS signal man page
修改强>
实际上它也可以在Linux上发生,因为Linux signal man page表示POSIX标准允许信号17
为SIGUSR2
,SIGCHLD
或SIGSTOP
。因此,我强烈建议使用标准库的signal
模块中的常量而不是硬编码的信号编号。
答案 1 :(得分:1)
好的,我终于找到了解决方案。问题实际上是在bash脚本中的chroot行:
echo './prepare.sh' | chroot "$2"
由于某种原因,这似乎不正确。在chroot中运行命令的正确方法是:
chroot chroot_path shell -c command
例如:
chroot '/home/chroot_jail' '/bin/sh' -c 'rm -rf /'
希望这有助于某人。
答案 2 :(得分:1)
这个帖子有点老了,但我相信我知道你问题的原因(有类似的问题):
来自here它说:
Linux支持下面列出的标准信号。 [...]首先是原始POSIX.1-1990标准中描述的信号。
Signal Value Action Comment ────────────────────────────────────────────────────────────────────── SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process SIGINT 2 Term Interrupt from keyboard SIGQUIT 3 Core Quit from keyboard SIGILL 4 Core Illegal Instruction SIGABRT 6 Core Abort signal from abort(3) SIGFPE 8 Core Floating-point exception SIGKILL 9 Term Kill signal SIGSEGV 11 Core Invalid memory reference SIGPIPE 13 Term Broken pipe: write to pipe with no readers; see pipe(7) SIGALRM 14 Term Timer signal from alarm(2) SIGTERM 15 Term Termination signal SIGUSR1 30,10,16 Term User-defined signal 1 SIGUSR2 31,12,17 Term User-defined signal 2 SIGCHLD 20,17,18 Ign Child stopped or terminated SIGCONT 19,18,25 Cont Continue if stopped SIGSTOP 17,19,23 Stop Stop process SIGTSTP 18,20,24 Stop Stop typed at terminal SIGTTIN 21,21,26 Stop Terminal input for background process SIGTTOU 22,22,27 Stop Terminal output for background process
它表明,当收到SIGTSTP,SIGTTIN或SIGTTOU信号时,进程(每个默认操作)也会停止。
这page解释了:
[SIGTTIN和SIGTTOU]是发送到后台进程的信号,它们尝试从(SIGTTIN)读取或写入(SIGTTOU)它们的控制 终端(或tty)。
...
[...] [后台进程]更改终端设置确实会导致发送SIGTTOU
我使用sudo strace -tt -o [trace_output_file] -p [pid]
查看哪个信号触发了我的进程停止。
如何解决问题?遗憾的是,我无法让你的缩减示例工作:你的infinite.sh看起来如何?为什么要在执行期间删除它? 我建议重定向stdin和stdout。你试过以下这个吗?
from subprocess import DEVNULL
p = psutil.Popen(["bash", mode, script_path, self.TESTENV_ROOT],
stdout=DEVNULL, stderr=DEVNULL, STDIN=DEVNULL)
您当然也可以使用subprocess.PIPE来处理Python代码中的输出或简单地重定向到文件。我不知道如何处理未经授权的修改tty设置的尝试。