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:

  1. Determine what pseudo terminal the fork bomb is going to run in

    $ tty
    /dev/pts/4
    
  2. 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.