_FORTIFY_SOURCEというバッファーオーバーフロー攻撃を防ぐのにとても有用なマクロがある。
知らなかった人は以下のmanでもまず見てください

http://linuxjm.sourceforge.jp/html/LDP_man-pages/man7/feature_test_macros.7.html

_FORTIFY_SOURCE (glibc 2.3.4 以降)
このマクロを定義すると、文字列やメモリの操作を行う様々な関数を 使用する際にバッファオーバーフローを検出するための軽めのチェックが 実行されるようになる。すべてのバッファオーバーフローが検出される わけではなく、あくまでよくある例についてだけである。 現在の実装では、以下の関数にチェックが追加されている: memcpy(3), mempcpy(3), memmove(3), memset(3), stpcpy(3), strcpy(3), strncpy(3), strcat(3), strncat(3), sprintf(3), snprintf(3), vsprintf(3), vsnprintf(3), gets(3). _FORTIFY_SOURCE が 1 に設定された場合、コンパイラの最適化レベルが 1 (gcc -O1) かそれ以上であれば、規格に準拠するプログラムの振る舞いを 変化させないようなチェックが実行される。 _FORTIFY_SOURCE が 2 に設定された場合、さらなるチェックが追加されるが、 規格に準拠するプログラムのいくつかが失敗する可能性がある。 いくつかのチェックはコンパイル時に実行でき、コンパイラの警告として 表示される。他のチェックは実行時に行われ、チェックに失敗した場合 には実行時エラーとなる。 このマクロを使用するにはコンパイラの対応が必要であり、 バージョン 4.0 以降の gcc(1) で利用できる。



じゃあ、これがどういう風に動くのかという話なんですが、いくつか実例を見せます。

まず、固定長配列を使ったケース
https://gist.github.com/kosaki/5514378

まあ、明らかにバッファが1バイト足りないんですが、

cc -D_FORTIFY_SOURCE=2 -Wall -g -O2    strcpy_test.c   -o strcpy_test
In file included from /usr/include/string.h:642,
from strcpy_test.c:1:
In function ‘strcpy’,
inlined from ‘main’ at strcpy_test.c:10:
/usr/include/bits/string3.h:105: 警告: call to __builtin___strcpy_chk will always overflow destination buffer


コンパイルするといきなり、いや絶対バッファーオーバーフローするからやめとけと警告してくれます。親切ですね。
で、実行すると strcpyを呼んだ所で *** buffer overflow detected *** というメッセージとともに abort() してくれます。abortされちゃうと攻撃者としては乗っ取り失敗なので防御になってるわけです。

% ./strcpy_test
./strcpy_test
*** buffer overflow detected ***: ./strcpy_test terminated
======= Backtrace: =========
/lib64/libc.so.6(__fortify_fail+0x37)[0x3d28502507]
/lib64/libc.so.6[0x3d285003f0]
./strcpy_test[0x400506]
/lib64/libc.so.6(__libc_start_main+0xfd)[0x3d2841ecdd]
./strcpy_test[0x400429]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:02 1861838 /home/kosaki/tmp/alloc_size/strcpy_test
00600000-00601000 rw-p 00000000 fd:02 1861838 /home/kosaki/tmp/alloc_size/strcpy_test
00f08000-00f29000 rw-p 00000000 00:00 0 [heap]
3d28000000-3d28020000 r-xp 00000000 fd:00 1175192 /lib64/ld-2.12.so
3d2821f000-3d28220000 r--p 0001f000 fd:00 1175192 /lib64/ld-2.12.so
3d28220000-3d28221000 rw-p 00020000 fd:00 1175192 /lib64/ld-2.12.so
3d28221000-3d28222000 rw-p 00000000 00:00 0
3d28400000-3d2858a000 r-xp 00000000 fd:00 1175212 /lib64/libc-2.12.so
3d2858a000-3d28789000 ---p 0018a000 fd:00 1175212 /lib64/libc-2.12.so
3d28789000-3d2878d000 r--p 00189000 fd:00 1175212 /lib64/libc-2.12.so
3d2878d000-3d2878e000 rw-p 0018d000 fd:00 1175212 /lib64/libc-2.12.so
3d2878e000-3d28793000 rw-p 00000000 00:00 0
3d32c00000-3d32c16000 r-xp 00000000 fd:00 1175232 /lib64/libgcc_s-4.4.7-20120601.so.1
3d32c16000-3d32e15000 ---p 00016000 fd:00 1175232 /lib64/libgcc_s-4.4.7-20120601.so.1
3d32e15000-3d32e16000 rw-p 00015000 fd:00 1175232 /lib64/libgcc_s-4.4.7-20120601.so.1
7f640b265000-7f640b268000 rw-p 00000000 00:00 0
7f640b277000-7f640b279000 rw-p 00000000 00:00 0
7fff67b14000-7fff67b29000 rw-p 00000000 00:00 0 [stack]
7fff67bf3000-7fff67bf4000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
[1] 24433 abort (core dumped) ./strcpy_test



次に、mallocを使ったケース
https://gist.github.com/kosaki/5514394

これもまったく同じようにコンパイル時警告+実行時abortに成功します。すばらしいですね。
次にネタばらしに入るのですが、現実のプロジェクトでよくあるようにmallocに一枚ラッパーをかぶせて
使ってみます。

xmallocを使ったケース
https://gist.github.com/kosaki/5514404


% make
make
cc -D_FORTIFY_SOURCE=2 -Wall -g -O2 strcpy_test3.c xmalloc.c -o strcpy_test3

% ./strcpy_test3


おや、一転して警告も実行時エラーも出なくなってしまいました。
そうです。gccは "malloc" という関数がなにをしている関数が知っていてチートしていたのです。

じゃあ、どうすればいいかというと __attribute__((alloc_size)) という機能をつかって自分の関数が
mallocと同等であるとコンパイラに教えてあげればいい。

マニュアルから引用
http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
alloc_size
The alloc_size attribute is used to tell the compiler that the function return value points to memory, where the size is given by one or two of the functions parameters. GCC uses this information to improve the correctness of __builtin_object_size.
The function parameter(s) denoting the allocated size are specified by one or two integer arguments supplied to the attribute. The allocated size is either the value of the single function argument specified or the product of the two function arguments specified. Argument numbering starts at one.

For instance,

void* my_calloc(size_t, size_t) __attribute__((alloc_size(1,2)))
void my_realloc(void*, size_t) __attribute__((alloc_size(2)))
declares that my_calloc returns memory of the size given by the product of parameter 1 and 2 and that my_realloc returns memory of the size given by parameter 2.


annotateしたソース
https://gist.github.com/kosaki/5514415

これは mallocを直接呼んだソースと、まったく同じように動作する。

mallocに皮をかぶせるだけで、_FORTIFY_SOURCEの機能の大半を無効化してしまい大変もったいないので、全員いますぐ alloc_sizeを理解して使うべきである。

はい、このエントリでいいたいこと終わり。以下は余談。

じゃあ、ここで strcpyが実際にどう展開されているかであるが、gccのソース読むとめどいので代わりに
wcpcpy で説明する。まあ、だいたい同じだ。だいたい。


_fortify_function wchar_t *
__NTH (wcpcpy (wchar_t *__restrict __dest, const wchar_t *__restrict __src))
{
if (__bos (__dest) != (size_t) -1)
return __wcpcpy_chk (__dest, __src, __bos (__dest) / sizeof (wchar_t));
return __wcpcpy_alias (__dest, __src);
}


ここで、__bos(__dest) が -1 のときは、元々の wcpcpyを呼ぶけど、それ以外のときは
wcpcpy_chk()を呼ぶという処理になっているがわかる。

https://gist.github.com/kosaki/5514541


__bosというのは __builtin_object_size() という gccの builtinで バッファーオーバーフローを
理解する上で超重要な機能。

マニュアルから引用 http://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html
— Built-in Function: size_t __builtin_object_size (void * ptr, int type)
is a built-in construct that returns a constant number of bytes from ptr to the end of the object ptr pointer points to (if known at compile time). __builtin_object_size never evaluates its arguments for side-effects. If there are any side-effects in them, it returns (size_t) -1 for type 0 or 1 and (size_t) 0 for type 2 or 3. If there are multiple objects ptr can point to and all of them are known at compile time, the returned number is the maximum of remaining byte counts in those objects if type & 2 is 0 and minimum if nonzero. If it is not possible to determine which objects ptr points to at compile time, __builtin_object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 for type 2 or 3.


要約すると、sizeof()だと配列の時しかバッファサイズとれないけど、__builtin_object_sizeだとポインタでも、コンパイルタイムに計算可能だったらサイズが取れるよ。と

というわけで、無事バッファ長がとれたので、コピーしてる最中にバッファ長超えそうになったら
abortする処理(ここでは __chk_fail()) を呼ぶようにするだけである。


/* Copy SRC to DEST, returning the address of the terminating L'\0' in
DEST. Check for overflows. */
wchar_t *
__wcpcpy_chk (wchar_t *dest, const wchar_t *src, size_t destlen)
{
wchar_t *wcp = (wchar_t *) dest - 1;
wint_t c;
const ptrdiff_t off = src - dest + 1;

do
{
if (__builtin_expect (destlen-- == 0, 0))
__chk_fail ();
c = wcp[off];
*++wcp = c;
}
while (c != L'\0');

return wcp;
}



こんな感じで __builtin_object_size() さえ理解しておけば自作関数にバッファオーバーフローチェックを追加するのは、あんまり難しくない。