Warning message

Log in to add comments.

Enhance application security with FORTIFY_SOURCE

Siddharth Sharma published on 2014-03-26T13:30:10+00:00, last updated 2014-03-26T13:30:10+00:00

The FORTIFY_SOURCE macro provides lightweight support for detecting buffer overflows in various functions that perform operations on memory and strings. Not all types of buffer overflows can be detected with this macro, but it does provide an extra level of validation for some functions that are potentially a source of buffer overflow flaws. It protects both C and C++ code. FORTIFY_SOURCE works by computing the number of bytes that are going to be copied from a source to the destination. In case an attacker tries to copy more bytes to overflow a buffer, the execution of the program is stopped, and the following exception is returned:

*** buffer overflow detected ***: ./foobar terminated
======= Backtrace: =========
/lib64/libc.so.6[0x382d875cff]
/lib64/libc.so.6(__fortify_fail+0x37)[0x382d906b17]
...

FORTIFY_SOURCE provides buffer overflow checks for the following functions:

memcpy, mempcpy, memmove, memset, strcpy, stpcpy, strncpy, strcat, 
strncat, sprintf, vsprintf, snprintf, vsnprintf, gets.

The Feature Test Macros man page (man feature_test_macros) states:

If _FORTIFY_SOURCE is set to 1, with compiler optimization level 1 (gcc -O1) and above, checks that shouldn't change the behavior of conforming programs are performed.  With _FORTIFY_SOURCE set to 2  some  more  checking  is added, but some conforming programs might fail.  Some of the checks can be performed at compile time, and result in compiler warnings; other checks take place at run time, and result in a run-time error if the check fails.  Use of this macro requires compiler support, available with gcc(1) since version 4.0.

Consider the following example that shows potentially dangerous code:

// fortify_test.c
#include<stdio.h>

/* Commenting out or not using the string.h header will cause this
 * program to use the unprotected strcpy function.
 */
//#include<string.h>

int main(int argc, char **argv) {
char buffer[5];
printf ("Buffer Contains: %s , Size Of Buffer is %d\n",
                               buffer,sizeof(buffer));
strcpy(buffer,argv[1]);
printf ("Buffer Contains: %s , Size Of Buffer is %d\n",
                               buffer,sizeof(buffer));
}

We can compile the above example to use FORTIFY_SOURCE (-D_FORTIFY_SOURCE) and optimization flags (-g -02) using the following command:

~]$ gcc -D_FORTIFY_SOURCE=1 -Wall -g -O2 fortify_test.c \
    -o fortify_test

If we disassemble the binary that is the output of the above command, we can see that no extra check function is called to uncover any potential buffer overflows when copying a string:

~]$ objdump -d ./fortify_test
0000000000400440 :
400440:  48 83 ec 18             sub $0x18,%rsp
400444:  ba 05 00 00 00          mov $0x5,%edx
400449:  bf 10 06 40 00          mov $0x400610,%edi
40044e:  48 89 e6                mov %rsp,%rsi
400451:  31 c0                   xor %eax,%eax
400453:  e8 b8 ff ff ff          callq 400410
400458:  48 b8 64 65 61 64 62    movabs $0x6665656264616564,%rax
40045f:  65 65 66
400462:  48 89 e6                mov %rsp,%rsi
400465:  ba 05 00 00 00          mov $0x5,%edx
40046a:  48 89 04 24             mov %rax,(%rsp)
40046e:  bf 10 06 40 00          mov $0x400610,%edi
400473:  31 c0                   xor %eax,%eax
400475:  c6 44 24 08 00          movb $0x0,0x8(%rsp)
40047a:  e8 91 ff ff ff          callq 400410
40047f:  31 c0                   xor %eax,%eax
400481:  48 83 c4 18             add $0x18,%rsp
400485:  c3                      retq
400486:  66 90                   xchg %ax,%ax

This means that any potential buffer overflows would be undetected and could allow an attacker to leverage the flaw in the program. Debugging the same program shows that we can overwrite arbitrary data (in our case with the character 'A' represented by '\x41') in the RAX register:

$ gdb -q ./fortify_test
Reading symbols from /home/sid/security/fortify/fortify_test...done.

(gdb) br 8
Breakpoint 1 at 0x4004b8: file fortify_test.c, line 8.

(gdb) r $(python -c 'print "\x41" * 360')
Starting program: /home/sid/security/fortify/fortify_test \ 
$(python -c 'print "\x41" * 360')

Buffer Contains: ����� , Size Of Buffer is 5
Breakpoint 1, main (argc=, argv=0x7fffffffd788) at fortify_test.c:8
8 printf ("Buffer Contains: %s , Size Of Buffer is %d\n",buffer,
sizeof(buffer));

(gdb) i r
rax 0x7fffffffd690 140737488344720
rbx 0x7fffffffd788 140737488344968
rcx 0x4141414141414141 4702111234474983745
rdx 0x41 65
rsi 0x7fffffffdd40 140737488346432
rdi 0x7fffffffd7ef 140737488345071
rbp 0x0 0x0
rsp 0x7fffffffd690 0x7fffffffd690
r8 0x2d 45
r9 0x0 0
r10 0x7fffffffd450 140737488344144
r11 0x382d974c60 241283058784
r12 0x4004d4 4195540
r13 0x7fffffffd780 140737488344960
r14 0x0 0
r15 0x0 0
rip 0x4004b8 0x4004b8
eflags 0x206 [ PF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0

(gdb) x /100x $rax
0x7fffffffd690: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffd6a0: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffd6b0: 0x41414141 0x41414141 0x41414141 0x41414141
...
0x7fffffffd7d0: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffd7e0: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffd7f0: 0x41414141 0x41414141 0xffffde00 0x00007fff
0x7fffffffd800: 0xffffde6a 0x00007fff 0xffffde85 0x00007fff
0x7fffffffd810: 0xffffde93 0x00007fff 0xffffdeae 0x00007fff

Next, let us uncomment the inclusion of the string.h header in our test program and supply a string to the strcpy function with the length that exceeds the defined length of our buffer:

// fortify_test.c
#include<stdio.h>
#include<string.h>

int main(int argc, char **argv) {
char buffer[5];
printf ("Buffer Contains: %s , Size Of Buffer is %d\n",
                               buffer,sizeof(buffer));
// Here the compiler the length of string to be copied
strcpy(buffer,"deadbeef");
printf ("Buffer Contains: %s , Size Of Buffer is %d\n",
                               buffer,sizeof(buffer));
}

If we attempt to compile the above program using FORTIFY_SOURCE and an appropriate optimization flag, the compiler returns a warning because it correctly detects the buffer overflaw in the buffer variable:

~]$ gcc -D_FORTIFY_SOURCE=1 -Wall -g -O2 fortify_test.c \ 
-o fortify_test

In file included from /usr/include/string.h:636:0,
from fortify_test.c:2:
In function ‘strcpy’,
inlined from ‘main’ at fortify_test.c:7:8:
/usr/include/bits/string3.h:104:3: warning: call to 
__builtin___memcpy_chk will always overflow destination buffer 
[enabled by default]
return __builtin___strcpy_chk (__dest, __src, __bos (__dest));
^

If we disassemble the binary output of the above command, we can see the call to <__memcpy_chk@plt>, which checks for a potential buffer overflow:

~]$ objdump -d ./fortify_test
...
00000000004004b0 :
4004b0:       48 83 ec 18             sub    $0x18,%rsp
4004b4:       ba 05 00 00 00          mov    $0x5,%edx
4004b9:       bf 80 06 40 00          mov    $0x400680,%edi
4004be:       48 89 e6                mov    %rsp,%rsi
4004c1:       31 c0                   xor    %eax,%eax
4004c3:       e8 a8 ff ff ff          callq  400470 <printf@plt>
4004c8:       48 89 e7                mov    %rsp,%rdi
4004cb:       b9 05 00 00 00          mov    $0x5,%ecx
4004d0:       ba 09 00 00 00          mov    $0x9,%edx
4004d5:       be b0 06 40 00          mov    $0x4006b0,%esi
4004da:       e8 b1 ff ff ff          callq  400490<__memcpy_chk@plt> 
4004df:       48 89 e6                mov    %rsp,%rsi
4004e2:       ba 05 00 00 00          mov    $0x5,%edx
4004e7:       bf 80 06 40 00          mov    $0x400680,%edi
4004ec:       31 c0                   xor    %eax,%eax
4004ee:       e8 7d ff ff ff          callq  400470 <printf@plt>
4004f3:       31 c0                   xor    %eax,%eax
4004f5:       48 83 c4 18             add    $0x18,%rsp
4004f9:       c3                      retq   
4004fa:       66 90                   xchg   %ax,%ax
...

However, if the program is modified so that the strcpy function takes a value that has a variable length, compiling it will not return any warnings:

// fortify_test.c
#include<stdio.h>
#include<string.h>

int main(int argc, char **argv) {
char buffer[5];
printf ("Buffer Contains: %s , Size Of Buffer is %d\n",
                               buffer,sizeof(buffer));

// String length is determined at runtime
strcpy(buffer,argv[1]);
printf ("Buffer Contains: %s , Size Of Buffer is %d\n",
                               buffer,sizeof(buffer));
}
~]$ gcc -D_FORTIFY_SOURCE=1 -Wall -g -O2 fortify_test.c \ 
    -o fortify_test
~]$

Because FORTIFY_SOURCE cannot predict the length of the string that is passed from argv[1], the compiler does not return any warning related to a buffer overflow at compile time. If we run this program and supply it with a string that triggers a buffer overflow, the program terminates:

~]$ ./fortify_test $(python -c 'print "\x41" * 360')
Buffer Contains: �Q��� , Size Of Buffer is 5
*** buffer overflow detected ***: ./fortify_test terminated
======= Backtrace: =========
/lib64/libc.so.6[0x382d875cff]
/lib64/libc.so.6(__fortify_fail+0x37)[0x382d906b17]
/lib64/libc.so.6[0x382d904d00]
./fortify_test[0x4004dd]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x382d821d65]
./fortify_test[0x400525]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:05 9967292 /home/sid/security/ \
fortify/fortify_test
00600000-00601000 r--p 00000000 fd:05 9967292 /home/sid/security/ \
fortify/fortify_test
00601000-00602000 rw-p 00001000 fd:05 9967292 /home/sid/security/ \
fortify/fortify_test
013ee000-0140f000 rw-p 00000000 00:00 0 [heap]
382d400000-382d420000 r-xp 00000000 fd:03 922951 /usr/lib64/ld-2.18.so
382d61f000-382d620000 r--p 0001f000 fd:03 922951 /usr/lib64/ld-2.18.so
382d620000-382d621000 rw-p 00020000 fd:03 922951 /usr/lib64/ld-2.18.so
382d621000-382d622000 rw-p 00000000 00:00 0
382d800000-382d9b4000 r-xp 00000000 fd:03 928040 /usr/lib64/ \
libc-2.18.so
382d9b4000-382dbb4000 ---p 001b4000 fd:03 928040 /usr/lib64/ \
libc-2.18.so
382dbb4000-382dbb8000 r--p 001b4000 fd:03 928040 /usr/lib64/ \
libc-2.18.so
382dbb8000-382dbba000 rw-p 001b8000 fd:03 928040 /usr/lib64/ \
libc-2.18.so
382dbba000-382dbbf000 rw-p 00000000 00:00 0
382f800000-382f815000 r-xp 00000000 fd:03 928048 /usr/lib64/ \
libgcc_s-4.8.2-20131212.so.1
382f815000-382fa14000 ---p 00015000 fd:03 928048 /usr/lib64/ \
libgcc_s-4.8.2-20131212.so.1
382fa14000-382fa15000 r--p 00014000 fd:03 928048 /usr/lib64/ \
libgcc_s-4.8.2-20131212.so.1
382fa15000-382fa16000 rw-p 00015000 fd:03 928048 /usr/lib64/ \
libgcc_s-4.8.2-20131212.so.1
7ffb727a5000-7ffb727a8000 rw-p 00000000 00:00 0
7ffb727cc000-7ffb727cf000 rw-p 00000000 00:00 0
7fffa1945000-7fffa1967000 rw-p 00000000 00:00 0 [stack]
7fffa19fe000-7fffa1a00000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Aborted

To conclude this post, the use of FORTIFY_SOURCE is highly recommended when compiling source code. Developers should ensure their code uses protected functions by including the correct headers, use the -D_FORTIFY_SOURCE option, and use optimization flags equal to 1 or greater. It is also important to view the log files after a compilation of the source code to spot any anomalies FORTIFY_SOURCE detected.

For more information on FORTIFY_SOURCE, refer to http://gcc.gnu.org/ml/gcc-patches/2004-09/msg02055.html

About The Author

rhn-support-sisharma's picture

Siddharth Sharma