Introduction

Aujourd’hui la qualité d’un code dépend de deux chose : la première est de la qualité du programme en lui-même et la seconde est la sécurité de ce code. La qualité du code est donc le plus important pour un développeur (mise à part la conception de l’architecture de son code). Cependant il est impossible de sécuriser son code et sa conception a 100%. Il existera toujours des failles de sécurité. Cela est dû à l’affrontement continuel entre les développeurs d’application et les hackers. Ce qui va pousser les développeurs à toujours pousser la barre de la sécurité plus haut dans leurs codes, mais aussi pousser les hackers à chercher continuellement de nouvelles failles à exploiter. Dans cet article nous verrons comment nous protéger au mieux des bugs de conception et des bugs de sécurité en expliquant les bonnes pratiques pour avoir un code de qualité.

Il existe deux méthodes très efficaces permettant de trouver les bugs de sécurité d’une application :

  1. Bannir les API deprecated
  2. SAL

Nous expliquerons plus tard le fonctionnement de ces méthodes et comment elles permettent de réduire les bugs de sécurité. Ce qu’il faut retenir c’est que grâce à cela environ 84% des bugs lié à la mémoire tampon (buffer related) sont fixés.

Pour vous donner un exemple, Windows met en place des portes de sécurité/qualité sur les codes entrant dans la composition de son code source. Ces portes ont pour but de protéger et de garantir un certain niveau de qualité dans certains domaines comme la sécurité, la vie privée, la fiabilité, et bien d’autres. Comme vous l’aurez compris, celle qui nous intéresse est la porte de qualité qui concerne la sécurité.

 Cette porte exige les choses suivantes de tout nouveau code :

  • Tous les buffers de string doivent être annoté avec SAL (cela concerne les langages C/C++)
  • Les Api interdites doivent être retirées des bases du code (Ban API removal)
  • La cryptographie est interdite dans les bases de données
  • Des analyses statiques doivent être misent en place pour trouver et corriger les bugs
  • Tous les codes doivent être compiler avec /GS
  • Tous les codes doivent être lier avec h /SafeSEH, /DynamicBase et /NXCompat

Toute ces portes ont donc pour but d’uniformiser et de faire respecter les politiques concernant le code que ce soit pour la sécurité comme vu précédemment ou même pour la vie privée. Dans la suite de cet article nous allons nous concentrer sur l’explication des différentes pratiques exigées par la porte de sécurité en abordant, leurs fonctionnements, leurs utilités, ainsi que leurs efficacités.

SAL ou annoter des buffers de string

Le but de SAL ou Standard Annotation Language est d’annoncer explicitement le contrat entre les implémentations (appels) et les clients (appelants). Cela permet au développeur de trouver les bugs plus facilement.

Comment mettre en place la méthode SAL

SAL consiste donc à annoter des fonctions pour définir un comportement voulu par rapport aux buffers de string dans cette fonction. Autrement dit SAL définie l’utilisation correcte des tampons. Lorsqu’une fonction est annotée, tout le code de celle-ci se voit annoter de la même façon.

Le but étant donc de surveiller si une vulnérabilité existe au niveau des buffers de string , les pointeurs en C/C++ peuvent représenter l’adresse d’un objet ou bien l’adresse d’un tableau d’objet. Ses pointeurs doivent donc avoir une taille, soit elle est connue lors de la compilation, soit elle est connue lors de l’exécution du programme, cependant il n’est pas rare d’avoir des pointeurs surchargé et cela ne vous permet donc pas d’avoir un code totalement propre car vous ne pouvez pas compter sur le système de types pour vous aider. C’est à ce moment que SAL rentre en jeu. Pour mieux comprendre voici un exemple :

void FillString( char *buf, int s izebuf, char ch) {
   for (int i = 0; i < sizebuf; i++)
       buf[i] = ch;
}

Ce code est assez simple, il permet de stocker la valeur de la variable ‘ch’ dans le buffer tant que la variable ‘i’ est inferieur a la variable ‘sizebuf’, autrement si sizebuf vaut 3 et que ch vaut ‘a’ alors buf sera égale a ‘’aaa‘’. Cela dit imaginez que buf n’ait pas la place de stocker autant de ‘a’ autrement dit que sa taille soit inférieure à 3, cela entrainerait une erreur. Cela veut donc dire que ce programme contient une faille de sécurité. Pour illustrer cela voici la fonction main qui appelle notre fonction FillString.

int main(){
   char *b = (char*)malloc(2*sizeof(char));
   FillString(b,3,'a');
}

Comme vous pouvez le voir la premier ligne attribut de la mémoire à notre buffer ici il aura la place de stocker 2 variables de type char. Dans la seconde ligne nous appelons notre fonction en lui donnant en paramètre notre buffer, le nombre de char à stocker et le char à stocker. Cependant l’argument 2 est supérieur à la taille allouée à notre buffer. Lors de la compilation de ce programme vous n’aurez pas d’erreur cependant à l’exécution de notre programme, une erreur surviendra dû à notre faille de sécurité expliquée plus haut.

SAL va nous permettre d’indiquer au compilateur que sizebuf et directement lié à buf.

void FillString( __out_ecount(sizebuf) char* buf, 
               int sizebuf,
               char ch) 
{
   for (int i = 0; i < sizebuf; i++)
       buf[i] = ch;
}


Comme vous pouvez le voir lors de la déclaration de notre fonction dans les paramètres nous avons ajoutés  _out_ecount(sizebuf) juste avant notre  buffer, cette ajout est en réalité une macro de SAL qui indique au compilateur que buf doit avoir la taille de sizebuf. Si nous compilons notre programme le compilateur nous renverra un Warning signifiant qu’il y a un problème.

c:\code\saltest\saltest.cpp(54) : warning C6203: Buffer overrun for non-stack buffer ‘b’ in call to ‘FillString’: length ‘3’ exceeds buffer size ‘2’

 c:\code\saltest\saltest.cpp(54) : warning C6386: Buffer overrun: accessing ‘argument 1’, the writable size is ‘1*2’ bytes, but ‘3’ bytes might be written: Lines: 53, 54

c:\code\saltest\saltest.cpp(54) : warning C6387: ‘argument 1’ might be ‘0’: this does not adhere to the specification for the function ‘FillString’: Lines: 53, 54

Ce retour nous explique que notre buffer à une taille inférieure à celle de notre paramètre ‘sizebuf’ et que par conséquent cela est dangereux.

Il existe beaucoup d’autres macros dans SAL :

__in

Cette commande permet de signifier que le buffer designer ne soit pas NULL. Cependant il est conseiller d’utiliser l’attribut const si cela est possible, Le compilateur peut utiliser de meilleures optimisations. Voici un exemple d’utilisation :

BOOL Equals_elem(__in char *pElement) {
   //pElement ne seras donc pas null
}

__out

‘’__out’’ remplit un buffer non vide(non NULL) et permet à ce buffer d’être déréférencer. Voici un exemple d’utilisation :

bool GetFileVersion( char *lpsFile, __out char *pVersion) {
}

__in_opt

Cette commande permet de signifier que le buffer peut être optionnelle. Autrement dit le buffer peut être NULL. Le code qui suit en est un exemple si szMachineName est NULL, le code retournera des informations sur l’ordinateur locale :

BOOL GetOsType( __in_opt char *szMachineName, __out MACHINE_INFO *pMachineInfo);

__inout

__inout_bcount_full(n)

__inout_bcount_part(n,m)

__deref_out_bcount(n)

Les API interdites doivent être retirées des bases de données (Ban API removal) :

La plupart de ces fonctions interdites traitent des tampons à l’exécution et sont une source commune de nombreux tampons de dépassements. Chez Microsoft, il est simplement interdit d’utiliser une fonction s’il est démontré qu’elle conduit à un logiciel constamment non sécurisé. Le site l’équipe de Windows a fait un pas de plus en supprimant activement la plupart des fonctions interdites depuis Windows Vista. Nous disons la plupart parce que dans certains cas, il n’est pas nécessaire de remplacer une fonction par une fonction plus sûre si le tampon source est de confiance. Par exemple, le code ci-dessous est totalement sûr car la chaîne source est une constante. Il n’y a aucune chance que le l’attaquant puisse modifier cette chaîne sans avoir à patcher le binaire directement

int main() {
   const char *src = "Hello, World!";
   char dst[32];
   strcpy(dst,src);
}

Cet article est écrit par les Experts Infeeny et Kevin Ansard.