Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Warning: Running this command in most shells will result in a broken system that will need a forced shutdown to fix

I understand the recursive function :(){ :|: & };: and what it does. But I don't know where is the fork system call. I'm not sure, but I suspect in the pipe |.

share|improve this question
    
Related (and worth a read): How does a fork bomb work? –  terdon Aug 24 at 8:09

2 Answers 2

up vote 23 down vote accepted

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 extremely quickly.

share|improve this answer
    
In x | y, why is there a sub-shell created? For my understanding, when bash sees a pipe, it executes pipe() system call, that returns two fds. Now, command_left is execed and the output is fed to command_right as input. Now, command_right is execed. So, why is BASHPID different each time? –  shadyabhi Sep 3 '13 at 5:21
2  
@shadyabhi It's simple - x and y are 2 separate commands running in 2 separate processes, so you have 2 separate subshells. If x runs in the same process as the shell, that means x must be a built-in. –  jw013 Sep 3 '13 at 20:59

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.

share|improve this answer
7  
bash might be running on separate terminals. Better would be to use pkill -t pts/2. –  Maciej Piechotka Aug 31 '13 at 19:25
    
@MaciejPiechotka - thanks for the tip. Never seen that one before, I've added it to the answer! –  slm Aug 31 '13 at 22:48

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.