6.3. Dépassement de capacité

Les dépassements de capacité ("Buffer Overflows") existent depuis les débuts de l'architecture de Von-Neuman 1. Ils gagnèrent une grande notoriété en 1988 avec le ver pour Internet de Morris. Malheureusement, la même attaque basique reste effective aujourd'hui. Des 17 rapports de sécurité du CERT de 1999, 10 furent causés directement des bogues logiciels de dépassement de capacité. De loin la plus commune de types d'attaques par dépassement de capacité est basée sur la corruption de la pile.

La plupart des systèmes informatiques modernes utilise une pile pour passer les arguments aux procédures et stocker les variables locales Une pile est une zone mémoire dernier entré-premier sorti (Last In-First Out : LIFO) dans la zone de mémoire haute de l'image d'un processus. Quand un programme invoque une fonction une nouvelle structure pile est créée. Cette structure pile consiste dans les arguments passés à la fonction aussi bien que dans une quantité dynamique d'espace pour la variable locale. Le pointeur de pile est un registre qui référence la position courante du sommet de la pile. Etant donné que cette valeur change constamment au fur et à mesure que de nouvelles valeurs sont ajoutées au sommet de la pile, beaucoup d'implémentations fournissent aussi un pointeur de structure qui est positionné dans le voisinage du début de la structure pile de façon à ce que les variables locales soient plus facilement adressables relativement à cette valeur. 1 L'adresse de retour des appels de fonction est aussi stocké dans la pile, et cela est la cause des découvertes des dépassements de pile puisque faire déborder une variable locale dans une fonction peut écraser l'adresse de retour de cette fonction, permettant potentiellement à un utilisateur malveillant d'exécuter le code qu'il ou elle désire.

Bien que les attaques basées sur les dépassements de pile soient de loin les plus communes, il serait aussi possible de faire exploser la pile avec une attaque du tas (malloc/free).

Le langage de programmation C ne réalise pas de vérifications automatiques des limites sur les tableaux ou pointeurs comme d'autres langages le font. De plus, la librairie standard du C est remplie d'une poignée de fonctions très dangereuses.

strcpy(char *dest, const char *src)

Peut faire déborder le tampon dest

strcat(char *dest, const char *src)

Peut faire déborder le tampon dest

getwd(char *buf)

Peut faire déborder le tampon buf

gets(char *s)

Peut faire déborder le tampon s

[vf]scanf(const char *format, ...)

Peut faire déborder ses arguments.

realpath(char *path, char resolved_path[])

Peut faire déborder le tampon path

[v]sprintf(char *str, const char *format, ...)

Peut faire déborder le tampon str.

6.3.1. Exemple de dépassement de capacité

L'exemple de code suivant contient un dépassement de capacité conçu pour écraser l'adresse de retour et "sauter" l'instruction suivant immédiatement l'appel de la fonction. (Inspiré par 4)

#include <stdio.h>

void manipulate(char *buffer) {
  char newbuffer[80];
  strcpy(newbuffer,buffer);
}

int main() {
  char ch,buffer[4096];
  int i=0;

  while ((buffer[i++] = getchar()) != '\n') {};

  i=1;
  manipulate(buffer);
  i=2;
  printf("La valeur de i est : %d\n",i);
  return 0;
}

Examinons quel serait l'aspect de l'image mémoire de ce processus si nous avions entré 160 espaces dans notre petit programme avant d'appuyer sur Entrée.

[XXX Schéma ici!]

Evidemment une entrée plus malveillante pourrait être imaginée pour exécuter des instructions déjà compilées (comme exec(/bin/sh)).

6.3.2. Eviter les dépassements de capacité

La solution la plus simple au problème de débordement de pile est de toujours utiliser de la mémoire restreinte en taille et les fonctions de copie de chaîne de caractères. strncpy et strncat font parties de la libraire standard du C. Ces fonctions acceptent une valeur de longueur comme paramètre qui qui ne devrait pas être plus grande que la taille du tampon de destination. Ces fonctions vont ensuite copier `taille' octets de la source vers la destination. Toutefois, il y a un certain nombre de problèmes avec ces fonctions. Aucune fonction ne garantit une terminaison par le caractère NULL si la taille du tampon d'entrée est aussi grand que celui de destination. Le paramètre taille est aussi utilisé de façon illogique entre strncpy et strncat aussi il est facile pour les programmeurs d'être déroutés sur leur utilisation convenable. Il y a aussi une perte significative des performances comparé à strcpy lorsque l'on copie une courte chaîne dans un grand tampon puisque strncpy remplit de caractères NULL jusqu'à la taille spécifiée.

Dans OpenBSD, une autre implémentation de la copie de mémoire a été créée pour contourner ces problèmes. Les fonctions strlcpy et strlcat garantissent qu'elles termineront toujours le tampon de destination par un caractère NULL losque l'argument de taille est différent de zéro. Pour plus d'informations sur ces fonctions, voir 6. Les fonctions strlcpy et strlcat d'OpenBSD ont été inclues dans FreeBSD depuis la version 3.5.

6.3.2.1. V#233;rifications des limites en fonctionnement basées sur le compilateur

Malheureusement il y a toujours un très important assortiment de code en utilisation publique qui copie aveuglément la mémoire sans utiliser une des routines de copie limitée dont nous venons juste de discuter. Heureusement, il y a une autre solution. Plusieurs produits complémentaires pour compilateur et librairies existent pour faire de la vérification de limites pendant le fonctionnement en C/C++.

StackGuard est un de ces produits qui est implémenté comme un petit correctif ("patch") pour le générateur de code gcc. Extrait du site Internet de StackGuard, http://immunix.org/stackguard.html :

"StackGuard détecte et fait échouer les attaques par débordement de pile en empêchant l'adresse de retour sur la pile d'être altérée. StackGuard place un mot "canari" [1] à côté de l'adresse de retour quand la fontion est appelée. Si le mot "canari" a été altéré au retour de la fonction, alors une attaque par débordement de pile a été tentée et le programme répond en envoyant une alerte d'intrusion dans la trace système (syslog) et s'arrête alors."

"StackGuard est implémenté comme un petit correctif au générateur de code gcc, spécifiquement sur les routines function_prolog() et function_epilog(). function_prolog() a été amélioré pour laisser des "canaris" sur la pile quand les fonctions démarrent, et function_epilog vérifie l'intégrité des "canaris" quand la fonction se termine. Tout essai pour corrompre l'adresse de retour est alors détectée avant que la fonction ne retourne."



Recompiler votre application avec StackGuard est un moyen efficace pour stopper la plupart des attques par dépassement de capacité, mais cela peut toujours être compromis.

6.3.2.2. Vérifications des limites en fonctionnement basées sur les librairies

Les mécanismes basés sur le compilateur sont totalement inutiles pour logiciel seulement binaire que vous ne pouvez recompiler. Pour ces situations, il existe un nombre de librairies qui re-implémente les fonctions peu sûres de la librairie C (strcpy, fscanf, getwd, etc..) et assurent que ces fonctions ne peuvent pas écrire plus loin que le pointeur de pile.

  • libsafe

  • libverify

  • libparnoia

Malheureusement ces défenses basées sur les librairies possèdent un certain nombre de défauts. Ces librairies protègent seulement d'un très petit ensemble de problèmes liés à la sécurité et oublient de réparer le problème actuel. Ces défenses peuvent échouer si l'application a été compilée avec -fomit-frame-pointer. De même, les variables d'environnement LD_PRELOAD et LD_LIBRARY_PATH peuvent être réécrites/non définies par l'utilisateur.

Notes

[1]

NDT : Jaune de préférence pour être bien visible

Ce document, ainsi que d'autres peut être téléchargé sur ftp.FreeBSD.org/pub/FreeBSD/doc/.

Pour toutes questions à propos de FreeBSD, lisez la documentation avant de contacter <questions@FreeBSD.org>.
Pour les questions sur cette documentation, contactez <doc@FreeBSD.org>.