Макрос 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+ и напишем несложное приложение с его использованием. Регистрация открыта по ссылке.
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/709660/
Добавить комментарий