FORTIFY_SOURCE
In recent years Linux distributions started treating security more
seriously. Out of many security features two are directly affecting C
programmers: -fstack-protector
and -D_FORTIFY_SOURCE=2
. These GCC
options are now enabled by default on
Ubuntu and
Fedora.
What do these options do?
-fstack-protector
Consider the following C function:
void fun() { char *buf = alloca(0x100); /* Don't allow gcc to optimise away the buf */ asm volatile("" :: "m" (buf)); }
Compiled without the
stack protector,
with -fno-stack-protector
option, GCC produces the following assembly:
08048404 <fun>: push %ebp ; prologue mov %esp,%ebp sub $0x128,%esp ; reserve 0x128B on the stack lea 0xf(%esp),%eax ; eax = esp + 0xf and $0xfffffff0,%eax ; align eax mov %eax,-0xc(%ebp) ; save eax in the stack frame leave ; epilogue ret
On the other hand with
-fstack-protector
option GCC adds protection code to your functions that use alloca
or
have buffers larger than 8 bytes. Additional code ensures the stack
did not overflow. Here's the generated assembly:
08048464 <fun>: push %ebp ; prologue mov %esp,%ebp sub $0x128,%esp ; reserve 0x128B on the stack mov %gs:0x14,%eax ; load stack canary using gs mov %eax,-0xc(%ebp) ; save it in the stack frame xor %eax,%eax ; clear the register lea 0xf(%esp),%eax ; eax = esp + 0xf and $0xfffffff0,%eax ; align eax mov %eax,-0x10(%ebp) ; save eax in the stack frame mov -0xc(%ebp),%eax ; load canary xor %gs:0x14,%eax ; compare against one in gs je 8048493 <fun+0x2f> call 8048340 <__stack_chk_fail@plt> leave ; epilogue ret
After a function prologue a canary is loaded and saved into the stack. Later, just before the epilogue the canary is verified against the original. If the values don't match the program exits with an appropriate message. This can protect against some buffer overflow attacks. It incurs some performance penalty but it seems to be worth the benefit.
When the stack is overwritten and __stack_chk_fail
branch is taken
the program crashes with a message like this:
*** stack smashing detected ***: ./protected terminated ======= Backtrace: ========= /lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x45)[0xf76da0e5] /lib/i386-linux-gnu/libc.so.6(+0x10409a)[0xf76da09a] ./protected[0x80484de] ./protected[0x80483d7] /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xf75ef4d3] ./protected[0x8048411] ======= Memory map: ======== 08048000-08049000 r-xp 00000000 00:13 4058 ./protected 08049000-0804a000 r--p 00000000 00:13 4058 ./protected 0804a000-0804b000 rw-p 00001000 00:13 4058 ./protected 092e5000-09306000 rw-p 00000000 00:00 0 [heap] f759e000-f75ba000 r-xp 00000000 08:01 161528 /lib/i386-linux-gnu/libgcc_s.so.1 f75ba000-f75bb000 r--p 0001b000 08:01 161528 /lib/i386-linux-gnu/libgcc_s.so.1 f75bb000-f75bc000 rw-p 0001c000 08:01 161528 /lib/i386-linux-gnu/libgcc_s.so.1 f75d5000-f75d6000 rw-p 00000000 00:00 0 f75d6000-f7779000 r-xp 00000000 08:01 161530 /lib/i386-linux-gnu/libc-2.15.so f7779000-f777b000 r--p 001a3000 08:01 161530 /lib/i386-linux-gnu/libc-2.15.so f777b000-f777c000 rw-p 001a5000 08:01 161530 /lib/i386-linux-gnu/libc-2.15.so f777c000-f777f000 rw-p 00000000 00:00 0 f7796000-f779a000 rw-p 00000000 00:00 0 f779a000-f779b000 r-xp 00000000 00:00 0 [vdso] f779b000-f77bb000 r-xp 00000000 08:01 161542 /lib/i386-linux-gnu/ld-2.15.so f77bb000-f77bc000 r--p 0001f000 08:01 161542 /lib/i386-linux-gnu/ld-2.15.so f77bc000-f77bd000 rw-p 00020000 08:01 161542 /lib/i386-linux-gnu/ld-2.15.so ffeb2000-ffed3000 rw-p 00000000 00:00 0 [stack] Aborted
-D_FORTIFY_SOURCE=2
Sample C code:
void fun(char *s) { char buf[0x100]; strcpy(buf, s); /* Don't allow gcc to optimise away the buf */ asm volatile("" :: "m" (buf)); }
Compiled without the code fortified, with -U_FORTIFY_SOURCE
option:
08048450 <fun>: push %ebp ; prologue mov %esp,%ebp sub $0x118,%esp ; reserve 0x118B on the stack mov 0x8(%ebp),%eax ; load parameter `s` to eax mov %eax,0x4(%esp) ; save parameter for strcpy lea -0x108(%ebp),%eax ; count `buf` in eax mov %eax,(%esp) ; save parameter for strcpy call 8048320 <strcpy@plt> leave ; epilogue ret
With -D_FORTIFY_SOURCE=2
:
08048470 <fun>: push %ebp ; prologue mov %esp,%ebp sub $0x118,%esp ; reserve 0x118B on the stack movl $0x100,0x8(%esp) ; save value 0x100 as parameter mov 0x8(%ebp),%eax ; load parameter `s` to eax mov %eax,0x4(%esp) ; save parameter for strcpy lea -0x108(%ebp),%eax ; count `buf` in eax mov %eax,(%esp) ; save parameter for strcpy call 8048370 <__strcpy_chk@plt> leave ; epilogue ret
You can see GCC generated some additional code. This time instead
of calling strcpy(dst, src)
GCC automatically calls
__strcpy_chk(dst, src, dstlen)
. With
FORTIFY_SOURCE
whenever possible GCC tries to uses buffer-length aware replacements
for functions like strcpy
, memcpy
, memset
, etc.
Again, this prevents some buffer overflow attacks. Of course you should avoid
strcpy
and always use strncpy
, but it's worth noting that
FORTIFY_SOURCE can also help with strncpy
when GCC knows the
destination buffer size. For example:
void fun(char *s, int l) { char buf[0x100]; strncpy(buf, s, l); asm volatile("" :: "m" (buf[0])); }
Here GCC instead of calling strncpy(dst, src, l)
will call
__strncpy_chk(dst, src, l, 0x100)
as GCC is aware of the size of the
destination buffer.
When the buffer is overrun the program fails with a message very similar to the one seen previously. Instead of "stack smashing detected" you'll see "buffer overflow detected" headline:
*** buffer overflow detected ***: ./fortified terminated ======= Backtrace: ========= /lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x45)[0xf76d30e5] /lib/i386-linux-gnu/libc.so.6(+0x102eba)[0xf76d1eba] /lib/i386-linux-gnu/libc.so.6(+0x1021ed)[0xf76d11ed] ./fortified[0x8048488] ./fortified[0x80483a7] /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xf75e84d3] ./fortified[0x80483e1] ======= Memory map: ======== 08048000-08049000 r-xp 00000000 00:13 4208 ./fortified 08049000-0804a000 r--p 00000000 00:13 4208 ./fortified 0804a000-0804b000 rw-p 00001000 00:13 4208 ./fortified 08d6b000-08d8c000 rw-p 00000000 00:00 0 [heap] f7597000-f75b3000 r-xp 00000000 08:01 161528 /lib/i386-linux-gnu/libgcc_s.so.1 f75b3000-f75b4000 r--p 0001b000 08:01 161528 /lib/i386-linux-gnu/libgcc_s.so.1 f75b4000-f75b5000 rw-p 0001c000 08:01 161528 /lib/i386-linux-gnu/libgcc_s.so.1 f75ce000-f75cf000 rw-p 00000000 00:00 0 f75cf000-f7772000 r-xp 00000000 08:01 161530 /lib/i386-linux-gnu/libc-2.15.so f7772000-f7774000 r--p 001a3000 08:01 161530 /lib/i386-linux-gnu/libc-2.15.so f7774000-f7775000 rw-p 001a5000 08:01 161530 /lib/i386-linux-gnu/libc-2.15.so f7775000-f7778000 rw-p 00000000 00:00 0 f778f000-f7793000 rw-p 00000000 00:00 0 f7793000-f7794000 r-xp 00000000 00:00 0 [vdso] f7794000-f77b4000 r-xp 00000000 08:01 161542 /lib/i386-linux-gnu/ld-2.15.so f77b4000-f77b5000 r--p 0001f000 08:01 161542 /lib/i386-linux-gnu/ld-2.15.so f77b5000-f77b6000 rw-p 00020000 08:01 161542 /lib/i386-linux-gnu/ld-2.15.so fff8d000-fffae000 rw-p 00000000 00:00 0 [stack] Aborted