The fork bomb :(){ :|:& };:

If you’ve been a programmer long enough, you already know that running rm -rf / is one of the most dangerous things you can do. Here is another one, less obvious but just as harmful:

:(){ :|:& };:

It looks like someone fell asleep on the keyboard. Paste it into a terminal on an unprotected machine and within a few seconds nothing responds anymore. (Please don’t try this on your machine. Or at least do it in a VM you don’t care about.)

Let me explain what this actually does.

The trick is that : is a perfectly legal function name in bash. Rename it and the whole thing stops being mysterious:

bomb() {
  bomb | bomb &
}
bomb

bomb() defines a function. Its body calls bomb, pipes the output into another bomb, and the & sends the pair into the background. The semicolon ends the definition, and the final bomb calls it once to get things started.

Here is the original, piece by piece:

PieceWhat it does
:()Defines a function named : that takes no arguments.
{Starts the function body.
:Calls the function itself.
|Pipes its output into a second call.
:Calls the function again, so every call now starts two.
&Runs the pair in the background, so the caller doesn’t wait for it.
};Ends the function definition.
:Calls the function for the first time and starts the chain.

Where does the new process come from?

It comes from the pipe. When bash runs a pipeline like a | b, both sides have to run at the same time, so the shell automatically forks a new process for each side and connects the output of the left side to the input of the right side. That holds even when both sides are the same shell function. Each : in :|: runs in its own forked copy of bash.

The funny part is that : never prints anything, so the pipe doesn’t carry any data at all. It’s only there because it’s a short way to make the shell fork twice.

So the whole thing is essentially a recursive fork, and in effect a denial-of-service (DoS) attack that makes your own system stop responding. Each call starts two new copies of itself. Those two start four, the four start eight, and so on. The & is what makes it nasty. Without it, the bomb still grows exponentially, because both sides of every pipe run at the same time, but every process stays in your terminal’s foreground job, so Ctrl+C or closing the terminal kills the whole tree. With it, every call returns immediately and leaves its children running in the background. By the time you notice, the original shell doesn’t matter anymore. The processes have been orphaned and adopted by init, so neither Ctrl+C nor closing your terminal stops them.

Memory or CPU, which gives out first?

If you run this, which gives out first, the memory or the CPU?

The CPU goes first. None of these processes do any real work, but there are so many of them that the machine spends all its time creating new processes and switching between them. Within seconds every core is at 100%.

Memory climbs more slowly. Every process gets an entry in the kernel’s PID table, the kernel sets aside memory to keep track of each one, and each forked copy of bash has its own stack and a few pages of its own. That adds up, but a maxed-out CPU only makes the machine slow. What actually stops it is the kernel refusing to create any more processes, either because it has hit the limit on how many can exist or because it has no memory left for them. Either way, nothing new can start. You can’t open a shell, run ps, or run most other commands, because each of those needs a new process too.

This is the opposite of a regular recursive function. There, everything happens in one process on one core, so the CPU barely notices. Each call pushes another frame onto the stack, and you run out of memory first, in the form of a stack overflow.

What implications does this have?

This shows why it matters to restrict what your containers and VMs can use through control groups (cgroups). Every cgroup has a pids.max file under /sys/fs/cgroup/, and by default it contains max, which means no limit. Write a number into it instead:

echo 1000 > /sys/fs/cgroup/mycontainer/pids.max

Now once the processes in that cgroup reach 1,000, every new fork fails. The bomb stops growing, and the rest of the machine keeps working. Without that limit, a single container can fill the PID table for the whole host. If you run untrusted code, like an agent sandbox, this is one of the cheapest protections you can set, because sooner or later someone, or some model, is going to type those thirteen characters.