operator newの隠れた整数オーバーフローとVisualStudio 2005のVC++
void vuln(std::size_t nelem) { int* p = new int[nelem]; std::memset(p, 12345, nelem); }
上記の関数には整数オーバーフローに起因するヒープベースバッファオーバーフローの脆弱性があるんですが、どこがまずいかわかりますか?
まずいのはnewしている部分です。C++コンパイラは内部で、nelem * sizeof(int) 、つまりnelem * 0x4U を計算してから、その結果を引数にしてoperator newをcallするんですが*1、もしnelemの値が 0x40000001U だったりしたらどうなるでしょうか?これを4倍した値は0x100000004ですが、33bit目より上は捨てられてしまうので、結局計算値は 4 になります。つまり、vuln(0x40000001U); という呼び出しの中では、new int[0x40000001U] ではなく new int[4]; が行われるわけです。その後0x4000001Uだけmemsetした瞬間に、このコードはクラッシュします。memsetならたいした害はないですが、これがmemcpyだったら条件によっては任意コード実行の脆弱性につながりますよね。
こういうのって、手で対処コードを書くのは結構面倒で、ついさぼってしまったりしがちだと思うんですが、つい先日リリースされた Visual Studio 2005付属のVC++からは、これに対処するためのコードをコンパイラが自動的に挿入してくれるんだそうです。
まだVS2005をインストールしていないので確認してはいないんですが、Michael Howardさん(MSのセキュリティチームの偉い人)の2005/12/06のblogに書いてあるのでたぶん間違いないでしょう。次のように、掛け算のあとに seto でEFLAGSのOF(overflow flag)をチェックしています。そして、オーバーフローが起きると、operator new に引数として固定値(UINT_MAX == 4GB)がわたるようなトリックになっています。このnewは通常失敗して例外(std::bad_allocかな)が飛ぶので、処理が先に進まず安全*2ということのようです。
; _count$ = esi ; 15 : return new CFoo[count]; 00004 33 c9 xor ecx, ecx 00006 8b c6 mov eax, esi 00008 ba 04 00 00 00 mov edx, 4 0000d f7 e2 mul edx 0000f 0f 90 c1 seto cl 00012 f7 d9 neg ecx 00014 0b c8 or ecx, eax 00016 51 push ecx 00017 e8 00 00 00 00 call ??2@YAPAXI@Z ; operator new
こういう、コンパイラでの対処は面白いですね。g++にも搭載されないかなぁ(すでにあったりします?)。
お
Write Great Code〈Vol.1〉ハードウェアを知り、ソフトウェアを書く の2〜4章は、数値表現や演算のお話ですね。買いだ。