昨日公開した記事「IEに0-dayのバッファ・オーバーフロー?」に対して、ある知人から次のような質問をされました。「スタック・オーバーフローが任意コードの実行に結びつく可能性があるとの事だが、それは一体どのような状況なのか?」といった内容です。同様の疑問を持った読者がいるかもしれません。そこで、この場を借りてお答えしたいと思います。
「絶対ない」とは書かなかった理由
前回の記事で書いたように、スタック・オーバーフローで任意コードの実行が可能になる状況は非常に限定されています。というよりも、通常はあり得ません。単に無限再帰がスタックを消費し尽くすだけであり、バッファ・オーバーフローのように、リターン・アドレス(関数からの戻り先アドレス)などの特定アドレスを書き換えられるわけではありません。
ただし、「任意コード実行が可能となる状況は絶対にあり得ないのか?」と言えばそうとも言い切れません。前回の記事を書いているとき、任意コード実行の危険性は絶対に無いと言い切ってよいかどうか考えてみましたが、無理やり考えると無くは無さそうなので「理論的には、非常に限定された条件なら可能となる場合もありますが……」としました。
そこで今回は、スタック・オーバーフローで任意コードの実行が可能となる、特殊な状況(脆弱性)についてお話したいと思います。また、そのような状況はどの程度発生しやすいのかについても考えてみます。
任意コードの実行が可能となる例
スタックには、通常、リターン・アドレスと、関数内で確保されたローカル・バッファ(ローカル変数エリア)が格納されます。再帰無限呼び出しが発生すると、関数からの戻りアドレスとローカル・バッファを格納するためのメモリー・エリアがスタックに確保され続け、最終的にはスタックが足らなくなり、スタック・オーバーフロー例外が発生します。ここまでの過程では、DoS攻撃に至る事はあっても攻撃者が用意したコードを実行される事はありません。
再帰呼び出しが発生する前に例外ハンドラが定義されていた場合、スタック・オーバーフロー例外が発生すると、その例外ハンドラに処理が移行します。この例外ハンドラの中で、もしスタック・フレーム中に記述されている特定の4バイト値を参照し、その値を関数のエントリ・アドレスとして解釈して呼び出すようなコードが記述されていると、場合によっては任意コードの実行が可能となります。
具体的には、以下のすべてを満たすと任意コードの実行が可能となります。
- スタック・フレーム中のあるアドレスに、関数 e() のエントリ・アドレスがセットされている
- ローカル・バッファに、攻撃者がセットできるデータのポインタ buf を入れる処理がある
- ローカル・バッファが再帰により確保およびセットされ続け、関数 e() のエントリ・アドレスが bufのアドレス に上書きされる
- 例外ハンドラがあらかじめ用意されており、その例外ハンドラの中でスタックから関数 e() のエントリ・アドレスを取得し、呼び出している
現実的には限りなくゼロ
しかし条件3は、よほどトリッキーな処理をしない限り発生しないバグだと思います。また、上記すべての条件を満たすコードというのは、現実的にはちょっと無さそうな気がします。他にも、任意コードが実行可能となる状況はいくつか考えられますが、いずれのバグも上記の条件に負けず劣らず特殊であり、「ついうっかり」作りこんでしまうレベルのものでは無さそうです。
という訳で、スタック・オーバーフローによる任意コード実行は、「可能となる状況もあるが、その発生率は限りなく0に近い」と思います。ですので、通常はスタック・オーバーフローで可能な攻撃は最大でDoSであり、ウイルス感染や攻撃者による不正侵入などを許す事は無いでしょう。