Comment les pollutions des canaris de pile sont-elles enregistrées?


11

L'indicateur GCC -fstack-protector permet d'utiliser des canaris de pile pour la protection contre les débordements de pile. L'utilisation de ce drapeau par défaut a été plus importante ces dernières années.

Si un paquet est compilé avec -fstack-protector, et que nous débordons un tampon dans le programme, nous aurons probablement une erreur telle que:

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

Cependant, "qui" est responsable de ces messages d'erreur? Où ces messages sont-ils enregistrés? Le démon syslog récupère-t-il ces messages?

Réponses:


10

Le bris de pile est détecté par libssp, qui fait partie de gcc. Il essaie très fort de sortir le message sur un terminal, et seulement si cela échoue, il se connecte au journal système - donc en pratique, vous verrez des messages de dépassement de tampon dans les journaux des démons et peut-être des applications GUI.

Une fois qu'il a sorti son message, libsspessaie diverses façons de quitter, y compris le plantage de l'application; cela pourrait être détecté par l'un des enregistreurs de sortie anormaux, mais ce n'est pas garanti.


1
Permettez-moi de vous présenter un exemple concret pour explorer davantage cette explication. Choisissons nginx pour cet exemple. J'ai compilé nginx avec des canaris de pile. Lorsque j'exécute nginx, il démarre un processus mais ne produit rien dans le shell. Au lieu de cela, tous les messages sont enregistrés dans ses multiples fichiers journaux. Si nginx détecte un écrasement de pile, libsspaffichera son message par la sortie stderr utilisée par nginx. Ensuite, libsspessayez de quitter le processus (ou le processus enfant pour nginx). S'il "n'a pas besoin" de planter l'application, alors les enregistreurs de sortie anormaux ne le prendront pas. Est-ce une interprétation correcte?
aedcv

Pas tout à fait - il va essayer de planter l'application, en utilisant d' __builtin_trap()abord, puis , si cela ne fonctionne pas, en essayant de provoquer une violation du segment, et seulement si cela échoue, la sortie avec le statut 127.
Stephen Kitt

La partie impression des messages n'a pas de meilleures garanties de succès que la sortie via une méthode de rendement de base (par exemple abort()).
maxschlepzig

7

Les distributions Linux modernes comme CentOS / Fedora mettent en place un démon de gestion des plantages (par exemple systemd-coredumpou abortd), par défaut.

Ainsi, lorsque votre programme se termine de façon anormale (erreur de segmentation, exception non interceptée, abandon, instruction illégale, etc.), cet événement est enregistré et enregistré par ce démon. Ainsi, vous trouverez des messages dans le journal système et éventuellement une référence à un répertoire avec quelques détails supplémentaires (par exemple fichier core, logs, etc.).

Exemple

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

Compiler:

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

Exécuter:

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

L'état de sortie est 134, soit 128 + 6, c'est-à-dire 128 plus le numéro de signal d'abandon.

Le journal système:

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

Cela signifie que vous obtenez la journalisation à partir du auditddémon d'audit et du systemd-coredumpgestionnaire de plantage.

Pour vérifier si un démon de gestion des plantages est configuré, vous pouvez vérifier /proc, par exemple:

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

(tout testé sur Fedora 26, x86-64)


1
Je suis très heureux que vous ayez publié cet exemple. Les canaris sont mis en place par gcc. (Veuillez me corriger si je me trompe) Je suppose que ce qui se passe est quelque chose comme: gcc met du "code supplémentaire" dans le programme pour implémenter la fonctionnalité canary; pendant l'exécution et avant le retour d'une fonction, la valeur est vérifiée; s'il est pollué, le programme affichera le message "smashing de pile détecté" et génère une erreur. Cette erreur est détectée par le système d'exploitation, reconnaît une erreur de segmentation et imprime la trace et la carte mémoire que vous avez publiées. Enfin, le système d'exploitation tue l'application, génère un vidage de
mémoire

@aedcv, c'est à peu près l'histoire - pour être plus précis: la pile écrasant les appels de code de vérification abort()qui produit un signal d'abandon, c'est-à-dire qu'il n'y a pas de défaut de segmentation en cours. C'est juste que les gestionnaires de signaux par défaut pour les défauts d'abandon / de segmentation, etc. produisent la même action: écrire le cœur et quitter le processus avec un état de sortie zéro égal qui code également le numéro du signal. L'écriture du noyau est effectuée par le noyau et son comportement est configurable via /proc/.../core_pattern. Dans l'exemple ci-dessus, un assistant d'espace utilisateur est configuré et donc appelé. Le noyau déclenche également l'audit.
maxschlepzig

@maxschlepzig ce n'est pas tout à fait abort(), le code SSP utilise __builtin_trap()(mais l'effet est le même).
Stephen Kitt

1
@StephenKitt, regardez la trace de la pile dans l'exemple ci-dessus. Là, vous voyez clairement comment abort()s'appelle.
maxschlepzig

1
@maxschlepzig oui, bien sûr, mais c'est un détail d'implémentation (le code GCC utilise __builtin_trap()pour éviter d'avoir une dépendance explicite abort()). D'autres distributions ont des traces de pile différentes.
Stephen Kitt
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.