Fork爆弾(フォークばくだん)とは、コンピュータシステムへのDoS攻撃の一種で、新たなプロセスを生成するfork機能を使ったものである[1]。Fork爆弾はワームやウイルスのようにコンピュータからコンピュータへ広がることはない。これは、コンピュータ上で同時に実行可能なプログラム数あるいはプロセス数に制限があるという前提に依存したものである[2]。このような自己複製プログラムを wabbit、bacteria、rabbit programs などと呼ぶ。wabbit は単に自己複製するだけでなく、悪意ある副作用を持つようプログラムすることもできる[3]。
Fork爆弾は非常に高速に多数のプロセスを生成して、コンピュータのオペレーティングシステムの管理するプロセスのリストを埋め尽くす。プロセステーブルが埋め尽くされると、いずれかのプロセスを終了させない限り新たなプロセスを生成できなくなる。そして、たとえ1つのプロセスを終了させて対処のためのプロセスを生成しようとしても、Fork爆弾のプロセス群は空いたプロセスのスロットを迅速に埋めようと待っているのである。
プロセステーブルを埋め尽くすだけでなく、Fork爆弾はプロセッサ時間とメモリも占有する。結果としてFork爆弾以外のプロセス群やシステムは動作が困難(低速)となるか、動作できなくなる。
悪意を持って仕掛けられることもあるが、通常のソフトウェア開発において偶然Fork爆弾が発生してしまうこともある。ネットワークソケットで要求を待ちうけるクライアントサーバモデルにおけるサーバプログラムは、無限ループとなっていることが多く、しかもforkで子プロセスを生成する。そういったアプリケーションにちょっとしたバグがあれば、評価中にFork爆弾のように振る舞うことがある。
次の例は、UNIXのbashまたはzshでFork爆弾として機能する13文字であり、特によく知られている。
:(){ :|:& };:
まず、先頭の ":()" は ":" という関数(引数なし)を定義することを宣言している。続く "{ :|:& };" がその本体で、自分自身を2つ起動してパイプでつなぎ、バックグラウンドで実行することを意味する。バックグラウンドなので、親プロセスをkillしても子プロセスは終了しない。最後の ":" がその関数の実行開始を意味する。この文字列は関数名を ":" として意味をわかりにくくしている。これをもっと判りやすい名前に置換すると次のようになる。
forkbomb(){ forkbomb|forkbomb & } ; forkbomb
Microsoft Windows の cmd および command.com 用バッチファイル:
%0|%0
#include <unistd.h>
int main()
{
while(1)
fork();
return 0;
}
Perlによる例:
fork while fork
Pythonによる例:
import os
while True:
os.fork()
Rubyによる例:
def forkbomb
loop { fork { forkbomb } }
end; forkbomb
Fork爆弾が起動させられてしまった場合、その対処としてはFork爆弾が生成した全プロセスを終了させることしかないため、システムのリブート以外に対処法がないという状況になる可能性がある。問題のプロセス群を強制終了させるには新たなプロセスを生成しなくてはならないが、プロセステーブルの空きスロットがない可能性があるし、メモリ管理のデータ構造が不足している可能性もある。さらに、Fork爆弾のプロセスが終了してプロセステーブルのスロットが空いたとしても、残っているFork爆弾プロセスが素早く空きスロットを埋めようとする。Windows では、Fork爆弾をスタートさせたユーザーがログアウトすると問題が緩和される可能性がある。
しかし、実際の場ではシステム管理者が比較的容易にFork爆弾を抑制できることもある。例えば
:(){ :|: & };:
というシェル版Fork爆弾を考えてみよう。このコードが意味することは、fork を済ませた親プロセスがシステムに残り続けるわけではなく、すぐに終了するという事実である。従ってFork爆弾はプロセステーブルを埋めると同時に次々と終了している。そこで十分に頻繁に新たなプロセスを起動させようとすれば、Fork爆弾以外のプロセスを起動できる可能性がある。何もしないで存在するだけのプロセスをどんどん起動していけばFork爆弾プロセスの個数を減らすことができ、最終的に一掃できる可能性がある。例えば、次のような Z Shell 用コードでFork爆弾を一掃できる可能性がある[要出典]。
while (sleep 100 &) do; done
Fork爆弾プロセス群を停止させてから終了させるには、次のようなコマンドを入力する。
killall -STOP processWithBombName
killall -KILL processWithBombName
しかしこれには問題がある。killallコマンドはアトミックに動作するわけではないので、シグナルを送るべきプロセスのPIDを取得してから、実際にシグナルを送るまでにターゲットのプロセスが終了してしまい、Fork爆弾が数世代先に進んでいる可能性がある。
Linuxでは、procfsからプロセステーブルにアクセスできるので、bash の組み込み機能を使い、新たにプロセスを生成しなくともFork爆弾プロセスを終了させられる可能性がある。次の例はprocfsを使って問題のプロセスを特定し、それらを停止させ、終了させる。
cd /proc;
for p in [0-9]*; do read CMDLINE < $p/cmdline; if [[$CMDLINE == "processWithBombName"]]; then kill -s SIGSTOP $p; fi; done
for p in [0-9]*; do read CMDLINE < $p/cmdline; if [[$CMDLINE == "processWithBombName"]]; then kill -s SIGKILL $p; fi; done
基本的には通常のセキュリティ対策を施して、不正なアクセスによるプロセス生成を防ぐのが第一である。しかし、Fork爆弾はプログラミング初心者のミスによって発生することもある。
Fork爆弾が作動する方法は、可能な限り多くのプロセスを生成することである。従ってFork爆弾を防ぐには、1つのプログラムあるいは1人のユーザーが生成できるプロセス数を制限すればよい。信頼できないユーザーが相対的に少数のプロセスしか生成できないようにすれば、悪意があるかどうかに関わらず Fork爆弾の危険性は減る。しかし、複数のユーザーが協力してFork爆弾を使用すれば、そのプロセス数の合計がシステム全体のプロセス数の制限を使い切る可能性は依然として残る。
なお、偶発的にFork爆弾が生じた場合、複数ユーザーがそれに関係することはほとんどありえない。Linuxカーネルのgrsecurityというパッチには、Fork爆弾を開始したユーザーをロギングする機能もある[4]。
Unix系システムにはプロセス数の制限があり、ulimit というシェルコマンド[5]、あるいはその後継の setrlimit で制御される。Linuxカーネルにはユーザー毎のプロセス数を制限する RLIMIT_NPROC があり、setrlimit()
システムコールで設定される。また、LinuxやBSD系OSでは、/etc/security/limits.conf を編集することでも同様の効果が得られる[6]。
また、OSがFork爆弾を検出するという対策もある。Linuxには rexFBD というカーネルモジュールがあり[7]、そのような機能を実装している。
FreeBSDでは、システム管理者が /etc/login.conf
に制限を置くことができる[8]。
このような予防策を講じたとしても、Fork爆弾はシステムに重大な影響を及ぼす可能性がある。例えば、24個のCPUのあるサーバで、各ユーザーが100個までのプロセスを生成できる設定になっている場合、Fork爆弾が100個までしかプロセスを生成できないとしても24個のCPU全部を100%使用することもありうる。これによりシステムは応答不可能となり、システム管理者が問題を解決するためにログインすらできない状況になりうる。