• Aucun résultat trouvé

Exploitations de l’allocateur dynamique de mémoire

3.1 Correction d’erreurs – Trous de sécurité

3.1.1 Exploitations de l’allocateur dynamique de mémoire

Les systèmes Unix organisent la mémoire des processus utilisateurs en trois parties. La zone « .text » qui contient le code exécutable, la pile qui maintient les informa-tions nécessaire à l’exécution du code, et le tas qui permet aux utilisateurs de stocker les variables du programme. La quantité de données traitée par un programme étant variable au cours de l’exécution d’un programme, les systèmes Unix implémentent un appel système nommé brk qui permet de redimensionner la taille du tas. Cet appel sys-tème ne fournit aucun moyen de suivre l’utilisation mémoire du processus et ne permet pas de connaître les zones mémoires utilisées et celles libres. La norme POSIX a défini une interface standard pour l’allocation dynamique de mémoire. L’implémentation de ce standard pour le C fournie par la librairie standard C, propose un allocateur de mémoire dynamique. Cet allocateur de mémoire utilise l’appel système brk pour al-louer de grands fragments de mémoire qu’il partage ensuite en plus petits fragments. Le développeur dispose d’une interface (fonctions malloc, free, etc) lui permettant ainsi de manipuler facilement et à grain fin le contenu du tas.

Les performances d’un allocateur dynamique de mémoire s’évaluent sur trois cri-tères : la complexité des algorithmes d’allocations et de libérations de la mémoire, le rapport de grandeur entre la mémoire totale disponible et la mémoire utile, et le taux de fragmentation de la mémoire. Ces critères ont déterminé les choix architecturaux de l’allocateur de mémoire de la librairie standard C au détriment de la sécurité.

La librairie standard C organise la mémoire en deux listes doublement chaînées : la première liste tous les blocs mémoires et la seconde uniquement les blocs libres. Afin de maximiser la mémoire utile, les données nécessaires au fonctionnement de l’alloca-teur (listes des fragments utilisés/inutilisés) sont stockées sur le tas avec les données du programme. Le listing 3.1 montre l’organisation des fragments mémoires. Chaque fragment contient sa taille et celle du fragment précédent (ce sont des tailles totales et

t y p e d e f s t r u c t chunk {

u i n t 3 2 _ t p r e v _ s i z e ; // size of p r e v i o u s a d j a c e n t c h u n k 3 u i n t 3 2 _ t size ; // size of the c h u n k

u i n t 3 2 _ t * next ; // next free c h u n k u i n t 3 2 _ t * prev ; // p r e v i o u s free c h u n k } c h u n k _ t ;

8 v o i d* a l l o c a t e ( c h u n k _ t * c ) { c - > prev - > next = c - > next ; c - > next - > prev = c - > prev ; r e t u r n &( c - > next ); 13 } /* p is the free c h u n k s a f t e r w h i c h to i n s e r t c */ v o i d free ( c h u n k _ t * c , c h u n k _ t * p ) { c - > next = p - > next ; 18 c - > prev = p ; p - > next - > prev = c ; p - > next = c ; }

Listing 3.1: Définition des listes doublement chaînées utilisées par la librairie stantard C pour l’allocation dynamique de mémoire

non utiles), comme les fragments sont consécutifs en mémoire, ces deux informations permettent de parcourir la liste des fragments dans les deux sens à partir de n’im-porte quel fragment. Lorsque le fragment est alloué, le reste du fragment contient la mémoire utile pour l’utilisateur, sinon, le fragment contient également les adresses du fragment libre suivant et du fragment libre précédent. Le listing 3.1 présente également les routines réalisant le maintien des listes chaînées lors de l’allocation et la libération de fragments.

La librairie standard C ne vérifie pas lors de la libération d’un fragment que celui-ci était alloué. Ce comportement peut être exploité à des fins malicieuses. La figure 3.4(a) montre une portion de la mémoire, on y voit cinq fragments, deux sont libres : P et S. Lorsque que le fragment F est libéré, celui-ci est chaîné entre les fragments P et S (figure 3.4(b)). En forçant le programme à libérer le fragment F une seconde fois (figure 3.4(c)), l’allocateur va à nouveau l’insérer dans la liste entre P et S. Or comme F se trouve déjà entre P et S, les éléments next et prev de F vont pointer sur lui-même (listing 3.1 lignes 17 et 19). Comme F suit toujours P , il est toujours possible d’allouer F . Mais comme les éléments next et prev de F pointent sur lui-même, l’allocation de F laisse la liste des fragments libres inchangées et retourne l’adresse de l’élément next de F (figure 3.4(d)). Si le fragment F est destiné à recevoir une entrée clavier ou réseau, un utilisateur malveillant peut facilement écraser next avec l’adresse d’une charge virale stockée dans la suite de l’entrée, et prev par une adresse sur la pile pointant l’adresse de retour d’une fonction (figure 3.4(e)). En forçant le programme à allouer de nouveau le fragment F (figure 3.4(f)), la librairie standard va écraser l’adresse de retour de la fonction avec l’adresse de la charge virale (listing 3.1 ligne 10). Il suffira alors à l’utilisateur malveillant d’attendre que cette fonction termine pour que la charge virale s’exécute.

Ce comportement est une faille de sécurité importante qui a été largement exploitée par le passé pour conduire des attaques de déni de service [AL03] ou pour prendre le contrôle de systèmes à distance [Des97]. Afin de se prémunir de ce type d’attaques, une implémentation sécurisée de la librairie standard C peut être choisie lors du chargement des processus. Néanmoins, l’expérience démontre que cette variante de l’allocateur dynamique de mémoire est nettement moins performante et rarement utilisée. Ainsi, un

(a) Cinq fragments consécutifs, le premier et le dernier sont libres

(b) Après libération du fragment F

(c) Après une seconde libération de F

(d) Après allocation du fragment F

(e) Préparation de la charge

(f) Une nouvelle allocation de F finit la mise en place de l’attaque Fig. 3.4: Réalisation d’une attaque de type « double free bug »

administrateur système faisant face à une telle faille de sécurité, devrait alors déployer une version corrigée de l’application fautive. Ceci nécessitant de stopper l’application. Bien que le cache Web Squid utilise son propre allocateur de mémoire pour des raisons de performances, celui-ci se trouve également vulnérable aux attaques par exploitation de « double free bug » [Ubu05]. La gestion dynamique de la mémoire est un aspect crosscuttant dans Squid, en effet, elle représente environ 71% du code de Squid. Ainsi, pour garantir la continuité de service d’une application dans le cas d’une faille de type « double free bug », tout en limitant l’impact sur les performances, nous pensons que la protection contre l’exploitation de cette vulnérabilité doit pouvoir être activée uniquement en cas de nécessité.

La correction d’une faille de sécurité comme le « double free bug » nécessite de modifier la manière dont un programme interagit avec une bibliothèque. Ici, ces interactions sont principalement des appels aux fonctions d’allocation (malloc,

calloc, etc) et de libération (free) mémoire.