web-dev-qa-db-fra.com

Lequel de sprintf/snprintf est le plus sécurisé?

Je souhaite savoir laquelle de ces deux options est la plus sûre à utiliser:

#define MAXLEN 255
char buff[MAXLEN + 1]
  1. sprintf(buff, "%.*s", MAXLEN, name)

  2. snprintf(buff, MAXLEN, "%s", name)

Je crois comprendre que les deux sont identiques. Veuillez suggérer.

37
Arpit

Les deux expressions que vous avez données sont pas équivalent: sprintf ne prend aucun argument spécifiant le nombre maximal d’octets à écrire; il faut simplement un tampon de destination, une chaîne de format et un tas d'arguments. Par conséquent, il peut écrire plus d'octets que votre tampon ne dispose de suffisamment d'espace pour écrire, ce faisant, du code arbitraire. Le %.*s n'est pas une solution satisfaisante pour les raisons suivantes:

  1. Lorsque le spécificateur de format fait référence à la longueur, il fait référence à l'équivalent de strlen; c'est une mesure du nombre de caractères dans la chaîne, pas de sa longueur en mémoire (c'est-à-dire qu'il ne compte pas le terminateur nul).
  2. Toute modification de la chaîne de format (ajout d'une nouvelle ligne, par exemple) modifiera le comportement de la version sprintf en ce qui concerne les dépassements de mémoire tampon. Avec snprintf, un maximum fixe et clair est défini, quelles que soient les modifications apportées à la chaîne de formatage ou aux types d'entrée.
34
azernik

Pour l'exemple simple dans la question, il pourrait ne pas y avoir beaucoup de différence de sécurité entre les deux appels. Cependant, dans le cas général, snprintf() est probablement plus sécurisé. Une fois que vous avez une chaîne de format plus complexe avec plusieurs spécifications de conversion, il peut être difficile (ou presque impossible) de vous assurer que la longueur de la mémoire tampon est comptabilisée avec précision pour toutes les conversions - en particulier, les conversions précédentes ne produisant pas nécessairement un nombre fixe des caractères de sortie.

Donc, je resterais avec snprintf().

Un autre petit avantage de snprintf() (bien que n'étant pas lié à la sécurité) est qu'il vous indique la taille de la mémoire tampon dont vous avez besoin.

Une dernière remarque - vous devez spécifier la taille réelle de la mémoire tampon dans l'appel snprintf() - elle gérera la comptabilisation du terminateur nul pour vous:

snprintf(buff, sizeof(buff), "%s", name);
10
Michael Burr

Je dirais que snprintf() est beaucoup mieux tant que je n'ai pas lu ce passage: 

https://buildsecurityin.us-cert.gov/bsi/articles/knowledge/coding/838-BSI.html

En résumé: snprintf() non portable, changement de comportement d’un système à l’autre. Le problème le plus grave avec snprintf() peut survenir lorsque snprintf() est implémenté simplement en appelant sprintf().Vous pensez peut-être qu'il vous protège du dépassement de mémoire tampon et vous laisse baisser la garde, mais ce n'est pas le cas.

Alors maintenant, je dis toujours snprintf() plus sûr, mais aussi prudent lorsque je l'utilise.

5
Kadir Erdem Demir

Votre déclaration de sprintf est correcte, mais je ne serais pas assez confiant pour l'utiliser pour des raisons de sécurité (par exemple, il manque un caractère cryptique et vous êtes sans bouclier) tant qu'il y a un snprintf qui peut être appliqué à n'importe quel format ... wait snprintf n’est pas en ANSI C . C'est (seulement?) C99. Cela pourrait être une (faible) raison de préférer l’autre.

Bien. Vous pouvez aussi utiliser strncpy, n'est-ce pas?

par exemple. 

  char buffer[MAX_LENGTH+1];
  buffer[MAX_LENGTH]=0;             // just be safe in case name is too long
  strncpy(buffer,MAX_LENGTH,name);  // strncpy will never overwrite last byte
2
PypeBros

La meilleure et la plus souple serait d’utiliser snprintf!

size_t nbytes = snprintf(NULL, 0, "%s", name) + 1; /* +1 for the '\0' */
char *str = malloc(nbytes);
snprintf(str, nbytes, "%s", name);

Dans C99, snprintf renvoie le nombre d'octets écrits dans la chaîne, à l'exception de '\0'. S'il y a moins d'octets que nécessaire, snprintf renvoie le nombre d'octets qui aurait été nécessaire pour développer le format (en excluant toujours le '\0'). En transmettant à snprintf une chaîne de longueur 0, vous pouvez savoir à l'avance combien de temps aurait été la chaîne développée et l'utiliser pour allouer la mémoire nécessaire.

2

Il existe une différence importante entre ces deux méthodes: l'appel snprintf analysera l'argument name jusqu'à la fin (fin NUL) afin de déterminer la valeur de retour correcte. L’appel sprintf, par contre, lira AT PLUS 255 caractères de name.

Ainsi, si name est un pointeur sur un tampon non terminé par NUL comportant au moins 255 caractères, l'appel snprintf peut s'exécuter à la fin du tampon et déclencher un comportement non défini (tel qu'un blocage), alors que la version sprintf ne le sera pas.

1
Chris Dodd

Les deux donneront le résultat souhaité, mais snprintf est plus générique et protégera votre chaîne des dépassements, quelle que soit la chaîne de format indiquée.

De plus, étant donné que snprintf (ou sprintf ajoute) un \0 final, vous devez augmenter la taille du tampon de chaîne d'un octet, char buff[MAXLEN + 1].

0
Eli Iser