Allocation sur la pile

En langage C, on peut utiliser la fonction alloca() pour allouer temporairement de la mémoire. Exemple :

char* bonjour(const char* nom)
{
   char *texte = alloca(8+strlen(nom)+1);
   sprintf(texte, "Bonjour %s", nom);
   return strdup(texte);
}

À partir du C99, on peut également utiliser la notation "type nom[taille];" où taille est un argument passé à la fonction :

char* bonjour(const char* nom)
{
   char texte[8+strlen(nom)+1];
   sprintf(texte, "Bonjour %s", nom);
   return strdup(texte);
}

Même question pour les deux exemples : que se passe-t-il si l'argument (nom) a une longueur supérieure à 1000 caractères ? Et si la longueur est de 1000000 caractères ? En théorie, le système d'exploitation devrait agrandir la pile en conséquence. En pratique, la fonction sprintf() va écrire dans une zone mémoire non allouée et un signal SIGSEGV est envoyé au programme.

Dépassement de tampon

Un dépassement de tampon (buffer overflow en anglais) est l'attaque d'un programme visant à injecter du code en utilisant les faiblesses de la pile. C'est la méthode la plus utilisée aujourd'hui pour s'introduire dans un programme. Pourtant, elle est connue depuis novembre 1996 avec l'article Smashing The Stack For Fun And Profit (extrait de l'e-zine Phrack). Le but étant d'écraser la copie du registre EIP stocké sur la pile après les variables temporaires. Exemple de code vulnérable :

void exploit(char *nom)
{
   char texte[256];
   sprintf("Bonjour %s", nom);
   return strdup(texte);
}

Si la variable nom fait plus de 247 caractères (256 - "Bonjour" - "\0"), on va écrire en dehors de la variable texte.

Contre-mesures OpenBSD et Linux

Des contre-mesures ont été imaginée dans le compilateur gcc. Il y a d'abord eu le StackGuard publié en 1997 comme patch pour gcc. L'idée a été reprise dans ProPolice également connu sous le nom Stack-Smashing Protector (SSP). D'abord disponible sous forme de patch, OpenBSD utilise SSP par défaut depuis 2002 et il a fini par être intégré dans gcc 4.1 (mars 2006). SSP ajoute un canari sur la pile pour détecter les dépassements et réorganise les variables pour limiter la casse.

Les premières contre-mesures dans les systèmes d'exploitation sont celles d'OpenBSD écrites par Theo de Raadt : Exploit Mitigation Techniques (août 2004). Les principaux changements sont le patch W^X et l'ajout d'aléatoire dans les adresses mémoires. Le patch W^X permet d'avoir des pages mémoire qui autorisent soit l'écriture soit l'exécution, mais pas les deux. En particulier, on peut écrire dans la pile mais pas y exécuter de code.

Le projet PaX, composant de grsecurity, vise a porter ces techniques pour Linux. Grsecurity est disponible sous forme de patch pour les sources du noyau. Seule une partie a été intégrée à Linux : la technique de randomisation de l'espace mémoire introduite avec Linux 2.6.12 (juin 2005).

Contre-mesures Microsoft

Microsoft a également ses contre-mesures. Visual Studio.NET (2002) apporte l'option de compilation « /GS » qui ajoute un canari dans la pile (comme fait StackGuard). Visual Studio 2003 améliore cette option avec la réorganisation des arguments sur la pile et l'initialisation du canari pour les DLL avec des points d'entrée non standards. Windows a été entièrement compilé avec l'option /GS à partir de Windows 2003 (avril 2003) et de Windows XP SP2 (août 2004).

Windows Vista (janvier 2007) utilise la technique de randomisation de l'espace mémoire et supporte la technologie NX (support matériel des pages non exécutables).

N'abusez pas de la pile

Je pense que vous avez compris maintenant qu'il est déconseillé d'utiliser la pile. En l'occurence, remplacez les appels à alloca() par le couple malloc() + free().

Paul Eggert a rapporté les problèmes de alloca() en 2004 sur la liste de diffusion de la libc, mais apparement il n'a pas été pris au sérieux car aujourd'hui la libc utilise encore beaucoup cette fonction.

L'année suivante (2005), Gaël Delalleau présente ses travaux sur les vulnérabilités de la gestion de la mémoire sur divers systèmes d'exploitation. La conclusion est que tous les systèmes ont leurs failles mais qu'elles sont difficilement exploitables (ce qui ne veut pas dire qu'elles ne sont pas exploitables).

Pour finir, je vous conseille vivement la lecture des conseils de programmation de l'organisme CERT : CERT Secure Coding Standards.

J'ai écrit ce billet pour regrouper tous les liens que j'avais en relation avec la gestion de la pile. J'espère qu'il vous sera utile ;-)