这个fork bomb提取自此处:
:(){ :|:& };:
SA经常用这个脚本来测试用户进程限额设置是否正确(通过配置/etc/security/limits.conf和PAM)。
今天试了一把,真把系统搞挂了,最后不得不由SA重启机器-_-b
下面解析下这13个char:
:()
定义一个函数,名叫:,无参。
{ :|:& };
:的函数体。
:
调用该函数。
再看函数体部分,如果只是:,那只是个简单的尾递归程序,如果bash优化得当不会有任何问题。
再变复杂一些:
: => :|:
这样每递归一次,都会额外再创建2个进程(到这里其实就已经会不断fork,算是半个fork bomb),A和B,且A的STDOUT是B的STDIN,且caller会执行waitpid等待这两个进程挂掉,且caller挂掉会带走它们俩。
考虑第一次递归的情况,加上本身1个,总共会有3个进程,证明一下:
vi test:
#!/bin/bash
a(){
a
};
b(){
b
};
a|b
./test执行后,在另一个窗口ps -ef|grep test,可以看到确实如此:
root 17001 16625 0 20:53 pts/0 00:00:00 /bin/bash ./test
root 17002 17001 99 20:53 pts/0 00:00:01 /bin/bash ./test
root 17003 17001 99 20:53 pts/0 00:00:01 /bin/bash ./test
还可以看到函数体中管道两边创建的进程的爹都是caller: 17001 。
最后:
:|: => :|:&
加上&是为了让爹死了之后自己还能活。
shell命令如果没有&,执行过程是:
shell先fork一个进程A,这个进程收到SIGHUP信号时会退出。- 在A中调用
execve执行任务 shell执行waitpid等待A挂掉。
然后是带&的情况:
shell先fork一个进程B,并执行execve开始干活;该进程收到SIGHUP时不会退出,只是改认init(PID=1)为爹。shell不等待B挂掉继续干自己的活。
所以:|:&的净效果是,再fork一个进程B,工作内容是:|:,这时候caller没活干了也不用等B挂,所以直接退出。B在做:|:时又要再创建2个进程干同样的活,且要等它们挂。
这样1轮递归的效果是1个进程变成3个;到第2轮时B创建的2个进程会导致另外6个进程被创建,然后第一轮的3个都退出,还剩6个;到第3轮时,上一轮6个进程中的4个会导致另外12个进程被创建,然后这6个都退出,剩12个;之后依次类推。
容易发现规律是1->3->6->12->24,之后都是倍增,几何级数。且kill一个爹最多带走2个子,APM再高也来不及只好重启。
