スタック コラプション

これまでにスタックの主な役割について説明してきましたが、スタックが壊れてしまう、ということはよくあるバグのひとつです。それをスタックコラプションといいます。スタックコラプションが発生する主な原因とそれが発生したときの状況についてご説明します。

簡単に壊れるスタック

それぞれのスレッドには関連付けされたスタック領域があり、関数の呼び出し時のパラメータの引渡しやローカル変数の保持に使用されます。

さて、C言語のソースコードには大きな間違いがあります。ひと目でお分かりになりますでしょうか。

void Foo(char** pBuff) {

	char* buff[256];
	*pBuff = (char*) buff;

}

int main( int argc, char* argv[] ) {
	char* lpBuff = NULL;

	Foo(&lpBuff);

	sprintf(lpBuff, "Hello, World\n");
	printf("%s", lpBuff);	
	return 0;

}

関数 Foo でバッファを十分なサイズ用意し、そこに短い文字列を書き込んでいる、だから問題がないのではないかと思われた方は間違いです。 バッファ buff[256] の有効範囲 (スコープ) はあくまで関数 Foo 内だけですから、それを main で使用することは禁じられています。

ところが、実行してみると分かるのですが筆者の環境ではこのプログラムは何の問題もなく動作します (何の問題もないかのように実行してしまう、と表現するのが適切でしょう)。


実行結果 (確かに “Hello, World!” という文字列が表示されている)

ではデバッガを使ってこのプログラムの内部動作を見てみましょう。

0:000> bp stack!Foo ← 関数 Foo にブレークポイントを設定
0:000> g  ← プログラムの実行
Breakpoint 0 hit
eax=0012fee0 ebx=7ffdf000 ecx=0012feac edx=7ffe0304 esi=00000a28 edi=00000000
eip=00401000 esp=0012fed8 ebp=0012fee4 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
stack!Foo:
00401000 55               push    ebp
0:000> kv
ChildEBP RetAddr  Args to Child
0012fed4 0040102c 0012fee0 00000000 0012ffc0 stack!Foo (CONV: cdecl)
0012fee4 0040134e 00000001 00372c18 00372c60 stack!main+0x14 (CONV: cdecl)
0012ffc0 77e3eb69 0006f4d4 00080000 7ffdf000 stack!mainCRTStartup+0x169 
(CONV: cdecl)
0012fff0 00000000 004011e5 00000000 78746341 kernel32!BaseProcessStart+0x23 
(FPO: [Non-Fpo])
0:000> dv
          pBuff = 0012fee0
           buff = Array [256]
0:000> .frame 01
01 0012fee4 0040134e stack!main+0x14
0:000> dv
         lpBuff = 00000000  ""
0:000> g 0040102c
eax=0012fee0 ebx=7ffdf000 ecx=0012fad4 edx=7ffe0304 esi=00000a28 edi=00000000
eip=0040102c esp=0012fedc ebp=0012fee4 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000206
stack!main+14:
0040102c 83c404           add     esp,0x4
0:000> kv
ChildEBP RetAddr  Args to Child
0012fee4 0040134e 00000001 00372c18 00372c60 stack!main+0x14 (CONV: cdecl)
0012ffc0 77e3eb69 0006f4d4 00080000 7ffdf000 stack!mainCRTStartup+0x169 
(CONV: cdecl)
0012fff0 00000000 004011e5 00000000 78746341 kernel32!BaseProcessStart+0x23 
(FPO: [Non-Fpo])
0:000> dv
         lpBuff = 0012fad4  "" ← lpBuff は esp より小さい値を指している
0:000> dc lpBuff l8
0012fee0  0012fad4 0012ffc0 0040134e 00000001  ........N.@.....
0012fef0  00372c18 00372c60 00000094 00000005  .,7.`,7.........
0:000> dc esp l8
0012fedc  0012fee0 0012fad4 0012ffc0 0040134e  ............N.@.
0012feec  00000001 00372c18 00372c60 00000094  .....,7.`,7.....
0:000> g
Hello, World

このようにローカル変数のアドレスを呼び出し元へ返すことで、スタック内の不正な位置へ書き込みを行うことになります。 これは直ちにアクセス違反などを発生させる場合もあれば、今回のように見かけ上、正常終了したように見える場合があります。 特にこのようなバグは、お客様サイトにて実行されたときに姿を現します。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説