Pourquoi est gets()
dangereux
Le premier ver Internet (le Morris Internet Worm ) s'est échappé il y a environ 30 ans (1988-11-02), et il a utilisé gets()
un débordement de tampon comme l'une de ses méthodes de propagation d'un système à l'autre. Le problème de base est que la fonction ne sait pas quelle est la taille du tampon, donc elle continue de lire jusqu'à ce qu'elle trouve une nouvelle ligne ou rencontre EOF, et peut déborder les limites du tampon qui lui a été donné.
Vous devez oublier que vous avez déjà entendu dire que cela gets()
existait.
La norme C11 ISO / IEC 9899: 2011 a été éliminée en gets()
tant que fonction standard, qui est A Good Thing ™ (elle était formellement marquée comme «obsolète» et «obsolète» dans ISO / IEC 9899: 1999 / Cor.3: 2007 - Rectificatif technique 3 pour C99, puis supprimé dans C11). Malheureusement, il restera dans les bibliothèques pendant de nombreuses années (ce qui signifie «décennies») pour des raisons de rétrocompatibilité. Si cela ne tenait qu'à moi, la mise en œuvre de gets()
deviendrait:
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
Étant donné que votre code se bloquera de toute façon, tôt ou tard, il est préférable de régler le problème plus tôt que tard. Je serais prêt à ajouter un message d'erreur:
fputs("obsolete and dangerous function gets() called\n", stderr);
Les versions modernes du système de compilation Linux génèrent des avertissements si vous établissez un lien gets()
- et également pour certaines autres fonctions qui ont également des problèmes de sécurité ( mktemp()
,…).
Alternatives à gets()
fgets ()
Comme tout le monde l' a déjà dit, l'alternative canonique gets()
est en fgets()
spécifiant stdin
que le flux de fichiers.
char buffer[BUFSIZ];
while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
...process line of data...
}
Ce que personne d'autre n'a encore mentionné, c'est qu'il gets()
n'inclut pas la nouvelle ligne mais le fgets()
fait. Ainsi, vous devrez peut-être utiliser un wrapper fgets()
qui supprime la nouvelle ligne:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
return buffer;
}
return 0;
}
Ou mieux:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
buffer[strcspn(buffer, "\n")] = '\0';
return buffer;
}
return 0;
}
De plus, comme le souligne caf dans un commentaire et paxdiablo le montre dans sa réponse, fgets()
vous pourriez avoir des données sur une ligne. Mon code wrapper laisse ces données à lire la prochaine fois; vous pouvez facilement le modifier pour engloutir le reste de la ligne de données si vous préférez:
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
else
{
int ch;
while ((ch = getc(fp)) != EOF && ch != '\n')
;
}
Le problème résiduel est de savoir comment signaler les trois états de résultat différents - EOF ou erreur, ligne lue et non tronquée, et ligne partielle lue mais les données ont été tronquées.
Ce problème ne se pose pas gets()
car il ne sait pas où se termine votre tampon et piétine joyeusement au-delà de la fin, faisant des ravages sur votre disposition de mémoire magnifiquement entretenue, gâchant souvent la pile de retour (un débordement de pile ) si le tampon est alloué sur la pile, ou le piétinement sur les informations de contrôle si le tampon est alloué dynamiquement, ou la copie de données sur d'autres précieuses variables globales (ou module) si le tampon est alloué statiquement. Rien de tout cela n'est une bonne idée - ils incarnent l'expression «comportement indéfini».
Il existe également le TR 24731-1 (rapport technique du comité de normalisation C) qui offre des alternatives plus sûres à une variété de fonctions, notamment gets()
:
§6.5.4.1 La gets_s
fonction
Synopsis
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);
Contraintes d'exécution
s
ne doit pas être un pointeur nul. n
ne doit pas être égal à zéro ni supérieur à RSIZE_MAX. Un caractère de nouvelle ligne, une fin de fichier ou une erreur de lecture doit se produire dans la lecture des
n-1
caractères de stdin
. 25)
3 S'il y a une violation de contrainte d'exécution, s[0]
est défini sur le caractère nul et les caractères sont lus et ignorés stdin
jusqu'à ce qu'un caractère de nouvelle ligne soit lu, ou à la fin du fichier ou qu'une erreur de lecture se produise.
La description
4 La gets_s
fonction lit au plus un de moins que le nombre de caractères spécifié par n
dans le flux pointé par stdin
, dans le tableau pointé par s
. Aucun caractère supplémentaire n'est lu après un caractère de nouvelle ligne (qui est supprimé) ou après la fin du fichier. Le caractère de nouvelle ligne supprimé ne compte pas dans le nombre de caractères lus. Un caractère nul est écrit immédiatement après le dernier caractère lu dans le tableau.
5 Si la fin du fichier est rencontrée et qu'aucun caractère n'a été lu dans le tableau, ou si une erreur de lecture se produit pendant l'opération, le s[0]
paramètre est défini sur le caractère nul et les autres éléments de s
prennent des valeurs non spécifiées.
Pratique recommandée
6 La fgets
fonction permet aux programmes correctement écrits de traiter en toute sécurité les lignes d'entrée trop longtemps pour être stockées dans le tableau de résultats. En général, cela nécessite que les appelants soient fgets
attentifs à la présence ou à l'absence d'un caractère de nouvelle ligne dans le tableau de résultats. Envisagez d'utiliser fgets
(avec tout traitement nécessaire basé sur des caractères de nouvelle ligne) au lieu de
gets_s
.
25) La gets_s
fonction, contrairement à gets
, en fait une violation de contrainte d'exécution pour qu'une ligne d'entrée déborde le tampon pour le stocker. Contrairement à fgets
, gets_s
maintient une relation un à un entre les lignes d'entrée et les appels réussis vers gets_s
. Les programmes qui utilisent gets
s'attendent à une telle relation.
Les compilateurs Microsoft Visual Studio implémentent une approximation de la norme TR 24731-1, mais il existe des différences entre les signatures implémentées par Microsoft et celles du TR.
La norme C11, ISO / IEC 9899-2011, inclut TR24731 dans l'annexe K en tant que partie facultative de la bibliothèque. Malheureusement, il est rarement implémenté sur des systèmes de type Unix.
getline()
- POSIX
POSIX 2008 fournit également une alternative sûre aux gets()
appels getline()
. Il alloue de l'espace de manière dynamique à la ligne, vous avez donc besoin de la libérer. Il supprime donc la limitation de la longueur de ligne. Il renvoie également la longueur des données lues, ou -1
(et non EOF
!), Ce qui signifie que les octets nuls dans l'entrée peuvent être traités de manière fiable. Il existe également une variante «choisissez votre propre délimiteur à caractère unique» appelée getdelim()
; cela peut être utile si vous traitez la sortie d' find -print0
où les extrémités des noms de fichiers sont marquées avec un caractère ASCII NUL '\0'
, par exemple.
gets()
Buffer_overflow_attack