web-dev-qa-db-fra.com

Comment empêcher scanf de provoquer un débordement de tampon en C?

J'utilise ce code:

while ( scanf("%s", buf) == 1 ){

Quelle serait la meilleure façon d'empêcher un éventuel débordement de tampon afin de pouvoir lui passer des chaînes de longueurs aléatoires?

Je sais que je peux limiter la chaîne d'entrée en appelant par exemple:

while ( scanf("%20s", buf) == 1 ){

Mais je préfère pouvoir traiter toutes les entrées utilisateur. Ou cela ne peut-il pas être fait en toute sécurité en utilisant scanf et je devrais utiliser des fgets?

73
goe

Dans leur livre The Practice of Programming (qui vaut la peine d'être lu), Kernighan et Pike discutent de ce problème, et ils le résolvent en utilisant snprintf() pour créer la chaîne avec le tampon correct taille pour passer à la famille de fonctions scanf(). En effet:

int scanner(const char *data, char *buffer, size_t buflen)
{
    char format[32];
    if (buflen == 0)
        return 0;
    snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
    return sscanf(data, format, buffer);
}

Remarque, cela limite toujours l'entrée à la taille fournie en tant que "tampon". Si vous avez besoin de plus d'espace, vous devez effectuer une allocation de mémoire ou utiliser une fonction de bibliothèque non standard qui effectue l'allocation de mémoire pour vous.


Notez que la version POSIX 2008 (2013) de la famille de fonctions scanf() prend en charge un modificateur de format m (un caractère d'affectation-allocation) pour les entrées de chaîne (%s, %c, %[). Au lieu de prendre un argument char *, Il prend un argument char ** Et alloue l'espace nécessaire pour la valeur qu'il lit:

char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
    printf("String is: <<%s>>\n", buffer);
    free(buffer);
}

Si la fonction sscanf() ne parvient pas à satisfaire toutes les spécifications de conversion, alors toute la mémoire allouée pour les conversions de type %ms Est libérée avant le retour de la fonction.

56
Jonathan Leffler

Si vous utilisez gcc, vous pouvez utiliser le spécificateur GNU-extension a pour que scanf () alloue de la mémoire pour que vous puissiez conserver l'entrée:

int main()
{
  char *str = NULL;

  scanf ("%as", &str);
  if (str) {
      printf("\"%s\"\n", str);
      free(str);
  }
  return 0;
}

Edit: Comme Jonathan l'a souligné, vous devriez consulter les pages de manuel scanf car le spécificateur peut être différent (%m) et vous devrez peut-être activer certaines définitions lors de la compilation.

29
John Ledbetter

La plupart du temps, une combinaison de fgets et sscanf fait le travail. L'autre chose serait d'écrire votre propre analyseur, si l'entrée est bien formatée. Notez également que votre deuxième exemple a besoin d'un peu de modification pour être utilisé en toute sécurité:

#define LENGTH          42
#define str(x)          # x
#define xstr(x)         str(x)

/* ... */ 
int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array); 

Ce qui précède supprime le flux d'entrée jusqu'à mais sans inclure le caractère de nouvelle ligne (\n). Vous devrez ajouter une getchar() pour consommer cela. Vérifiez également si vous avez atteint la fin du flux:

if (!feof(stdin)) { ...

et c'est à peu près tout.

9
dirkgently

L'utilisation directe de scanf(3) et de ses variantes pose un certain nombre de problèmes. En règle générale, les utilisateurs et les cas d'utilisation non interactifs sont définis en termes de lignes d'entrée. Il est rare de voir un cas où, si suffisamment d'objets ne sont pas trouvés, plus de lignes résoudront le problème, mais c'est le mode par défaut pour scanf. (Si un utilisateur ne savait pas entrer un numéro sur la première ligne, une deuxième et une troisième ligne ne seront probablement pas utiles.)

Au moins si vous fgets(3) vous savez de combien de lignes d'entrée votre programme aura besoin, et vous n'aurez aucun débordement de tampon ...

4
DigitalRoss

Limiter la longueur de l'entrée est nettement plus facile. Vous pouvez accepter une entrée arbitrairement longue en utilisant une boucle, en lisant un peu à la fois, en réallouant de l'espace pour la chaîne si nécessaire ...

Mais cela demande beaucoup de travail, donc la plupart des programmeurs C coupent simplement l'entrée à une longueur arbitraire. Je suppose que vous le savez déjà, mais l'utilisation de fgets () ne vous permettra pas d'accepter des quantités arbitraires de texte - vous devrez toujours définir une limite.

1
Mark Bessey

Ce n'est pas beaucoup de travail pour créer une fonction qui alloue la mémoire nécessaire à votre chaîne. C'est une petite fonction c que j'ai écrite il y a quelque temps, je l'utilise toujours pour lire des chaînes.

Il renverra la chaîne de lecture ou si une erreur de mémoire se produit NULL. Mais sachez que vous devez libérer () votre chaîne et toujours vérifier sa valeur de retour.

#define BUFFER 32

char *readString()
{
    char *str = malloc(sizeof(char) * BUFFER), *err;
    int pos;
    for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++)
    {
        if(pos % BUFFER == BUFFER - 1)
        {
            if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL)
                free(str);
            str = err;
        }
    }
    if(str != NULL)
        str[pos] = '\0';
    return str;
}
1
user2247995