Why do these bash fork bombs work differently and what is the significance of & in it?

10,608

WARNING DO NOT ATTEMPT TO RUN THIS ON A PRODUCTION MACHINE. JUST DON'T. Warning: To try any "bombs" make sure ulimit -u is in use. Read below[a].

Let's define a function to get the PID and date (time):

bize:~$ d(){ printf '%7s %07d %s\n' "$1" "$BASHPID" "$(date +'%H:%M:%S')"; }

A simple, non-issue bomb function for the new user (protect yourself: read [a]):

bize:~$ bomb() { d START; echo "yes"; sleep 1; d END; } >&2

When that function is called to be executed works as this:

bize:~$ bomb
  START 0002786 23:07:34
yes
    END 0002786 23:07:35
bize:~$

The command date is executed, then a "yes" is printed, an sleep for 1 second, then the closing command date, and, finally, the function exits printing a new command prompt. Nothing fancy.

| pipe

When we call the function like this:

bize:~$ bomb | bomb
  START 0003365 23:11:34
yes
  START 0003366 23:11:34
yes
    END 0003365 23:11:35
    END 0003366 23:11:35
bize:~$

Two commands get started at same time, both commands will end 1 second later and then the prompt returns.

That's the reason for the pipe |, to start two processes in parallel.

& background

If we change the call adding an ending &:

bize:~$ bomb | bomb &
[1] 3380
bize:~$
  START 0003379 23:14:14
yes
  START 0003380 23:14:14
yes
    END 0003379 23:14:15
    END 0003380 23:14:15

The prompt returns immediately (all the action is sent to the background) and the two commands get executed as before. Please note the value of "job number" [1] printed before the PID of the process 3380. Later, the same number will be printed to indicate that the pipe has ended:

[1]+  Done                    bomb | bomb

That is the effect of &.

That is the reason of the &: to get processes started faster.

Simpler name

We can create a function called simply b to execute the two commands. Typed in three lines:

bize:~$ b(){
> bomb | bomb
> }

And executed as:

bize:~$ b
  START 0003563 23:21:10
yes
  START 0003564 23:21:10
yes
    END 0003564 23:21:11
    END 0003563 23:21:11

Note that we used no ; in the definition of b (the newlines were used to separate elements). However, for a definition on one line, it is usual to use ;, like this:

bize:~$ b(){ bomb | bomb ; }

Most of the spaces are also not mandatory, we can write the equivalent (but less clear):

bize:~$ b(){ bomb|bomb;}

We can also use a & to separate the } (and send the two processes to the background).

The bomb.

If we make the function bite its tail (by calling itself), we get the "fork bomb":

bize:~$ b(){ b|b;}       ### May look better as b(){ b | b ; } but does the same.

And to make it call more functions faster, send the pipe to the background.

bize:~$ b(){ b|b&}       ### Usually written as b(){ b|b& }

If we append the first call to the function after a required ; and change the name to : we get:

bize:~$ :(){ :|:&};:

Usually written as :(){ :|:& }; :

Or, written in a fun way, with some other name (a snow-man):

☃(){ ☃|☃&};☃

The ulimit (which you should have set before running this) will make the prompt return quite quickly after a lot of errors (press enter when the error list stops to get the prompt).

The reason of this being called a "fork bomb" is that the way in which the shell starts a sub-shell is by forking the running shell and then calling exec() to the forked process with the command to run.

A pipe will "fork" two new processes. Doing it to infinity causes a bomb.
Or a rabbit as was originally called because it reproduces so quickly.


Timing:

  1. :(){ (:) | (:) }; time :
    Terminated
    real 0m45.627s

  2. :(){ : | :; }; time :
    Terminated
    real 0m15.283s

  3. :(){ : | :& }; time :
    real 0m00.002 s
    Still Running


Your examples:

  1. :(){ (:) | (:) }; :

    Where the second closing ) separates the } is a more complex version of :(){ :|:;};:. Each command in a pipe is called inside a sub-shell anyway. Which is the effect of the ().

  2. :(){ : | :& }; :

    Is the faster version, written to have no spaces: :(){(:)|:&};: (13 characters).

  3. :(){ : | : }; : ### works in zsh but not in bash.

    Has a syntax error (in bash), a metacharacter is needed before the closing },
    as this:

     :(){ : | :; }; :
    

[a] Create a new clean user (I'll call mine bize). Login to this new user in a console either sudo -i -u bize, or:

$ su - bize
Password: 
bize:~$

Check and then change the max user processes limit:

bize:~$ ulimit -a           ### List all limits (I show only `-u`)
max user processes              (-u) 63931
bize:~$ ulimit -u 10        ### Low
bize:~$ ulimit -a
max user processes              (-u) 1000

Using only 10 works as is only one solitary new user: bize. It makes easier to call killall -u bize and get the system rid of most (not all) bombs. Please do not ask which ones still work, I will not tell. But still: Is quite low but on the safe side, adapt to your system.
This will ensure that a "fork bomb" will not collapse your system.

Further reading:

Share:
10,608

Related videos on Youtube

Dan K.
Author by

Dan K.

Updated on September 18, 2022

Comments

  • Dan K.
    Dan K. over 1 year

    I get how a normal fork bomb works, but I don't really understand why the & at the end of the common bash fork bomb is required and why these scripts behave differently:

    :(){ (:) | (:) }; :
    

    and

    :(){ : | :& }; :
    

    The former causes a cpu usage spike before throwing me back to the login screen. The latter instead just causes my system to freeze up, forcing me to hard reboot. Why is that? Both continually create new processes, so why does the system behave differently?

    Both of the scripts also behave differently from

    :(){ : | : }; :
    

    which doesn't cause any problems at all, even though I would have expected them to be alike. The bash manual page states that the commands in a pipeline are already executed in a subshell, so I'm led to believe that : | : should already suffice. I belive & should just run the pipeline in a new subshell, but why does that change so much?

    Edit: Using htop and limiting the amount of processes, I was able to see that the first variant creates an actual tree of processes, the second variant creates all the processes on the same level and the last variant doesn't seem to create any processes at all. This confuses me even more, but maybe it helps somehow?

    • adonis
      adonis about 8 years
      i think your last variant is missing a semicolon: :(){ : | :; }; :