Макрос FORTIFY_SOURCE служит для упрощенной процедуры обнаружения переполнений буфера (buffer overflows) в различных функциях, выполняющих операции с памятью и строками. Не все типы переполнений буфера могут быть обнаружены с помощью этого макроса, но он дает возможность осуществить дополнительную проверку для некоторых функций, которые потенциально могут быть источником проблем, связанных с buffer overflow. Он предназначен для защиты как C, так и C++ кода. FORTIFY_SOURCE вычисляет количество байт, которые будут копироваться из источника в место назначения. Если злоумышленник попытается скопировать больше байтов, чтобы переполнить буфер, выполнение программы останавливается, и будет возвращено следующее исключение:

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

FORTIFY_SOURCE обеспечивает проверку переполнения буфера для следующих функций:

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

На странице руководства Feature Test Macros (man feature_test_macros) говорится:

Если _FORTIFY_SOURCE установлен в 1, то при уровне оптимизации компилятора 1 (gcc -O1) и выше выполняются проверки, которые не должны изменить поведение соответствующих программ.  При установке _FORTIFY_SOURCE в значение 2 добавляется еще несколько проверок, однако при этом некоторые соответствующие программы могут дать сбой.  Некоторые из проверок могут быть выполнены во время компиляции, что приводит к предупреждениям компилятора; другие проверки осуществляются в процессе рантайма, здесь также в случае неудачи выдается ошибка.  Чтобы использовать этот макрос, требуется поддержка компилятора, доступная в gcc(1) начиная с версии 4.0.

Рассмотрим следующий пример, демонстрирующий потенциально опасный код:

// 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));
}

Мы можем скомпилировать приведенный выше пример для использования FORTIFY_SOURCE (-D_FORTIFY_SOURCE) и флагов оптимизации (-g -02) с помощью следующей команды:

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

Если мы разберем двоичный файл, который является результатом вышеприведенной команды, то увидим, что при копировании строки для выявления возможных переполнений буфера никакая дополнительная функция проверки не вызывается:

~]$ 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

Это означает, что все потенциальные переполнения буфера останутся незамеченными и могут позволить злоумышленнику использовать в своих интересах данный изъян в программе. При отладке той же программы видно, что мы можем перезаписать случайные данные (в нашем случае символ 'A', представленный '\x41') в регистр RAX:

$ 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

Далее, давайте раскомментируем включение заголовка string.h в нашу тестовую программу и передадим в функцию strcpy строку, длина которой превышает заданную длину нашего буфера:

// 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));
}

Если мы попытаемся скомпилировать приведенную выше программу, используя FORTIFY_SOURCE и соответствующий флаг оптимизации, то компилятор выдаст предупреждение, поскольку он безошибочно обнаружил превышение допустимого размера буфера в переменной buffer:

~]$ 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));
^

Если разобрать двоичный вывод приведенной выше команды, то можно увидеть вызов <__memcpy_chk@plt>, который проверяет потенциальное переполнение буфера:

~]$ 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
...

Однако если программа будет модифицирована таким образом, что функция strcpy будет принимать значение переменной длины, то при компиляции не появится никаких предупреждений:

// 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
~]$

Поскольку FORTIFY_SOURCE не может предсказать длину строки, передаваемой из argv[1], компилятор не выдает предупреждения о переполнении буфера во время компиляции. Если мы запустим эту программу и передадим ей строку, которая вызовет переполнение буфера, то программа будет прервана:

~]$ ./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

В заключение: при компиляции исходного кода настоятельно рекомендуется использовать FORTIFY_SOURCE. Разработчики должны убедиться, что их код использует защищенные функции, включив правильные заголовки, применяя опцию -D_FORTIFY_SOURCE и флаги оптимизации, равные 1 или больше. Также важно просматривать лог файлы после компиляции исходного кода, чтобы обнаружить любые аномалии, выявленные FORTIFY_SOURCE.

Более подробную информацию о FORTIFY_SOURCE см. на сайте.


Через пару дней пройдет открытый урок "GTK+: создаём приложение на C с графическим интерфейсом пользователя". На этом вебинаре мы познакомимся с широко распространённым фреймворком для создания приложений с графическим интерфейсом пользователя — GTK+ и напишем несложное приложение с его использованием. Регистрация открыта по ссылке.

Комментарии (0)