対策2 カナリア値チェックによるスタック保護
先ほどとは異なり、-fno-stack-protector
オプションを外して、コンパイルします。これによりスタック保護機能が有効なプログラムとなります。
$ gcc sample8.c -o ./sample8
$ ./sample8 mainが呼び出されました Address of main: cf 23 bb af ba 7f 00 00 Address of A: a2 23 bb af ba 7f 00 00 Address of B: 75 23 bb af ba 7f 00 00 Address of C: 9b 22 bb af ba 7f 00 00 Address of D: 84 22 bb af ba 7f 00 00 Aが呼び出されました Bが呼び出されました Cが呼び出されました メモリの内容 before: 00 00 00 00 00 57 3a 3e 5c 11 26 dc 20 0a 8f c7 ff 7f 00 00 93 23 bb af 何か入力してください: 12345 メモリの内容 after: 31 32 33 34 35 00 3a 3e 5c 11 26 dc 20 0a 8f c7 ff 7f 00 00 93 23 bb af *** stack smashing detected ***: terminated Aborted (core dumped)
スタックについては割愛しますが、ここでは「ローカル変数の領域を越えて書き込みが行なわれた」ことが検出されたと考えてください。
そして、この境界を越えて書き込みが行なわれたことを検知するのが、カナリア値です。
アイデアはとても簡単です。
まず、ローカル変数の領域とその次の領域の間の境界にランダムな値を置いておきます。もし、その部分がバッファオーバーフローで上書きされたら、元の値とは変わってしまいます。
つまり、実行前のカナリア値と実行後のカナリア値を比較して、値が変わっていれば、境界を越えた書き込みが起きたと判断できるわけです。
before/afterに注目して見ましょう。
メモリの内容 before: 00 00 00 00 00 57 3a 3e 5c 11 26 dc 20 0a 8f c7 ff 7f 00 00 93 23 bb af
メモリの内容 after: 31 32 33 34 35 00 3a 3e 5c 11 26 dc 20 0a 8f c7 ff 7f 00 00 93 23 bb af
バッファオーバーフローにより、「00 57 3a 3e」が「35 00 3a 3e」に書き換わってしまいました。これこそが、カナリア値の変化です。これを検知して、プログラムがエラーとなったというわけです。