叉炸弹上的叉()在哪里:(){ :|: & };:?

mav*_*lan 27 linux shell system-calls

警告:在大多数 shell 中运行此命令将导致系统损坏,需要强制关闭才能修复

我了解递归函数:(){ :|: & };:及其作用。但是我不知道fork系统调用在哪里。我不确定,但我怀疑在管道中|。

Chr*_*own 32

作为管道中的结果,x | y创建了一个子外壳来包含管道作为前台进程组的一部分。这会继续fork()无限期地创建子shell(通过),从而创建一个叉子炸弹。

$ for (( i=0; i<3; i++ )); do
>     echo "$BASHPID"
> done
16907
16907
16907
$ for (( i=0; i<3; i++ )); do
>     echo "$BASHPID" | cat
> done
17195
17197
17199
Run Code Online (Sandbox Code Playgroud)

但是,在代码运行之前实际上不会发生分叉,这是:代码中的最终调用。

拆解叉形炸弹的工作原理:

  • :() - 定义一个名为的新函数 :
  • { :|: & } - 一个函数定义,它递归地将调用函数传递到后台调用函数的另一个实例中
  • : - 调用叉炸弹功能

这往往不会占用太多内存,但会占用 PID 并消耗 CPU 周期。

  • @shadyabhi 这很简单 - `x` 和 `y` 是在 2 个独立进程中运行的 2 个独立命令,因此您有 2 个独立的子 shell。如果 `x` 与 shell 在同一进程中运行,则意味着 `x` 必须是内置的。 (2认同)

slm*_*slm 24

代码的最后一点;:是运行函数:(){ ... }。这就是分叉发生的地方。

分号终止第一个命令,我们开始另一个命令,即调用函数:。此函数的定义包括对自身( :)的调用,并且此调用的输出通过管道传送到后台版本:。这无限期地支持了这个过程。

每次调用该函数时:(),都是在调用 C 函数fork()。最终这将耗尽系统上的所有进程 ID (PID)。

例子

你可以|:&用其他东西换掉,这样你就可以了解发生了什么。

设置观察者

在一个终端窗口中执行以下操作:

$ watch "ps -eaf|grep \"[s]leep 61\""
Run Code Online (Sandbox Code Playgroud)

设置“保险丝延迟”叉炸弹

在另一个窗口中,我们将运行一个稍微修改过的 fork 炸弹版本。此版本将尝试自我调节,以便我们可以研究它在做什么。我们的版本会在调用函数之前休眠 61 秒:()。

此外,在调用后,我们还将对初始调用进行后台处理。Ctrl+ z,然后键入bg。

$ :(){ sleep 61; : | : & };:

# control + z
[1]+  Stopped                 sleep 61
[2] 5845
$ bg
[1]+ sleep 61 &
Run Code Online (Sandbox Code Playgroud)

现在,如果我们jobs在初始窗口中运行命令,我们将看到:

$ jobs
[1]-  Running                 sleep 61 &
[2]+  Running                 : | : &
Run Code Online (Sandbox Code Playgroud)

几分钟后:

$ jobs
[1]-  Done                    sleep 61
[2]+  Done                    : | :
Run Code Online (Sandbox Code Playgroud)

与观察员签到

同时在我们运行的另一个窗口中watch:

Every 2.0s: ps -eaf|grep "[s]leep 61"                                                                                                                                             Sat Aug 31 12:48:14 2013

saml      6112  6108  0 12:47 pts/2    00:00:00 sleep 61
saml      6115  6110  0 12:47 pts/2    00:00:00 sleep 61
saml      6116  6111  0 12:47 pts/2    00:00:00 sleep 61
saml      6117  6109  0 12:47 pts/2    00:00:00 sleep 61
saml      6119  6114  0 12:47 pts/2    00:00:00 sleep 61
saml      6120  6113  0 12:47 pts/2    00:00:00 sleep 61
saml      6122  6118  0 12:47 pts/2    00:00:00 sleep 61
saml      6123  6121  0 12:47 pts/2    00:00:00 sleep 61
Run Code Online (Sandbox Code Playgroud)

流程层次结构

aps -auxf显示了这个进程层次结构:

$ ps -auxf
saml      6245  0.0  0.0 115184  5316 pts/2    S    12:48   0:00 bash
saml      6247  0.0  0.0 100988   468 pts/2    S    12:48   0:00  \_ sleep 61
....
....
saml      6250  0.0  0.0 115184  5328 pts/2    S    12:48   0:00 bash
saml      6268  0.0  0.0 100988   468 pts/2    S    12:48   0:00  \_ sleep 61
saml      6251  0.0  0.0 115184  5320 pts/2    S    12:48   0:00 bash
saml      6272  0.0  0.0 100988   468 pts/2    S    12:48   0:00  \_ sleep 61
saml      6252  0.0  0.0 115184  5324 pts/2    S    12:48   0:00 bash
saml      6269  0.0  0.0 100988   464 pts/2    S    12:48   0:00  \_ sleep 61
...
...
Run Code Online (Sandbox Code Playgroud)

清理时间

Akillall bash会在事情失控之前阻止事情。以这种方式进行清理可能有点笨手笨脚,一种更温和的方式可能不会将每个bash外壳都拆掉,可以执行以下操作:

  1. 确定 fork 炸弹将在哪个伪终端运行

    $ tty
    /dev/pts/4
    
    Run Code Online (Sandbox Code Playgroud)
  2. 杀死伪终端

    $ pkill -t pts/4
    
    Run Code Online (Sandbox Code Playgroud)

发生什么了?

好吧,每次调用bash和sleep都是从运行命令fork()的bashshell对 C 函数的调用。

  • `bash` 可能在不同的终端上运行。最好使用`pkill -t pts/2`。 (7认同)