Pourquoi la fonction gets est-elle si dangereuse qu'elle ne devrait pas être utilisée?
Lorsque j'essaie de compiler du code C qui utilise la fonction gets()
avec GCC,
Je comprends ça
avertissement:
(.text + 0x34): attention: la fonction' gets ' est dangereuse et ne doit pas être utilisée.
Je me souviens que cela a quelque chose à voir avec la protection et la sécurité de la pile,mais je ne sais pas exactement pourquoi?
Quelqu'un peut-il m'aider à supprimer cet avertissement et expliquer pourquoi il existe un tel avertissement sur l'utilisation de gets()
?
Si gets()
est si dangereux alors pourquoi ne pouvons-nous pas l'enlever?
11 réponses
Pour utiliser gets
en toute sécurité, vous devez savoir exactement combien de caractères vous allez lire, de sorte que vous pouvez rendre votre tampon assez grand. Vous ne le saurez que si vous savez exactement quelles données vous allez lire.
Au Lieu de l'aide de gets
, vous souhaitez utiliser fgets
, qui a la signature
char* fgets(char *string, int length, FILE * stream);
(fgets
, s'il lit une ligne entière, laissera le '\n'
dans la chaîne; vous devrez faire face à cela.)
Il est resté une partie officielle de la langue up à la norme ISO C de 1999, mais il a été officiellement supprimé par la norme 2011. La plupart des implémentations C le supportent toujours, mais au moins gcc émet un avertissement pour tout code qui l'utilise.
Pourquoi gets()
est-il dangereux
Le premier ver internet (le ver Internet Morris ) s'est échappé il y a environ 30 ans (1988-11-02), et il a utilisé gets()
et un débordement de tampon comme l'une de ses méthodes de propagation de système en système. Le problème de base est que la fonction ne sait pas quelle est la taille du tampon, donc elle continue à 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é.
Tu devrais oublier que tu as déjà entendu ça gets()
existait.
La norme C11 ISO / IEC 9899: 2011 a éliminé gets()
en tant que Fonction standard, ce qui est une bonne chose™ (elle a été formellement marquée comme "obsolescente" 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 c'était à moi, la mise en œuvre de gets()
deviendrait:
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
Étant Donné que votre code crash quoi qu'il en soit, tôt ou tard, il est préférable d'éviter 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 liez gets()
- et aussi pour d'autres fonctions qui ont également des problèmes de sécurité (mktemp()
, ...).
Alternatives à gets()
Fgets ()
Comme tout le monde l'a dit, l'alternative canonique à gets()
est fgets()
spécifiant stdin
comme flux de fichier.
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 que gets()
n'inclut pas la nouvelle ligne mais fgets()
le fait. Donc, vous devrez peut-être utiliser un wrapper autour de 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;
}
Aussi, caf, souligne dans un commentaire et paxdiablo indique dans sa réponse, avec fgets()
, vous pourriez avoir des données à gauche sur une ligne. Mon code wrapper laisse ces données à lire la prochaine fois; vous pouvez facilement les Modifier pour engloutir le 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 rapporter 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 avec 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 tendue, gâchant souvent la pile de retour (un débordement de pile ) si le tampon est alloué sur la pile, ou piétiner les informations de contrôle si le tampon est alloué dynamiquement, ou copier des données sur d'autres variables globales précieuses (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 y a aussi le TR 24731-1 (rapport technique du Comité de normalisation C) qui fournit des alternatives plus sûres à une variété de fonctions, y compris gets()
:
§6.5.4.1 la fonction
gets_s
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 peut être égal à zéro, ni être supérieur à RSIZE_MAX. Une erreur de caractère de nouvelle ligne, de fin de fichier ou de lecture doit se produire lors de la lecturen-1
caractères destdin
.25)3 S'il y a une violation de contrainte d'exécution,
s[0]
est défini sur le caractère null et les caractères sont lus et jetés destdin
jusqu'à ce qu'un caractère de nouvelle ligne soit lu, ou erreur de lecture se produit.Description
4
gets_s
fonction lit au plus, un de moins que le nombre de caractères spécifié parn
à partir du flux pointé parstdin
, dans le tableau pointé pars
. Aucun autre les caractères sont lus après un caractère de nouvelle ligne (qui est ignoré) ou après la fin du fichier. Le caractère de nouvelle ligne rejeté ne compte pas dans le nombre de caractères lire. 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 erreur se produit pendant l'opération, alors
s[0]
est défini sur le caractère null, et l'autre les éléments des
prennent des valeurs non spécifiées.Pratique Recommandée
6 la fonction
fgets
permet également aux programmes correctement écrits de traiter en toute sécurité les lignes d'entrée long pour stocker dans le tableau résultat. En général, cela exige que les appelants defgets
paient attention à la présence ou l'absence d'un caractère de nouvelle ligne dans le tableau résultat. Considérer utiliserfgets
(avec tout traitement nécessaire basé sur des caractères de nouvelle ligne) au lieu degets_s
.25) la fonction
gets_s
, contrairement àgets
, en fait une violation de contrainte d'exécution pour une ligne d'entrée débordez le tampon pour le stocker. Contrairement àfgets
,gets_s
maintient un un-à-un la relation entre lignes d'entrée et appels réussis versgets_s
. Les programmes qui utilisentgets
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 à L'Annexe K en tant que partie facultative de la bibliothèque. Malheureusement, il est rarement implémenté sur Unix-like système.
getline()
- POSIX
POSIX 2008 fournit également une alternative sûre à gets()
appelé getline()
. Il alloue de l'espace pour la ligne dynamiquement, de sorte que vous finissez par avoir besoin de le libérer. Il supprime donc la limitation de la longueur de la ligne. Il renvoie également la longueur des données lues, ou -1
(et non EOF
!), ce qui signifie que les octets null dans l'entrée peuvent être gérés de manière fiable. Il y a aussi une variation "choisissez votre propre délimiteur de caractère unique" appelé getdelim()
; cela peut être utile si vous traitez avec la sortie de find -print0
où les extrémités des noms de fichiers sont marquées avec un caractère ASCII NUL '\0'
, par exemple.
Parce que gets
ne fait aucune sorte de vérification en obtenant des octets de stdin et en les mettant quelque part. Un exemple simple:
char array1[] = "12345";
char array2[] = "67890";
gets(array1);
Maintenant, tout d'abord, vous êtes autorisé à entrer le nombre de caractères que vous voulez, gets
ne s'en souciera pas. Deuxièmement, les octets sur la taille du tableau dans lequel vous les mettez (dans ce cas array1
) écraseront tout ce qu'ils trouvent en mémoire parce que gets
les écrira. Dans l'exemple précédent, cela signifie que si vous entrez "abcdefghijklmnopqrts"
peut-être, de façon imprévisible, il écrasera également array2
ou autre.
La fonction n'est pas sûre car elle suppose une entrée cohérente. NE JAMAIS L'UTILISER!
Vous ne devriez pas utiliser gets
car il n'a aucun moyen d'arrêter un débordement de tampon. Si l'utilisateur tape plus de données que ce qui peut contenir dans votre tampon, vous finirez probablement par une corruption ou pire.
En fait, ISO ont effectivement pris l'étape de suppression gets
de la norme C (à partir de C11, bien qu'elle ait été dépréciée en C99) qui, compte tenu de la compatibilité ascendante, devrait être une indication de la mauvaise qualité de cette fonction.
La bonne chose à faire est d'utiliser la fonction fgets
avec le handle de fichier stdin
puisque vous pouvez limiter les caractères lus par l'utilisateur.
, Mais cela a aussi ses problèmes, tels que:
- les caractères supplémentaires saisis par l'utilisateur seront récupérés la prochaine fois.
- Il n'y a pas de notification rapide que l'Utilisateur a entré trop de données.
À cette fin, presque tous les codeurs C à un moment donné de leur carrière écriront également un wrapper plus utile autour de fgets
. Voici le mien:
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
Avec un code de test:
// Test program for getLine().
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
printf ("No input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long\n");
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
Il fournit les mêmes protections que fgets
en ce sens qu'il empêche les débordements de tampon, mais il informe également l'appelant de ce qui s'est passé et efface les caractères excédentaires afin qu'ils n'affectent pas votre prochaine opération d'entrée.
Sentir libre d'utiliser comme vous le souhaitez, je me permets de le publier sous le "faites ce que vous sacrément bien envie de" permis :-)
Pour lire à partir du stdin:
char string[512];
fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
Vous ne pouvez pas supprimer les fonctions de L'API sans casser L'API. Si vous le feriez, de nombreuses applications ne compileraient plus ou ne s'exécuteraient plus du tout.
C'est la raison que une référence donne:
Lecture d'une ligne qui déborde tableau pointé par s résultats dans comportement indéfini. L'utilisation de fgets() est recommandé.
J'ai lu récemment, dans un USENET post à comp.lang.c
, que gets()
est retiré de la norme. WOOHOO
Vous serez heureux de savoir que le Commission vient de voter (à l'unanimité, comme il s'avère) pour supprimer gets () de le projet de ainsi.
Dans C11 (ISO / IEC 9899: 201x), gets()
a été supprimé. (Il est obsolète dans ISO / IEC 9899: 1999 / Cor.3:2007(F))
En plus de fgets()
, C11 introduit une nouvelle alternative sûre gets_s()
:
C11 K. 3. 5.4. 1 la fonction
gets_s
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n);
Cependant, dans la section pratique recommandée, fgets()
est toujours préféré.
La fonction
fgets
permet aux programmes correctement écrits de traiter en toute sécurité les lignes d'entrée durée de stockage dans le résultat tableau. En général, cela exige que les appelants defgets
paient attention à la présence ou l'absence d'un caractère de nouvelle ligne dans le tableau résultat. Considérer utiliserfgets
(avec tout traitement nécessaire basé sur des caractères de nouvelle ligne) au lieu degets_s
.
Je voudrais lancer une invitation sérieuse à tous les mainteneurs de bibliothèques C qui incluent toujours gets
dans leurs bibliothèques "au cas où quelqu'un en dépend encore": veuillez remplacer votre implémentation par l'équivalent de
char *gets(char *str)
{
strcpy(str, "Never use gets!");
return str;
}
Cela aidera à s'assurer que personne n'en dépend encore. Merci.
gets()
est dangereux parce qu'il est possible pour l'utilisateur de planter le programme en tapant dans l'invite de commandes. Il ne peut pas détecter la fin de la mémoire disponible, donc si vous allouez une quantité de mémoire trop petite pour le but, cela peut provoquer une erreur seg et un crash. Parfois, il semble très peu probable qu'un utilisateur tape 1000 lettres dans une invite destinée au nom d'une personne, mais en tant que programmeurs, nous devons rendre nos programmes pare-balles. (il peut aussi être un risque de sécurité si un utilisateur peut planter un système programme en envoyant trop de données).
fgets()
vous permet de spécifier combien de caractères sont retirés du tampon d'entrée standard, afin qu'ils ne dépassent pas la variable.
La fonction C gets est dangereuse et a été une erreur très coûteuse. Tony Hoare le singularise pour une mention spécifique dans son discours "références nulles: L'erreur de milliards de dollars":
Http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
Toute l'heure vaut la peine d'être regardée, mais pour ses commentaires, Voir à partir de 30 minutes avec la critique spécifique autour de 39 minutes.
Espérons que cela aiguise votre appétit pour toute la conversation, ce qui attire l'attention sur la façon dont nous avons besoin de preuves d'exactitude plus formelles dans les langues et comment les concepteurs de langues devraient être blâmés pour les erreurs dans leurs langues, pas le programmeur. Cela semble avoir été toute la raison douteuse pour les concepteurs de mauvais langages de pousser le blâme aux programmeurs sous le couvert de "liberté du programmeur".