Comment se prémunir contre certains débordements de pile
Date : 27 Juillet 2005
Comment se prémunir contre certains débordements de pile
Pour lutter contre les débordements de pile, et prévenir ceux qui ne sont pas encore découverts, et donc pas encore corrigés, il existe, selon les OS, différentes possibilités. Après avoir rappelé celle proposée pour les systèmes SOLARIS, qui est référencée dans plusieurs avis du Cert-IST, nous développons la solution proposée par Lucent pour les systèmes Linux.
I. Solaris 2.6 et supérieur
Solaris propose une alternative par le biais du noyau (kernel). Il est possible de rajouter deux options dans le fichier /etc/system pour rendre l'information contenue dans la pile "non exécutable" (non_exec_stack).
Sous Solaris 2.6 et 7, mettre dans /etc/system :
set noexec_user_stack=1
set noexec_user_stack_log=1
et rebooter le système.
Ainsi, toute procédure qui aurait été - malicieusement - stockée dans la pile n'est plus accessible par débordement.
Toutefois, cette solution présente ses limites : il existe différents moyens assez complexes pour contourner cette protection et forcer l'exécution de la pile. Néanmoins, les exploitations des vulnérabilités sont rarement codées avec cette optique, et la fonctionnalité reste très intéressante.
II. Linux
Pour les systèmes Linux, Bell-Labs propose l’alternative "Libsafe". Il s'agit d'une librairie pour systèmes linux, distribuée sous licence GPL permettant de prévenir et d'arrêter les débordements de pile avant leur exécution.
II.1. Présentation
Libsafe se présente comme une librairie dynamique (type .so), permettant une interface directe avec les programmes lors de leur exécution, sans que ceux-ci aient besoin d'être recompilés. A chaque exécution, la librairie est automatiquement chargée par le système.
Plusieurs fonctions C traditionnellement dangereuses sont ainsi réécrites, et masquent les fonctions originales. En voici une liste exhaustive pour la version 1.3 que nous avons testée :
- strcpy(char *dest, const char *src),
- strcat(char *dest, const char *src),
- getwd(char *buf),
- gets(char *s),
- [vf]scanf(const char *format, ...),
- realpath(char *path, char resolved_path[]),
- [v]sprintf(char *str, const char *format, ...)
Ces fonctions, très souvent utilisées, ne sont pourtant pas les seules susceptibles de produire, dans certaines conditions, un débordement de pile : si les arguments ne sont pas contrôlés avec attention, c'est aussi le cas des fonctions memset(), bzero(), ... De ce fait, la librairie ne peut donc pas prévenir TOUS les débordements.
II.2. Exemples
A titre d'exemple, nous avons testé l'efficacité de la librairie face à deux débordements très simples.
II.2.1 test sur strcpy()
Considérons le programme source test1.c suivant :
main()
{
char tampon[10];
strcpy(tampon,
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
printf("Si j'affiche cette ligne, je n'ai pas complètement réussi le
débordement\n");
}
Compilation :
gcc -g -o test1 test1.c
II.2.1.1. Exécution sans Libsafe:
# ./test1
Segmentation fault (core dumped)
#
# gdb ./test1 ./core
GNU gdb 4.17.0.11 with Linux support
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
Core was generated by `./t'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0 0x41414141 in ?? ()
On retrouve bien 0x41 = 65 = 'A', cqfd
II.2.1.2. Exécution avec Libsafe
Installation de libsafe :
- création/édition du fichier /etc/ld.so.preload et rajouter la ligne suivante:
[replibsafe]/libsafe.so.1
- Lancer le programme ldconfig pour recharger les librairies dynamiques :
# ldconfig
Test du débordement de test1 :
# ./test1
Detected an attempt to write across stack boundary.
Terminating /home/sps/test1.
#
Libsafe permet de journaliser les débordements des fonctions qu'il wrappe via syslog. Ci dessous, l'extrait généré par le test précédent :
authpriv.log:Jun 7 15:56:00 machine.test libsafe.so[9887]: version 1.3
authpriv.log:Jun 7 15:56:00 machine.test libsafe.so[9887]: detected an
attempt to write across stack boundary.
authpriv.log:Jun 7 15:56:00 machine.test libsafe.so[9887]: terminating
/home/sps/t
authpriv.log:Jun 7 15:56:00 machine.test libsafe.so[9887]: overflow caused
by strcpy()
II.2.2 test sur memset :
Considérons le programme source test2.c suivant :
main()
{
char tampon[10];
memset(tampon, 'A', 100);
printf("Si j'affiche cette ligne, je n'ai pas complètement réussi le
débordement\n");
}
Compilation :
gcc -g -o test2 test2.c
II.2.2.1. Exécution sans Libsafe
# ./test2
Segmentation fault (core dumped)
#
# gdb ./test2 ./core
GNU gdb 4.17.0.11 with Linux support
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
Core was generated by `./t'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0 0x41414141 in ?? ()
On retrouve bien 0x41 = 65 = 'A', cqfd
II.2.2.2 Exécution avec Libsafe
# ./test2
Segmentation fault (core dumped)
#
# gdb ./test2 ./core
GNU gdb 4.17.0.11 with Linux support
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
Core was generated by `./t'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0 0x41414141 in ?? ()
Libsafe n'a pas évité le débordement - fonction memset non wrappée.
II.3. Conclusion
Libsafe est un moyen simple de contenir des débordements de pile simples et classiques, sur les fonctions les plus généralement mal utilisées, mais n' intervenient pas sur certains débordements plus fins.
Bien que cette librairie augmente indiscutablement le niveau de sécurité, ne serait-ce que par la journalisation d'un débordement arrêté, elle ne permet pas de prévenir toutes les attaques.
L'outil reste tout de même intéressant, et est applicable à d'autres contextes que celui de la sécurité (développement par exemple).
Il est intéressant de noter également que la distribution sous licence GPL autorise la modification du code source pour usage local, et qu'il est facilement possible d'adapter l'outil à ses besoins et de le faire évoluer.
Plus d'informations, et téléchargement : voir www.bell-labs.com/org/11356/libsafe.html
NB: Le Cert-IST envisage de proposer, en collaboration avec le Cert-A, des compléments à cette librairie.