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


25

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

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


相关(值得一读):叉子炸弹如何工作?
terdon

Answers:


30

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

$ 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

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

拆卸前叉炸弹的工作方式:

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

这往往不会占用太多内存,但是会吸收PID并占用CPU周期。


在中x | y,为什么要创建一个子外壳?据我了解,当bash看到a时pipe,它将执行pipe()系统调用,并返回2 fds。现在,将exec编辑command_left 并将输出作为输入馈送到command_right。现在,已exec编辑command_right 。那么,为什么BASHPID每次都不同?
Abhijeet Rastogi 2013年

2
@shadyabhi很简单- xy2个在2个独立的进程中运行单独的命令,让你有2个独立的子shell。如果x运行过程与外壳运行过程相同,则意味着它x必须是内置的。
jw013 2013年

24

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

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

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

您可以将换成|:&其他东西,以便对发生的事情有所了解。

设置观察者

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

$ watch "ps -eaf|grep \"[s]leep 61\""

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

在另一个窗口中,我们将运行一个稍作修改的前叉炸弹版本。此版本将尝试限制自身,以便我们可以研究它的作用。我们的版本将在调用该函数之前休眠61秒:()

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

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

# control + z
[1]+  Stopped                 sleep 61
[2] 5845
$ bg
[1]+ sleep 61 &

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

$ jobs
[1]-  Running                 sleep 61 &
[2]+  Running                 : | : &

几分钟后:

$ jobs
[1]-  Done                    sleep 61
[2]+  Done                    : | :

与观察者签到

同时,在另一个我们正在运行的窗口中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

流程层次

ps -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
...
...

清理时间

A killall bash会在事情失控之前阻止它们。以这种方式进行清理可能会比较费力,一种较温和的方式(可能不会将每个bash外壳都撕裂)将执行以下操作:

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

    $ tty
    /dev/pts/4
    
  2. 杀死伪终端

    $ pkill -t pts/4

发生什么了?

好吧,每次调用bashsleep都是从运行命令fork()bashshell中调用C函数。


7
bash可能在单独的终端上运行。最好使用pkill -t pts/2
Maciej Piechotka

@MaciejPiechotka-感谢您的提示。以前从未见过,我已将其添加到答案中!
slm
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.