web-dev-qa-db-fra.com

Pourquoi ce code est-il vulnérable aux attaques par dépassement de tampon?

int func(char* str)
{
   char buffer[100];
   unsigned short len = strlen(str);

   if(len >= 100)
   {
        return (-1);
   }

   strncpy(buffer,str,strlen(str));
   return 0;
}

Ce code est vulnérable à une attaque par dépassement de tampon, et j'essaie de comprendre pourquoi. Je pense que cela a à voir avec len d'être déclarée short au lieu de int, mais je ne suis pas vraiment sûr.

Des idées?

148
Jason

Sur la plupart des compilateurs, la valeur maximale d'un unsigned short Est 65535.

Toute valeur supérieure à celle-ci est encapsulée, de sorte que 65536 devient 0 et 65600 devient 65.

Cela signifie que les longues chaînes de la bonne longueur (par exemple 65600) réussiront la vérification et satureront la mémoire tampon.


Utilisez size_t Pour stocker le résultat de strlen(), pas unsigned short, Et comparez len à une expression qui code directement la taille de buffer. Donc par exemple:

char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0]))  return -1;
memcpy(buffer, str, len + 1);
193
orlp

Le problème est ici:

strncpy(buffer,str,strlen(str));
                   ^^^^^^^^^^^

Si la chaîne est supérieure à la longueur du tampon cible, strncpy la recopiera quand même. Vous basez le nombre de caractères de la chaîne en tant que nombre à copier au lieu de la taille du tampon. La bonne façon de faire est la suivante:

strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = '\0';

Cela limite la quantité de données copiées à la taille réelle de la mémoire tampon moins un pour le caractère final nul. Ensuite, nous définissons le dernier octet dans la mémoire tampon sur le caractère NULL en tant que sauvegarde supplémentaire. La raison en est que strncpy va copier jusqu’à n octets, y compris le null final, si strlen (str) <len - 1. Si ce n’est pas le cas, le null n’est pas copié et vous avez un scénario de plantage, car votre mémoire tampon est non terminée. chaîne.

J'espère que cela t'aides.

EDIT: Après un examen approfondi et la contribution d’autres personnes, un codage possible de la fonction est donné ci-après:

int func (char *str)
  {
    char buffer[100];
    unsigned short size = sizeof(buffer);
    unsigned short len = strlen(str);

    if (len > size - 1) return(-1);
    memcpy(buffer, str, len + 1);
    buffer[size - 1] = '\0';
    return(0);
  }

Puisque nous connaissons déjà la longueur de la chaîne, nous pouvons utiliser memcpy pour copier la chaîne à partir de l’emplacement référencé par str dans la mémoire tampon. Notez que selon la page de manuel de strlen (3) (sur un système FreeBSD 9.3), ce qui suit est indiqué:

 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.

Ce que j'interprète comme étant que la longueur de la chaîne n'inclut pas le null. C'est pourquoi je copie len + 1 octets pour inclure la valeur null et le test vérifie que la longueur est <taille du tampon - 2. Moins un car le tampon commence à la position 0 et un autre pour s'assurer qu'il y a de la place pour le nul.

EDIT: il s'avère que la taille de quelque chose commence par 1 alors que l'accès commence par 0, donc le -2 avant était incorrect car il renverrait une erreur pour tout ce qui est> 98 octets, mais il devrait être> 99 octets.

EDIT: Bien que la réponse concernant un raccourci non signé soit généralement correcte, car la longueur maximale pouvant être représentée est de 65 535 caractères, cela n'a pas d'importance, car si la chaîne est plus longue que celle-ci, la valeur sera renvoyée à la ligne. C'est comme si on prenait 75,231 (ce qui correspond à 0x000125DF) et que l'on masquait les 16 bits supérieurs, ce qui vous donnait 9695 (0x000025DF). Le seul problème que je vois avec ceci est que les 100 premiers caractères sont passés après 65 535 car la vérification de la longueur permettra la copie, mais cela ne fera que copier jusqu'aux 100 premiers caractères de la chaîne dans tous les cas et mettre fin à la chaîne à zéro.. Ainsi, même avec le problème enveloppant, le tampon ne sera toujours pas saturé.

Cela peut ou non poser un risque de sécurité en fonction du contenu de la chaîne et de l'utilisation que vous en faites. Si le texte est simplement lisible par un humain, il n'y a généralement pas de problème. Vous venez juste d'obtenir une chaîne tronquée. Cependant, si cela ressemble à une URL ou même à une séquence de commandes SQL, vous pourriez avoir un problème.

28
Daniel Rudy

Même si vous utilisez strncpy, la longueur de la coupure dépend toujours du pointeur de chaîne passé. Vous n'avez aucune idée de la longueur de cette chaîne (l'emplacement du terminateur nul par rapport au pointeur). Donc, appeler strlen seul vous ouvre la porte à la vulnérabilité. Si vous voulez être plus en sécurité, utilisez strnlen(str, 100).

Le code complet corrigé serait:

int func(char *str) {
   char buffer[100];
   unsigned short len = strnlen(str, 100); // sizeof buffer

   if (len >= 100) {
     return -1;
   }

   strcpy(buffer, str); // this is safe since null terminator is less than 100th index
   return 0;
}
11
Patrick Roberts

La réponse avec l'emballage est juste. Mais il y a un problème que je pense n'a pas été mentionné si (len> = 100)

Eh bien, si Len aurait 100 ans, nous copierions 100 éléments et nous n’aurions pas le\0 final. Cela signifierait clairement que toute autre fonction dépendant de la chaîne finie appropriée irait bien au-delà du tableau d'origine.

La chaîne problématique de C est IMHO insoluble. Il vaut toujours mieux avoir des limites avant l'appel, mais même cela ne va pas aider. Il n'y a pas de vérification des limites et donc les débordements de mémoire tampon peuvent toujours et se produiront malheureusement ....

4
Friedrich

Au-delà des problèmes de sécurité liés à l'appel de strlen plusieurs fois, il est généralement déconseillé d'utiliser des méthodes string sur des chaînes dont la longueur est connue avec précision [pour la plupart des fonctions de chaîne, le cas où elles doivent être utilisées est vraiment très étroit-- sur des cordes pour lesquelles une longueur maximale peut être garantie, mais dont la longueur exacte n'est pas connue]. Une fois que la longueur de la chaîne d'entrée est connue et la longueur du tampon de sortie est connue, il convient de déterminer la taille d'une région à copier, puis d'utiliser memcpy() pour effectuer la copie en question. Bien qu'il soit possible que strcpy puisse surpasser memcpy() lors de la copie d'une chaîne de 1 à 3 octets ou plus, sur de nombreuses plateformes, memcpy() sera probablement plus de deux fois rapide lorsqu'il s'agit de grandes chaînes.

Bien que dans certains cas, la sécurité se fasse au détriment des performances, il s'agit d'une situation dans laquelle l'approche sécurisée est aussi la plus rapide. Dans certains cas, il peut être raisonnable d'écrire du code qui ne soit pas sécurisé contre des entrées ayant un comportement étrange, si le code fournissant les entrées peut garantir qu'elles se comporteront correctement et si la protection contre des entrées mal comportées entraverait les performances. Le fait de s'assurer que les longueurs de chaîne ne sont vérifiées qu'une seule fois améliore les deux performances et sécurité, bien qu'une chose supplémentaire puisse être faite pour aider à protéger la sécurité même lorsque le suivi de la longueur d'une chaîne est effectué manuellement: pour chaque chaîne devant contenir un null final , écrivez explicitement le null final plutôt que d’attendre que la chaîne source l’ait. Donc, si on écrivait un strdup équivalent:

char *strdupe(char const *src)
{
  size_t len = strlen(src);
  char *dest = malloc(len+1);
  // Calculation can't wrap if string is in valid-size memory block
  if (!dest) return (OUT_OF_MEMORY(),(char*)0); 
  // OUT_OF_MEMORY is expected to halt; the return guards if it doesn't
  memcpy(dest, src, len);      
  dest[len]=0;
  return dest;
}

Notez que la dernière instruction peut généralement être omise si la mémoire a traité len+1 octets, mais si un autre thread devait modifier la chaîne source, le résultat pourrait être une chaîne de destination non terminée par NUL.

3
supercat