本連載の第2回と第3回では主にスタックバッファオーバーフローについて説明しました。今回はそれに関連して、スタックバッファオーバーフロー検知の仕組みに関する話を紹介し、その後、動的メモリ管理に関連する脆弱性を見ていきたいと思います。
この連載の第2回と第3回では、スタックバッファオーバーフローを取り上げました。この話題に関連する記事を見つけたのでご紹介しましょう。
Is Your Stack Protector Working?
http://undeadly.org/cgi?action=article&sid=20131202075805
記事の冒頭にも書いてありますが、この記事はOpenBSDの開発者の1人、Ted Unangstさんのブログ記事を転載したものです。
OpenBSDで使われているコンパイラー、GCCには、ProPoliceと呼ばれるスタックバッファオーバーフロー検知のメカニズムが実装されています。OpenBSDでは、この仕組みをデフォルトで有効(オプションでいうと-fstack-protector-all)にして使っています。ブログ記事では、脆弱性を作り込んだテストコードが、OpenBSDでどのようにコンパイルされるのかを確かめています。
使われているテストコードは次のようなものです。
#include <stdio.h> int main(int argc, char *argv[]) { int x = 0xabad1dea; // the best number. char buf[32]; scanf("%32s", buf); printf("%s 0x%x\n", buf, x); return 0; }
scanf()で標準入力から読み込むデータを格納する領域として、32バイトの配列bufが用意されています。ところがscanf()の第1引数は“%32s”なので、最大32文字(ASCII文字で32バイト)まで読み込んでしまいます。scanf()は読み込んだ文字列データの後ろに‘\0’(NULL終端文字)を置くので、配列bufとして確保された領域の1バイト後ろに‘\0’を書き込むことになります。いわゆるoff-by-oneエラーというものです。
ProPoliceなどのバッファオーバーフロー検知技術のアイデアは、このような範囲外への書き込みが起こったことをチェックしようというものです。配列bufの直後(この部分をcanaryと呼んでいます)に一定の値を書き込んでおき、その値が書き換わっていないかどうかの確認を、関数からリターンするタイミングで行うようなコードを入れておくのです。
ブログ記事に掲載されている実行結果では、バッファオーバーフローが検知されプログラムが異常終了しています。
> gcc -o scanf scanf.c > ./scanf 1234567890123456789012345678901 1234567890123456789012345678901 0xabad1dea > ./scanf 12345678901234567890123456789012 12345678901234567890123456789012 0xabad1dea Abort trap
ところが、このブログ記事に付けられたコメントで、「自分の環境では異常終了しないぞ」と書き込んでいる人がいました。
実はこの辺りの挙動は、CPUアーキテクチャによって異なります。Tedさんがこのコードを試したi386アーキテクチャの場合、bufの直後にcanaryが置かれるため、オーバーフローがたった1バイトでも検知されました。
一方、コメントを書き込んだ人が使っていたamd64アーキテクチャの場合、データアラインメントの都合でbufとcanaryの間に隙間が空いてしまうので、1バイトはみ出した程度ではcanary自体の書き換えが起こらず、オーバーフローが検知されません。実際にOpenBSD/amd64環境でコンパイルしたアセンブラーコードを見てみましょう。
Copyright © ITmedia, Inc. All Rights Reserved.