Bashのコマンド汚染ひとり攻防戦

シェルの有名なお遊び(?)に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コマンドも関数定義により汚染することができる。
builtincommandunsetの3つを一度に潰されると打つ手はない。

ひとつの対策:aliasの利用

関数定義をするときに、その名前が

  • すでにaliasに登録されている
  • 引数を持つコマンドのエイリアスである

場合はエラーが出てくる。
例えば私の環境だと

$ alias ls
alias ls='ls -G'
$ ls() { echo ls; }
-bash: 予期しないトークン `(' 周辺に構文エラーがあります

これはどうやら実際には 'ls -G'() { echo ls; } と解釈されてしまうようである。
逆に上記の特性を利用して

$ alias builtin="builtin --"

とすれば、builtinという名の関数を定義することはできなくなる。
また、幸いにもbuiltinコマンドにはオプションがないので -- をつけても問題ない。

unaliasされたり、$PATHbuiltinコマンド置かれたら終わりだって?知らんな

追記:aliasでは対策ができなかった

こちらをご覧ください。

先に挙げた対策は name() { process; } という関数宣言においては有効に働くが、引用ツイート5行目にあるfunction name() { process; } という関数宣言から守ることはできなかった。

しかも exec 2>&1 で標準エラー出力も表示できるようになっているはずなのに、4行目に関してエラーが出ない。
これはシェルスクリプト内においてはaliasが参照されないことを意味する。

まぁいずれにしても根本的対策は変わらない。

根本的対策

他人に自分のシェルをいじらせる隙を与えるな

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です