Как регистрируются загрязнения канареек?

11

Флаг GCC -fstack-protector flag разрешает использование канареек стека для защиты переполнения стека. Использование этого флага по умолчанию стало более заметным в последние годы.

Если пакет скомпилирован с -fstack-protector, и мы переполняем буфер в программе, мы, вероятно, получим ошибку, такую ​​как:

*** buffer overflow detected ***: /xxx/xxx terminated

Тем не менее, «кто» отвечает за эти сообщения об ошибках? Где эти сообщения регистрируются? Демон системного журнала выбирает эти сообщения?

aedcv
источник

Ответы:

10

Обнаружение разрушения стека libssp, которое является частью gcc. Он очень старается вывести сообщение в терминал, и только в случае неудачи он записывает в системный журнал - так что на практике вы увидите сообщения о переполнении буфера в журналах для демонов и, возможно, приложений с графическим интерфейсом.

Как только он выведет свое сообщение, он libsspпопытается выйти из него различными способами, включая сбой приложения; это может быть обнаружено одним из ненормальных регистраторов выхода, но это не гарантировано.

Стивен Китт
источник
1
Позвольте мне привести конкретный пример как способ дальнейшего изучения этого объяснения. Давайте выберем nginx для этого примера. Я скомпилировал nginx со стеком канареек. Когда я запускаю nginx, он запускает процесс, но ничего не выводит в оболочку. Вместо этого любые сообщения регистрируются в его нескольких файлах журнала. Если nginx обнаружит разрушение стека, он libsspвыведет свое сообщение с помощью вывода stderr, используемого nginx. Затем libsspможет попытаться выйти из процесса (или дочернего процесса для nginx). Если приложение «не нуждается» в аварийном завершении приложения, то аварийные регистраторы выхода не поймут это. Это правильная интерпретация?
aedcv
Не совсем - он попытается завершить работу приложения, используя __builtin_trap()сначала, а затем, если это не удается, пытается спровоцировать нарушение сегмента, и только если это не удается, выход со статусом 127.
Стивен Китт
Печать части сообщений не имеет лучших гарантий успеха, чем выход с помощью основного метода (например abort()).
maxschlepzig
7

Современные дистрибутивы Linux, такие как CentOS / Fedora , по умолчанию устанавливают демон обработки сбоев (например, systemd-coredumpили abortd).

Таким образом, когда ваша программа завершается ненормальным образом (segfault, uncaught exception, abort, недопустимая инструкция и т. Д.), Это событие регистрируется и регистрируется этим демоном. Таким образом, вы найдете некоторые сообщения в системном журнале и, возможно, ссылку на каталог с некоторыми дополнительными сведениями (например, файл ядра, журналы и т. Д.).

пример

$ cat test_stack_protector.c 
#include <string.h>

int f(const char *q)
{
  char s[10];
  strcpy(s, q);
  return s[0] + s[1];
}

int main(int argc, char **argv)
{
  return f(argv[1]);
}

Обобщение:

$ gcc -Wall -fstack-protector test_stack_protector.c -o test_stack_protector

Выполнение:

$ ./test_stack_protector 'hello world'
*** stack smashing detected ***: ./test_stack_protector terminated
======= Backtrace: =========
/lib64/libc.so.6(+0x7c8dc)[0x7f885b4388dc]
/lib64/libc.so.6(__fortify_fail+0x37)[0x7f885b4dfaa7]
/lib64/libc.so.6(__fortify_fail+0x0)[0x7f885b4dfa70]
./test_stack_protector[0x400599]
./test_stack_protector[0x4005bd]
/lib64/libc.so.6(__libc_start_main+0xea)[0x7f885b3dc50a]
./test_stack_protector[0x40049a]
======= Memory map: ========
00400000-00401000 r-xp 00000000 00:28 1151979                            /home/juser/program/stackprotect/test_stack_protector
00600000-00601000 r--p 00000000 00:28 1151979                            /home/juser/program/stackprotect/test_stack_protector
00601000-00602000 rw-p 00001000 00:28 1151979                            /home/juser/program/stackprotect/test_stack_protector
0067c000-0069d000 rw-p 00000000 00:00 0                                  [heap]
7f885b1a5000-7f885b1bb000 r-xp 00000000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b1bb000-7f885b3ba000 ---p 00016000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b3ba000-7f885b3bb000 r--p 00015000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b3bb000-7f885b3bc000 rw-p 00016000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b3bc000-7f885b583000 r-xp 00000000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b583000-7f885b783000 ---p 001c7000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b783000-7f885b787000 r--p 001c7000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b787000-7f885b789000 rw-p 001cb000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b789000-7f885b78d000 rw-p 00000000 00:00 0 
7f885b78d000-7f885b7b4000 r-xp 00000000 00:28 945341                     /usr/lib64/ld-2.25.so
7f885b978000-7f885b97b000 rw-p 00000000 00:00 0 
7f885b9b0000-7f885b9b3000 rw-p 00000000 00:00 0 
7f885b9b3000-7f885b9b4000 r--p 00026000 00:28 945341                     /usr/lib64/ld-2.25.so
7f885b9b4000-7f885b9b6000 rw-p 00027000 00:28 945341                     /usr/lib64/ld-2.25.so
7ffc59966000-7ffc59987000 rw-p 00000000 00:00 0                          [stack]
7ffc5999c000-7ffc5999f000 r--p 00000000 00:00 0                          [vvar]
7ffc5999f000-7ffc599a1000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
zsh: abort (core dumped)  ./test_stack_protector 'hello world'

Состояние выхода - 134, что составляет 128 + 6, то есть 128 плюс номер сигнала прерывания.

Системный журнал:

Oct 16 20:57:59 example.org audit[17645]: ANOM_ABEND auid=1000 uid=1000 gid=1000 ses=3 subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 pid=17645 comm="test_stack_prot" exe="/home/juser/program/stackprotect/test_stack_protector" sig=6 res=1
Oct 16 20:57:59 example.org systemd[1]: Started Process Core Dump (PID 17646/UID 0).
Oct 16 20:57:59 example.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-coredump@21-17646-0 comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
Oct 16 20:57:59 example.org systemd-coredump[17647]: Process 17645 (test_stack_prot) of user 1000 dumped core.

                           Stack trace of thread 17645:
                           #0  0x00007f885b3f269b raise (libc.so.6)
                           #1  0x00007f885b3f44a0 abort (libc.so.6)
                           #2  0x00007f885b4388e1 __libc_message (libc.so.6)
                           #3  0x00007f885b4dfaa7 __fortify_fail (libc.so.6)
                           #4  0x00007f885b4dfa70 __stack_chk_fail (libc.so.6)
                           #5  0x0000000000400599 f (test_stack_protector)
                           #6  0x00000000004005bd main (test_stack_protector)
                           #7  0x00007f885b3dc50a __libc_start_main (libc.so.6)
                           #8  0x000000000040049a _start (test_stack_protector)
Oct 16 20:57:59 example.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-coredump@21-17646-0 comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
Oct 16 20:58:00 example.org abrt-notification[17696]: Process 17645 (test_stack_protector) crashed in __fortify_fail()

Это означает , что вы получите вход от auditdаудита демона и в systemd-coredumpаварии обработчика.

Чтобы проверить, настроен ли демон обработки сбоев, вы можете проверить /proc, например:

$ cat /proc/sys/kernel/core_pattern
|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %e

(все проверено на Fedora 26, x86-64)

maxschlepzig
источник
1
Я очень рад, что вы опубликовали этот пример. Канарские острова устанавливаются GCC. (Пожалуйста, исправьте меня, если я ошибаюсь) Я предполагаю, что происходит что-то вроде: gcc помещает «дополнительный код» в программу для реализации канареечной функциональности; во время выполнения и перед возвратом функции проверяется значение; при загрязнении программа выдаст сообщение «Обнаружено разрушение стека» и выдаст ошибку. Эта ошибка обнаруживается ОС, распознает ошибку сегментации и печатает отправленную вами обратную трассировку и карту памяти. Наконец, ОС убивает приложение, генерирует дамп ядра и регистрирует в
системном
@aedcv, это в значительной степени история, если быть более точным: стек, проверяющий вызовы кода, abort()которые выдают сигнал прерывания, то есть не происходит ошибки сегментации. Просто обработчики сигналов по умолчанию для сбоя / сбоя сегментации и т. Д. Дают одно и то же действие: записать ядро ​​и выйти из процесса с неравным нулем состояния выхода, которое также кодирует номер сигнала. Запись ядра выполняется ядром, а его поведение настраивается с помощью /proc/.../core_pattern. В приведенном выше примере вспомогательный пользовательский пространства настроен и, таким образом, вызывается. Ядро также запускает одитинг.
maxschlepzig
@maxschlepzig это не совсем abort(), код SSP использует __builtin_trap()(но эффект тот же).
Стивен Китт,
1
@StephenKitt, взгляните на трассировку стека в приведенном выше примере. Там вы четко видите, как abort()называется.
maxschlepzig
1
@maxschlepzig Да, конечно, но это деталь реализации (код GCC используется, __builtin_trap()чтобы избежать явной зависимости от abort()). Другие дистрибутивы имеют разные трассировки стека.
Стивен Китт