Come vengono registrati gli inquinamenti delle canarie dello stack?


11

Il flag GCC -fstack-protector flag consente l'utilizzo di canarini di stack per la protezione di overflow dello stack. L'uso di questo flag di default è stato più importante negli ultimi anni.

Se un pacchetto viene compilato con -fstack-protector e trabocciamo di un buffer nel programma, è probabile che si verifichi un errore come:

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

Tuttavia, "chi" è responsabile di questi messaggi di errore? Dove vengono registrati questi messaggi? Il demone syslog seleziona questi messaggi?

Risposte:


10

Lo smashing dello stack viene rilevato da libssp, di cui fa parte gcc. Si sforza molto di inviare il messaggio a un terminale e solo se fallisce si registra nel registro di sistema, quindi in pratica vedrai i messaggi di overflow del buffer nei registri per i demoni e forse le applicazioni della GUI.

Una volta emesso il suo messaggio, libsspprova a uscire in vari modi, incluso il crash dell'applicazione; questo potrebbe essere rilevato da uno dei logger di uscita anomali, ma ciò non è garantito.


1
Consentitemi di dare un esempio concreto come modo per esplorare ulteriormente questa spiegazione. Scegliamo nginx per questo esempio. Ho compilato nginx con stack canarini. Quando eseguo nginx, avvia un processo ma non genera nulla sulla shell. Invece, tutti i messaggi vengono registrati nei suoi file di registro multipli. Se nginx rileva lo smashing dello stack, libsspgenererà il suo messaggio dall'output stderr utilizzato da nginx. Quindi, libssppotrebbe tentare di uscire dal processo (o processo figlio per nginx). Se "non è necessario" arrestare in modo anomalo l'applicazione, i logger di uscita anomali non lo rileveranno. È un'interpretazione corretta?
aedcv,

Non proprio - si sarà provare mandare in crash l'applicazione, utilizzando __builtin_trap(), poi se non funziona, cercando di provocare una violazione segmento, e solo se non funziona, uscendo con lo status di 127.
Stephen Kitt

La stampa della parte dei messaggi non ha migliori garanzie di successo rispetto all'uscita tramite un metodo di rendimento principale (ad es abort().).
maxschlepzig,

7

Le moderne distribuzioni Linux come CentOS / Fedora hanno impostato un demone di gestione degli arresti anomali (ad esempio systemd-coredumpo abortd), per impostazione predefinita.

Pertanto, quando il programma termina in modo anomalo (segfault, eccezione non rilevata, interruzione, istruzione illegale ecc.), Questo evento viene registrato e registrato da quel demone. Pertanto, nel diario di sistema sono presenti alcuni messaggi e possibilmente un riferimento a una directory con alcuni dettagli aggiuntivi (ad es. File core, registri, ecc.).

Esempio

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

Compilare:

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

Eseguire:

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

Lo stato di uscita è 134, ovvero 128 + 6, ovvero 128 più il numero del segnale di interruzione.

Il diario di sistema:

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

Ciò significa che si ottiene la registrazione dal auditddaemon di controllo e dal systemd-coredumpgestore dell'arresto anomalo.

Per verificare se è configurato un daemon di gestione degli arresti anomali, è possibile verificare /proc, ad esempio:

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

(tutto testato su Fedora 26, x86-64)


1
Sono molto contento che tu abbia pubblicato questo esempio. I canarini sono messi in atto da gcc. (Per favore, correggimi se sbaglio) Presumo che ciò che accade sia qualcosa di simile: gcc inserisce "codice extra" nel programma per implementare la funzionalità canaria; durante l'esecuzione e prima che ritorni una funzione, viene verificato il valore; se inquinato, il programma produrrà il messaggio "stack smashing rilevato" e genererà un errore. Questo errore viene rilevato dal sistema operativo, riconosce un errore di segmentazione e stampa la backtrace e la mappa di memoria postata. Infine, il sistema operativo uccide l'applicazione, genera un dump principale e accede al diario di sistema
aedcv,

@aedcv, questa è praticamente la storia - per essere più precisi: lo stack distrugge il controllo delle chiamate di codice abort()che produce un segnale di interruzione, cioè non c'è nessun errore di segmentazione in corso. È solo che i gestori di segnale predefiniti per errore di interruzione / segmentazione ecc. Producono la stessa azione: scrivere core ed uscire dal processo con uno stato di uscita uguale a zero che codifica anche il numero del segnale. La scrittura principale viene eseguita dal kernel e il suo comportamento è configurabile tramite /proc/.../core_pattern. Nell'esempio sopra riportato viene configurato un helper dello spazio utente e quindi chiamato. Il kernel attiva anche il controllo.
maxschlepzig,

@maxschlepzig non è del tutto abort(), usa il codice SSP __builtin_trap()(ma l'effetto è lo stesso).
Stephen Kitt,

1
@StephenKitt, beh, dai un'occhiata alla traccia dello stack nell'esempio sopra. Lì vedi chiaramente come abort()viene chiamato.
maxschlepzig,

1
@maxschlepzig sì, certo, ma questo è un dettaglio di implementazione (il codice GCC usa __builtin_trap()per evitare di avere una dipendenza esplicita abort()). Altre distribuzioni hanno tracce di stack diverse.
Stephen Kitt,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.