This fork bomb always reminds me of the something an AI programming teacher said on one of the first lessons I attended "To understand recursion, first you must understand recursion".
At it's core, this bomb is a recursive function. In essence, you create a function, which calls itself, which calls itself, which calls itself.... until system resources are consumed. In this specific instance, the recursion is amplified by the use of piping the function to itself AND backgrounding it.
I've seen this answered over on StackOverflow, and I think the example given there illustrates it best, just because it's easier to see what it does at a glance (stolen from the link above...)
☃(){ ☃|☃& };☃
Define the bug function ☃() { ... }
, the body of which calls itself (the bug function), piping the output to itself (the bug function) ☃|☃
, and background the result &
. Then, after the function is defined, actually call the bug function, ; ☃
.
I note that at least on my Arch VM, the need to background the process is not a requirement to have the same end result, to consume all available process space and render the host b0rked. Actually now I've said that it seems to sometimes terminate the run away process and after a screenful of -bash: fork: Resource temporarily unavailable
it will stop with a Terminated
(and journalctl
shows bash core dumping).
To answer your question about csh/tcsh, neither of those shells support functions, you can only alias. So for those shells you'd have to write a shell script which calls itself recursively.
zsh seems to suffer the same fate (with the same code), does not core dump and causes Arch to give Out of memory: Kill process 216 (zsh) score 0 or sacrifice child.
, but it still continues to fork. After a while it then states Killed process 162 (systemd-logind) ...
(and still continues to have a forking zsh).
Arch doesn't seem to have a pacman
version of ksh, so I had to try it on debian instead. ksh objects to :
as a function name, but using something - say b()
instead seems to have the desired result.
Is there anyway to stop this without rebooting the machine?
It's not quite impossible, and you can do it via luck -- i.e., you manage to kill all the processes before another one is spawned.1 But you have to get very very lucky, so it is not a reliable or worthwhile effort [maybe slm is luckier than me here, lol -- TBH I haven't tried that hard]. If you play around with priorities, your chances could improve (see man nice
), although I suspect this will also mess with the efficacy of the fork bomb.
A better idea might be to use one that times out. For an example in C, see footnote number 5 to my answer here.2 You can do the same thing with a shell script, albeit would not be as short as :(){ :|:& };:
:
#!/bin/bash
export fbomb_duration=$1
export fbomb_start=$(date +%s)
go () {
now=$(date +%s)
if [[ $(($now-$fbomb_start)) -gt $fbomb_duration ]]
then exit 0;
fi
go &
}
while ((1)); do
go
done
Execute that with one argument, a number of seconds. All forks will die after that time.
1 In fact, it can happen all on its own, eventually, if the kernel OOM killer gets lucky. But don't hold your breath.
2 The method used there to hamstring that particular bomb (by setting vm.overcommit_memory=2
) will almost certainly not work in general, but you could try. I'm not since I'd like to leave my system running for now ;)
Best Answer
Piping doesn't require that the first instance finishes before the other one starts. Actually, all it is really doing is redirecting the stdout of the first instance to the stdin of the second one, so they can be running simultaneously (as they have to for the fork bomb to work).
':' is not writing anything to the other ':' instance, it's just redirecting the stdout to the stdin of the second instance. If it writes something during its execution (which it never will, since it does nothing but forking itself) it would go to the stdin of the other instance.
It helps to imagine stdin and stdout as a pile:
Whatever is written to the stdin will be piled up ready for when the program decides to read from it, while the stdout works the same way: a pile you can write to, so other programs can read from it when they want to.
That way it's easy to imagine situations like a pipe that has no communication happening (two empty piles) or non-synchronized writes and reads.
Since we are just redirecting the input and output of the instances, there is no requirement for the first instance to finish before the second one starts. It's actually usually desired that both run simultaneously so the second can work with the data being parsed by the first one on the fly. That's what happens here, both will be called without needing to wait for the first to finish. That applies to all pipe chains lines of commands.
The first one wouldn't work, because even though it's running itself recursively, the function is being called in the background (
: &
). The first:
doesn't wait until the "child":
returns before ending itself, so in the end you'd probably only have one instance of:
running. If you had:(){ : };:
it would work though, since the first:
would wait for the "child":
to return, which would wait for its own "child":
to return, and so on.Here's how different commands would look like in terms of how many instances would be running:
:(){ : & };:
1 instance (calls
:
and quits) -> 1 instance (calls:
and quits) -> 1 instance (calls:
and quits) -> 1 instance -> ...:(){ :|: &};:
1 instance (calls 2
:
's and quits) -> 2 instances (each one calls 2:
's and quits) -> 4 instances (each one calls 2:
's and quits) -> 8 instances -> ...:(){ : };:
1 instance (calls
:
and waits for it to return) -> 2 instances (child calls another:
and waits for it to return) -> 3 instances (child calls another:
and waits for it to return) -> 4 instances -> ...:(){ :|: };:
1 instance (calls 2
:
's and waits for them to return) -> 3 instances (children calls 2:
's each and wait for them to return) -> 7 instances (children calls 2:
's each and wait for them to return) -> 15 instances -> ...As you can see, calling the function in the background (using
&
) actually slows the fork bomb, because the callee will quit before the called functions returns.