我想让我的系统对分叉炸弹更具弹性。这可以通过使用 systemd 的DefaultTasksMax
参数或使用所有有效 shellcgroup
的pids
控制器来部分实现,例如通过 cgrulesengd:
*:bash pids users/shells/bash/
*:zsh pids users/shells/zsh/
还有一个用于设置 shell 限制的小脚本:
CGDIR=/sys/fs/cgroup/
mkdir -p $CGDIR/pids/users/shells/
echo '8192' > $CGDIR/pids/users/shells/pids.max
mkdir -p $CGDIR/pids/users/shells/bash/
echo '2048' > $CGDIR/pids/users/shells/bash/pids.max
mkdir -p $CGDIR/pids/users/shells/zsh/
echo '1024' > $CGDIR/pids/users/shells/zsh/pids.max
当我使用 bash shell 启动终端并:(){ :|:& };:
在其中执行时,什么也没有发生。我几乎立即得到 2048 pids.current
,CPU 利用率为 50-60%。当我关闭终端时,叉子炸弹就会消失,一切都会恢复正常。
当我想使用 zsh shell 对终端执行相同操作时,我立即获得 100% CPU,并且在关闭终端后,fork 炸弹仍然会杀死我的系统。解决此问题的唯一方法是killall -9 zsh
在装有其他 shell 的终端中发出几次命令。
为什么不能像在 bash 中杀死 fork 炸弹一样在 zsh 中杀死它?我的意思是,只要关闭终端,炸弹就会被引爆。
答案1
在 中bash
,当fork()
EAGAIN 失败时,当达到限制时(使用 setrlimit,或使用那些 cgroup pid 限制),bash
休眠并在 1 秒后再次尝试,然后在 2 秒后(如果再次失败),然后在 4 秒后,然后它休眠 8 秒并放弃,甚至没有尝试另一个 fork(!))。
然后bash
,一旦达到限制(在安静的系统上不到一秒的时间),大多数 bash 进程就会休眠。这就是为什么叉式炸弹bash
比其他炮弹的危害要小得多。
关闭终端不会终止这些进程。也许您看到的是 fork 炸弹在 15 秒多一点 (1+2+4+8) 后逐渐消失,此时所有成功启动并进入睡眠状态的进程在其启动后 8 秒同时死亡。第四次尝试分叉。
在 中zsh
,没有这样的重试和睡眠,所有进程都 fork 和退出。当一个进程死亡时,就会释放一个可供分叉进程之一使用的进程。
如果你想杀死一个 fork 炸弹,最简单的方法就是杀死整个进程组,使用它killall
并不可行,因为killall
需要收集进程列表,然后为每个进程单独发送杀死。这对于所有进程都处于睡眠状态的情况来说很好bash
,但对于始终生成进程的其他 shell 来说则不然,因此可以在每次终止之间启动进程。
您可以使用 获取 pgid ps -j
,并使用 终止进程组kill -- "-$pgid"
。