这个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再高也来不及只好重启。
|