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 avoidstrcpy 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