シェルの有名なお遊び(?)にFork爆弾(fork bomb)と言うものがある。
なおこのコマンドを実際に実行してはいけない。
:(){ :|:& };:
このコードは少ない文字数で記述されているという特徴があるが、組み込み(builtin)コマンドの :
を上書きしているというのも特筆すべき(??)点である。
組み込みコマンドを上書きできるといろいろ悪さができそうなので、いろいろな悪さの仕方とそれの対策を考えてみた。
通常のコマンド汚染とその対策
まず組み込みコマンドの話をする前に、通常のコマンドを関数で上書きするところから始める。
$ zsh() {
> echo そんなものはない
> }
$ zsh
これだと「そんなものはない」が出力されて終わる。
この状態でちゃんとzshを実行するためには以下のどちらかを実行すれば良い。
$ unset zsh #関数定義を消す
$ command zsh #$PATHにあるzshコマンドを直接実行する
Aliasによる汚染とその対策
$ alias emacs="vim"
これはよく見る(???)戦争の火種である。
これを仕掛けられたemacsユーザは落ち着いて以下のコマンドのどちらかを入力してほしい。
$ unalias emacs #aliasを消す
$ "emacs" #aliasを無視してコマンドを実行する
組み込みコマンド汚染
さて、本題に戻る。:
のように組み込みコマンドが上書きできるということは、command
コマンドもunset
コマンドも書き換えられてしまう危険があるということだ。
$ command() {
> unko.say 残念だな
> }
$ unset() {
> unko.shout 無駄だッ!
> }
$ unset command
_人人人人人人人_
> 無駄だッ! <
 ̄Y^Y^Y^Y^Y^Y^Y^ ̄
?
(???)
(?????)
(???????)
(?????????)
汚染例に出されてしまうシェル芸人御用達コマンド
組み込みコマンド汚染の対策
組み込みコマンドを汚染されたときに、最後に頼りになるのはその名の通りbuiltin
コマンドだ。
$ builtin unset command
上のコマンドを実行したときの挙動は以下のようになる。
builtin unset
で本来のunset
コマンドを呼び出す- したがって通常のbashにおける
unset command
と同じ挙動になる unset command
は、ユーザ定義されたcommand
関数を削除する- これによって
command
コマンドの挙動が通常に戻る
同じようにbuiltin unset unset
も実行できる。
builtin & command & unset潰し
もちろんこのbuiltin
コマンドも関数定義により汚染することができる。builtin
、command
、unset
の3つを一度に潰されると打つ手はない。
ひとつの対策:aliasの利用
関数定義をするときに、その名前が
- すでにaliasに登録されている
- 引数を持つコマンドのエイリアスである
場合はエラーが出てくる。
例えば私の環境だと
$ alias ls
alias ls='ls -G'
$ ls() { echo ls; }
-bash: 予期しないトークン `(' 周辺に構文エラーがあります
これはどうやら実際には 'ls -G'() { echo ls; }
と解釈されてしまうようである。
逆に上記の特性を利用して
$ alias builtin="builtin --"
とすれば、builtin
という名の関数を定義することはできなくなる。
また、幸いにもbuiltin
コマンドにはオプションがないので --
をつけても問題ない。
unalias
されたり、$PATH
にbuiltin
コマンド置かれたら終わりだって?知らんな
追記:aliasでは対策ができなかった
こちらをご覧ください。
先に挙げた対策は name() { process; }
という関数宣言においては有効に働くが、引用ツイート5行目にあるfunction name() { process; }
という関数宣言から守ることはできなかった。
しかも exec 2>&1
で標準エラー出力も表示できるようになっているはずなのに、4行目に関してエラーが出ない。
これはシェルスクリプト内においてはalias
が参照されないことを意味する。
まぁいずれにしても根本的対策は変わらない。
根本的対策
他人に自分のシェルをいじらせる隙を与えるな