'
&
$
% 補足:
関数側では受け渡されるものが元々(呼出し側で)配列として確保さ れたものかどうかのチェックはしない。仮引数として与えられるも のを配列の先頭番地と見なして処理を進めるだけである。
• プログラムの12行目 も11行目と同じ処理をしている。一次元配列の場合、配列名は 計算機内部では先頭要素を指す定数ポインタとして扱われているので、v+40 は先頭 から40個後の要素を指すポインタ値である。
'
&
$
% 補足:ポインタに関する算術は普通の算術演算とは異る。v+40の番地は実際には
&v[0]+40×sizeof(double)番地、すなわち &v[40]番地 である。
7.5. 関数呼出しの実装 187
[motoki@x205a]$ cat -n buffer-overflow.c
1 /*---*/
2 /* 関数呼出しの際に引数の値や局所変数等が */
3 /* 共通のスタックに積まれることを理解するための例 */
4 /*---*/
5 /* これらのことが悪用されてコンピュータが不正侵入される */
6 /* こともある。(バッファオーバーフロー攻撃) */
7 /*---*/
8 #include <stdio.h>
9 #include <stdlib.h>
10
11 void test(int a, int b, int c);
12
13 int main(void) 14 {
15 test(1, 2, 3);
16 exit(EXIT_SUCCESS);
17
18 printf("\nここには来ないはずだが、、、。\n");
19 return 0;
20 } 21
22 void test(int a, int b, int c) 23 {
24 int buf[256];
25
26 printf("(関数test内) a= %d\n", a);
27 buf[258] = 999;
28 printf("(関数test内) a= %d\n", a);
29
30 buf[257] += 16;
31 }
[motoki@x205a]$ gcc buffer-overflow.c [motoki@x205a]$ ./a.out
(関数test内) a= 1 (関数test内) a= 999
ここには来ないはずだが、、、。
[motoki@x205a]$
このプログラムの場合、27行目と30行目では確保したメモリ外への書き込みを行ってい る。この部分を見当外れの代入文と見て無視すると、プログラムは次の様な実行結果を出 してくれるはずである。
(関数test内) a= 1
(関数test内) a= 1
しかし、上で示した実行結果では、この期待に反して28行目のprintf()で出力される値 が 999になっているし、実行されるはずのない18行目が実行されている。では何故こう いう実行結果になったのかというと、それは関数呼び出しの実装方法に由来する。実は、
上のプログラムで関数test() が呼び出された直後には、関数呼び出しを実装するために 用意されているpush-downスタックの状態は次の様になっている。
関数 main()
関数 test(a, b, c)
test(1, 2, 3);
exit(0);
呼出し
仮引数 c の領域 仮引数 b の領域 仮引数 a の領域
test() の処理を終了した後の戻り番地 1
2 3
int buf[256];
main() の処理を終了した後の戻り番地 関数 test() 内
の局所配列 buf[ ]
この図を見ると、プログラム27行目で不当に参照している buf[258] という領域は関数
test() の第1仮引数 aのために用意された領域と重なり、30行目で不当に参照している
buf[257] という領域は関数main() への戻り番地を保持する領域と重なっていることが
分かる。それゆえ、
• 27行目の代入文で 仮引数 a への999 という値の代入が行われ、それが28行目の printf()で出力される。 また、
• 30行目の代入文で main()関数への戻り番地が16バイトだけ後にずらされ、その結 果、test()関数を終了すると次に16行目のexit()ではなく18行目のprintf()が 実行されてしまう。
'
&
$
% バッファ・オーバーフロー攻撃:
もし、外部へのサービスを行っているサーバプログラムにユーザ名入力の場面が あり、そこからサーバプログラムの局所変数領域内に実行コード(e.g.シェル実 行)が組み込まれ、「関数処理終了後の戻り番地」を保持する領域にこの実行コー ドの番地がセットされてしまうと、関数処理終了と同時に組み込まれたコードが
(root権限で)実行されてしまう。また、setuidビットが設定され所有者がroot
のコマンド(e.g.passwdコマンド)の引数に、同様のバッファオーバーフローを 引き起こすバイト列が与えられると、悪意のあるコードがroot権限で実行され てしまう。そういった理由で、関数呼び出しのための上記の機構はインターネッ ト等を通じたコンピュータ/サーバへの攻撃の足掛かりにされることもある。一 旦不正侵入されると、root権限で任意のプログラムが実行され得るので甚大な 被害を被る危険性も高い。
Buffer Overflowのセキュリティホールに繋がる関数としてはC言語では次の様
なものを挙げることが出来る。
gets(), strcpy(), strcat(), sprintf(), vsprintf(), scanf(), vscanf(), ...