Where is the fork() on the fork bomb :(){ :|: & };:?
As a result of the pipe in x | y
, a subshell is created to contain the pipeline as part of the foreground process group. This continues to create subshells (via fork()
) indefinitely, thus creating a fork bomb.
$ 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
The fork does not actually occur until the code is run, however, which is the final invocation of :
in your code.
To disassemble how the fork bomb works:
:()
- define a new function called:
{ :|: & }
- a function definition that recursively pipes the calling function into another instance of the calling function in the background:
- call the fork bomb function
This tends to not be too memory intensive, but it will suck up PIDs and consume CPU cycles.
The last bit of the code, ;:
is running the function :(){ ... }
. This is where the fork is occurring.
The semicolon terminates the first command, and we're starting another one, i.e. invoking the function :
. The definition of this function includes a call to itself (:
) and the output of this call is piped to a backgrounded version :
. This props up the process indefinitely.
Every time you're calling the function :()
you're calling the C function fork()
. Eventually this will exhaust all the process IDs (PIDs) on the system.
Example
You can swap out the |:&
with something else so you can get an idea of what's going on.
Setup a watcher
In one terminal window do this:
$ watch "ps -eaf|grep \"[s]leep 61\""
Setup the "fuse delayed" fork bomb
In another window we'll run a slightly modified version of the fork bomb. This version will attempt to throttle itself so we can study what it's doing. Our version will sleep for 61 seconds before calling the function :()
.
Also we'll background the initial call as well, after it's invoked. Ctrl + z, then type bg
.
$ :(){ sleep 61; : | : & };:
# control + z
[1]+ Stopped sleep 61
[2] 5845
$ bg
[1]+ sleep 61 &
Now if we run the jobs
command in the initial window we'll see this:
$ jobs
[1]- Running sleep 61 &
[2]+ Running : | : &
After a couple of minutes:
$ jobs
[1]- Done sleep 61
[2]+ Done : | :
Check in with the watcher
Meanwhile in the other window where we're running 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
Process hierarchy
And a ps -auxf
shows this process hierarchy:
$ 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
...
...
Clean up time
A killall bash
will stop things before they get out of hand. Doing your clean up this way may be a little heavy handed, a kinder gentler way which won't potentially tear every bash
shell down, would be to do the following:
Determine what pseudo terminal the fork bomb is going to run in
$ tty /dev/pts/4
Kill the pseudo terminal
$ pkill -t pts/4
So what's going on?
Well each invocation of bash
and sleep
is a call to the C function fork()
from the bash
shell from where the command was run.