Jak są rejestrowane zanieczyszczenia kanarek stosu?

11

Flaga Flaga GCC -fstack-protector umożliwia użycie kanarek stosu do ochrony przed przepełnieniem stosu. Domyślnie użycie tej flagi było bardziej widoczne w ostatnich latach.

Jeśli pakiet zostanie skompilowany z opcją -fstack-protector i przepełnimy bufor w programie, prawdopodobnie wystąpi błąd, taki jak:

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

Jednak „kto” odpowiada za te komunikaty o błędach? Gdzie są rejestrowane te wiadomości? Czy demon syslog wybiera te wiadomości?

aedcv
źródło

Odpowiedzi:

10

Stos Smashing wykrywa libssp, który jest częścią gcc. To bardzo się stara , aby wyjście wiadomość do terminalu, i tylko wtedy, gdy nie ma on zalogować się do dziennika systemu - tak w praktyce zobaczysz przepełnienie bufora komunikatów w dziennikach o demonach i aplikacji może GUI.

Po wyświetleniu komunikatu libssppróbuje wypróbować różne sposoby wyjścia, w tym awarię aplikacji; może to zostać złapane przez jeden z nienormalnych rejestratorów wyjścia, ale nie jest to gwarantowane.

Stephen Kitt
źródło
1
Pozwolę sobie podać konkretny przykład jako sposób na dalsze zbadanie tego wyjaśnienia. Wybierzmy nginx dla tego przykładu. Skompilowałem nginx z kanarkami stosowymi. Kiedy uruchamiam nginx, uruchamia proces, ale nic nie wysyła do powłoki. Zamiast tego wszelkie wiadomości są rejestrowane w wielu plikach dziennika. Jeśli nginx wykryje rozbicie stosu, libsspwyśle ​​swój komunikat przez wyjście stderr używane przez nginx. Następnie libsspmoże spróbować zakończyć proces (lub proces potomny dla nginx). Jeśli aplikacja „nie musi” zawieszać aplikacji, nieprawidłowe rejestratory wyjścia nie będą tego odbierać. Czy to poprawna interpretacja?
aedcv
Niezupełnie - to będzie próbować rozbić aplikację, używając __builtin_trap()najpierw, a następnie, jeśli to zawiedzie, próbuje sprowokować naruszenie segment i tylko wtedy, gdy nie powiedzie się, wychodzenia ze stanu 127.
Stephen Kitt
Drukowanie części wiadomości nie ma lepszych gwarancji sukcesu niż wyjście za pomocą podstawowej metody generowania (np abort().).
maxschlepzig
7

Nowoczesne dystrybucje Linuksa, takie jak CentOS / Fedora , domyślnie konfigurują demona do obsługi awarii (np. systemd-coredumpLub abortd).

Tak więc, gdy twój program kończy się w nienormalny sposób (segfault, nieprzechwycony wyjątek, przerwanie, nielegalne instrukcje itp.) To zdarzenie jest rejestrowane i rejestrowane przez tego demona. W związku z tym w dzienniku systemowym znajdują się pewne komunikaty i ewentualnie odniesienie do katalogu z dodatkowymi szczegółami (np. Plik podstawowy, dzienniki itp.).

Przykład

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

Skompilować:

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

Wykonać:

$ ./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'

Status wyjścia ma wartość 134, czyli 128 + 6, tj. 128 plus numer sygnału przerwania.

Dziennik systemu:

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()

Oznacza to, że logujesz się od auditddemona kontroli i programu systemd-coredumpobsługi awarii.

Aby sprawdzić, czy skonfigurowano demona do obsługi awarii, możesz sprawdzić /proc, np .:

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

(wszystko testowane na Fedorze 26, x86-64)

maxschlepzig
źródło
1
Bardzo się cieszę, że opublikowałeś ten przykład. Kanarki są wprowadzane przez gcc. (Proszę, popraw mnie, jeśli się mylę). Zakładam, że tak się dzieje: gcc umieszcza w programie „dodatkowy kod” w celu zaimplementowania funkcji kanarka; podczas wykonywania i przed powrotem funkcji sprawdzana jest wartość; jeśli zostanie zanieczyszczony, program wyświetli komunikat „wykryto zniszczenie stosu” i zgłosi błąd. Ten błąd jest wykrywany przez system operacyjny, rozpoznaje błąd segmentacji i drukuje przesłany ślad i mapę pamięci. Na koniec OS zabija aplikację, generuje zrzut
pamięci
@aedcv, jest to właściwie historia - a ściślej: rozbijanie stosu sprawdzanie wywołań kodu, abort()co daje sygnał przerwania, tzn. nie występuje błąd segmentacji. Po prostu domyślne procedury obsługi sygnałów dla błędu przerwania / segmentacji itp. Dają tę samą akcję: zapisz rdzeń i wyjdź z procesu z nierównym zerowym statusem wyjścia, który również koduje numer sygnału. Pisanie rdzenia odbywa się przez jądro, a jego zachowanie można skonfigurować za pomocą /proc/.../core_pattern. W powyższym przykładzie pomocnik w przestrzeni użytkownika jest skonfigurowany i wywołany. Jądro uruchamia także inspekcję.
maxschlepzig
@maxschlepzig nie jest całkiem abort(), używa kodu SSP __builtin_trap()(ale efekt jest taki sam).
Stephen Kitt
1
@StephenKitt, no cóż, spójrz na ślad stosu w powyższym przykładzie. Tam wyraźnie widać, jak abort()się nazywa.
maxschlepzig
1
@maxschlepzig tak, oczywiście, ale jest to szczegół implementacji (kod GCC używa, __builtin_trap()aby uniknąć wyraźnej zależności abort()). Inne dystrybucje mają różne ślady stosu.
Stephen Kitt