Wie werden Stapelkanarienverschmutzungen protokolliert?

11

Das Flag GCC -fstack-protector ermöglicht die Verwendung von Stapelkanarien für den Stapelüberlaufschutz. Die standardmäßige Verwendung dieser Flagge war in den letzten Jahren stärker ausgeprägt.

Wenn ein Paket mit -fstack-protector kompiliert wird und ein Puffer im Programm überläuft, wird wahrscheinlich ein Fehler angezeigt, wie z.

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

"Wer" ist jedoch für diese Fehlermeldungen verantwortlich? Wo werden diese Nachrichten protokolliert? Wählt der Syslog-Daemon diese Nachrichten aus?

aedcv
quelle

Antworten:

10

Stack Smashing wird von erkannt libssp, was ein Teil von istgcc . Es ist sehr schwierig , die Nachricht an ein Terminal auszugeben, und nur wenn dies fehlschlägt, wird sie im Systemprotokoll protokolliert. In der Praxis werden in den Protokollen Pufferüberlaufmeldungen für Daemons und möglicherweise GUI-Anwendungen angezeigt.

Sobald die Nachricht ausgegeben wurde, werden libsspverschiedene Möglichkeiten zum Beenden versucht, einschließlich des Absturzes der Anwendung. Dies wird möglicherweise von einem der abnormalen Exit-Logger abgefangen, dies ist jedoch nicht garantiert.

Stephen Kitt
quelle
1
Lassen Sie mich ein konkretes Beispiel geben, um diese Erklärung weiter zu untersuchen. Wählen wir für dieses Beispiel Nginx aus. Ich habe Nginx mit Stapelkanarien kompiliert. Wenn ich nginx ausführe, wird ein Prozess gestartet, aber nichts an die Shell ausgegeben. Stattdessen werden alle Nachrichten in mehreren Protokolldateien protokolliert. Wenn nginx das Zerschlagen von Stapeln erkennt, libsspgibt es seine Nachricht über die von nginx verwendete stderr-Ausgabe aus. libsspVersuchen Sie dann möglicherweise, den Prozess (oder den untergeordneten Prozess für Nginx) zu beenden. Wenn die Anwendung nicht zum Absturz gebracht werden muss, werden abnormale Exit-Logger dies nicht erkennen. Ist dies eine korrekte Interpretation?
Aedcv
Nicht ganz - es wird versuchen, die Anwendung zum __builtin_trap()ersten Mal zum Absturz zu bringen. Wenn dies fehlschlägt, wird versucht, eine Segmentverletzung zu provozieren, und nur wenn dies fehlschlägt, wird der Status 127 beendet.
Stephen Kitt
Das Drucken des Nachrichtenteils hat keine besseren Erfolgsgarantien als das Beenden über eine Kernausbeutungsmethode (z abort(). B. ).
Maxschlepzig
7

Moderne Linux-Distributionen wie CentOS / Fedora richten standardmäßig einen Daemon für die Absturzbehandlung (z. B. systemd-coredumpoder abortd) ein.

Wenn Ihr Programm auf ungewöhnliche Weise beendet wird (Segfault, nicht erfasste Ausnahme, Abbruch, unzulässige Anweisung usw.), wird dieses Ereignis von diesem Daemon registriert und protokolliert. So finden Sie im Systemjournal einige Meldungen und möglicherweise einen Verweis auf ein Verzeichnis mit einigen zusätzlichen Details (z. B. Kerndatei, Protokolle usw.).

Beispiel

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

Kompilieren:

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

Ausführen:

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

Der Ausgangsstatus ist 134, was 128 + 6 ist, dh 128 plus der Abbruch-Signalnummer.

Das Systemjournal:

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

Das bedeutet, dass Sie die Protokollierung vom auditdÜberwachungsdämon und vom systemd-coredumpCrash-Handler erhalten.

Um zu überprüfen, ob ein Daemon für die Absturzbehandlung konfiguriert ist, können Sie Folgendes überprüfen /proc: z.

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

(alles getestet auf Fedora 26, x86-64)

maxschlepzig
quelle
1
Ich bin sehr froh, dass Sie dieses Beispiel gepostet haben. Die Kanarienvögel werden von gcc aufgestellt. (Bitte korrigieren Sie mich, wenn ich falsch liege.) Ich gehe davon aus, dass Folgendes passiert: gcc fügt "zusätzlichen Code" in das Programm ein, um die kanarische Funktionalität zu implementieren. Während der Ausführung und bevor eine Funktion zurückkehrt, wird der Wert überprüft. Bei Verschmutzung gibt das Programm die Meldung "Stack Smashing Detected" aus und löst einen Fehler aus. Dieser Fehler wird vom Betriebssystem erkannt, erkennt einen Segmentierungsfehler und druckt die von Ihnen veröffentlichte Rückverfolgung und Speicherzuordnung. Schließlich beendet das Betriebssystem die Anwendung, generiert einen Core-Dump und protokolliert im Sys-Journal
aedcv
@aedcv, das ist so ziemlich die Geschichte - um genau zu sein: Der Stapel zerschmettert Überprüfungscode-Aufrufe, abort()die ein Abbruchsignal ergeben, dh es liegt kein Segmentierungsfehler vor. Es ist nur so, dass die Standard-Signalhandler für Abbruch- / Segmentierungsfehler usw. dieselbe Aktion ausführen: Schreiben Sie den Kern und beenden Sie den Prozess mit einem Exit-Status ungleich Null, der auch die Signalnummer codiert. Das Kernschreiben wird vom Kernel ausgeführt und sein Verhalten kann über konfiguriert werden /proc/.../core_pattern. Im obigen Beispiel wird ein User-Space-Helfer konfiguriert und somit aufgerufen. Der Kernel löst auch die Überwachung aus.
Maxschlepzig
@maxschlepzig es ist nicht ganz abort(), der SSP-Code verwendet __builtin_trap()(aber der Effekt ist der gleiche).
Stephen Kitt
1
@StephenKitt, sehen Sie sich die Stapelverfolgung im obigen Beispiel an. Dort sieht man deutlich, wie abort()es heißt.
Maxschlepzig
1
@maxschlepzig ja natürlich, aber das ist ein Implementierungsdetail (GCC-Code wird verwendet __builtin_trap(), um eine explizite Abhängigkeit von zu vermeiden abort()). Andere Distributionen haben andere Stapelspuren.
Stephen Kitt