Question

Lorsque je tente de compiler du code C qui utilise la fonction gets() avec GCC, je reçois cet avertissement:

  

(. + Texte 0x34): avertissement:. La fonction `obtient est dangereux et ne doit pas être utilisé

Je me souviens a quelque chose à voir avec la protection et la sécurité pile, mais je ne sais pas exactement pourquoi.

Comment puis-je supprimer cet avertissement et pourquoi est-il un tel avertissement sur l'utilisation gets()?

Si gets() est si dangereux, pourquoi ne pouvons-nous l'enlever?

Était-ce utile?

La solution

Pour utiliser gets en toute sécurité, vous devez savoir exactement combien de personnages vous en train de lire, de sorte que vous pouvez rendre votre tampon assez grand. Vous ne savez que si vous savez exactement quelles sont les données que vous allez lire.

Au lieu d'utiliser gets, vous voulez utiliser fgets , qui a signature

char* fgets(char *string, int length, FILE * stream);

(fgets, si elle lit une ligne entière, quittera la '\n' dans la chaîne;. Vous devrez faire face à cela)

Il est resté une partie officielle de la langue à la norme ISO 1999 C, mais il a été officiellement retiré par la norme 2011. La plupart des implémentations C soutiennent encore, mais au moins les questions gcc un avertissement pour tout code qui l'utilise.

Autres conseils

Pourquoi est-gets() dangereux

Le premier ver Internet ( Morris Internet Worm ) a échappé il y a environ 30 ans (1988- 11-02), et il utilise gets() et un débordement de tampon comme une de ses méthodes de multiplication d'un système à. Le problème de base est que la fonction ne sait pas la taille de la mémoire tampon est, il continue la lecture jusqu'à ce qu'il trouve une nouvelle ligne ou rencontre EOF, et peut déborder les limites du tampon, il a été donné.

Vous devriez oublier que vous jamais entendu dire que gets() existait.

La norme C11 ISO / IEC 9899: 2011 éliminé gets() en fonction standard, ce qui est une bonne chose ™ (il a été officiellement marqué comme 'obsolescents' et 'désapprouvée' dans la norme ISO / IEC 9899: 1999 / Cor.3: 2007 - Rectificatif technique 3 pour C99, puis retiré en C11). Malheureusement, il restera dans les bibliothèques depuis de nombreuses années (ce qui signifie « décennies ») pour des raisons de compatibilité ascendante. 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 bloque de toute façon, tôt ou tard, il est préférable de diriger la peine de plutôt 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ère des avertissements si vous établissez un lien 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 déjà dit, l'alternative canonique à gets() est fgets() spécifiant stdin que le flux de fichiers.

char buffer[BUFSIZ];

while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
    ...process line of data...
}

Qu'est-ce que personne ne dit autre encore est que gets() ne comprend pas la nouvelle ligne, mais fgets() fait. Donc, vous pourriez avoir besoin d'utiliser un wrapper autour fgets() qui supprime le saut de 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;
}

En outre, comme le souligne dans café un commentaire et paxdiablo montre dans sa réponse, avec fgets() peut contenir des données laissées sur une ligne. Mon code feuilles enveloppantes que les données à lire la prochaine fois; vous pouvez modifier facilement pour gober 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 comment signaler les trois états de résultat -. EOF ou une erreur, ligne de lecture et non tronquée, et la ligne partielle lecture, mais les données ont été tronquées

Ce problème ne se pose pas avec gets() parce qu'il ne sait pas où votre tampon se termine et piétine allègrement au-delà de la fin, faire des ravages sur votre mise en page de mémoire très bien entretenus, souvent déconner la pile de retour (un Stack débordement ) si la mémoire tampon est allouée sur la pile, ou piétinement sur les informations de commande si la mémoire tampon est allouée dynamiquement, ou les données de copie sur un autre global précieux (ou module) des variables si la mémoire tampon est allouée de manière statique. Aucun de ces derniers est une bonne idée -. Ils incarnent l'expression « behaviour` non défini


Il y a aussi TR 24731-1 (Rapport technique du C standard Committee) 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);
     

Runtime-contraintes

     

s ne doit pas être un pointeur NULL. n ne sera ni égale à zéro, ni être supérieur à   RSIZE_MAX. Un caractère de nouvelle ligne, hors d'usagefichier ou erreur de lecture doit avoir lieu dans la lecture   caractères n-1 de stdin. 25)

     

3 En cas de violation de l'exécution-contrainte, s[0] est réglé sur le caractère nul et caractères   sont lus et mis au rebut de stdin jusqu'à ce qu'un caractère de nouvelle ligne est lu, ou à la fin de fichier ou d'un   erreur de lecture se produit.

     

Description de

     

4 La fonction gets_s lit au plus un de moins que le nombre de caractères spécifié par n   à partir du flux pointé par stdin, dans le tableau désigné par s. pas supplémentaire   les caractères sont lus après un caractère de nouvelle ligne (qui est éliminé) ou après la fin de fichier.   Le caractère de nouvelle ligne mis au rebut ne compte pas le nombre de caractères lus. UNE   caractère nul est écrit immédiatement après le dernier caractère lu dans le tableau.

     

5 Si la fin de fichier est rencontré et pas de caractères ont été lus dans le tableau, ou si une lecture   erreur se produit pendant l'opération, puis s[0] est réglé sur le caractère nul, et l'autre   des éléments de s prennent des valeurs non spécifiées.

     

Pratique recommandée

     

6 La fonction fgets permet aux programmes correctement écrits pour traiter en toute sécurité les lignes d'entrée trop   longue pour stocker dans le tableau de résultats. En général, cela exige que les appelants de fgets payer   attention à la présence ou l'absence d'un caractère de nouvelle ligne dans le tableau de résultats. Considérer   en utilisant fgets (ainsi que tout traitement nécessaire sur la base de caractères de nouvelle ligne) au lieu de   gets_s.

     

25) La fonction gets_s, contrairement à gets, en fait une violation de l'exécution-contrainte pour une ligne d'entrée   déborder le tampon pour stocker. Contrairement à fgets, gets_s maintient une relation un-à-un entre   lignes d'entrée et les appels réussis à gets_s. Les programmes qui utilisent gets attendent une telle relation.

Les compilateurs Microsoft Visual Studio mettent en œuvre une approximation à la norme TR 24731-1, mais il existe des différences entre les signatures mises en œuvre par Microsoft et celles du TR.

La norme C11, ISO / IEC 9899-2011, comprend TR24731 à l'annexe K comme une partie facultative de la bibliothèque. Malheureusement, il est rarement mis en œuvre sur les systèmes Unix.


getline() - Posix

2008 Posix fournit également une alternative sûre à gets() appelé getline(). Il alloue de l'espace pour la ligne dynamique, donc vous finissez par avoir besoin de le libérer. Il supprime la limitation de la longueur de la ligne, donc. Il renvoie également la longueur des données qui a été lu, ou -1 (et non EOF!), Ce qui signifie que les octets nuls dans l'entrée peuvent être traitées de manière fiable. Il y a aussi une variation « choisir votre propre délimiteur unique caractère » appelé getdelim(); cela peut être utile si vous avez affaire à la sortie de find -print0 où les extrémités des noms de fichiers sont marqués d'un ASCII NUL caractère '\0', par exemple.

Parce que gets ne fait aucune sorte de contrôle tout en obtenant des octets de stdin et les mettre 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 se souciera pas à ce sujet. D'autre part les octets sur la taille du tableau dans lequel vous les mettez (dans ce cas array1) va écraser tout ce qu'ils trouvent dans la mémoire parce que gets leur écrire. Dans l'exemple précédent, cela signifie que si vous "abcdefghijklmnopqrts" entrée peut-être, imprévisiblement, il remplacera également array2 ou autre.

La fonction est dangereuse car elle suppose entrée cohérente. NE JAMAIS UTILISER!

Vous ne devriez pas utiliser gets car il n'a aucun moyen d'arrêter un dépassement de mémoire tampon. Si les types d'utilisateurs dans plus de données que peuvent tenir dans votre mémoire tampon, vous finirez probablement avec la corruption ou pire.

En fait, l'ISO ont effectivement pris l'initiative de suppression gets de la norme C (à partir de C11, mais il a été dépréciée en C99) qui, compte tenu de la façon très ils évaluent la compatibilité ascendante, devrait être une indication de la façon dont cette fonction était mauvaise.

La bonne chose à faire est d'utiliser la fonction fgets avec la poignée de fichier stdin puisque vous pouvez limiter les caractères lus de l'utilisateur.

Mais cela a aussi ses problèmes tels que:

  • caractères supplémentaires saisis par l'utilisateur seront pris en charge la prochaine fois.
  • il n'y a pas de notification rapide que l'utilisateur est entré trop de données.

À cette fin, presque tous les codeur C à un moment donné dans leur carrière rédigera une enveloppe plus utile autour fgets aussi bien. 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 offre les mêmes protections que fgets en ce qu'elle empêche les débordements de tampon, mais il avertit également l'appelant à ce qui est arrivé et efface les caractères en excès afin qu'ils ne touchent pas votre opération d'entrée suivante.

Ne hésitez pas à l'utiliser comme vous le souhaitez, je délie sous le « faire ce que vous voulez sacrément bien » licence: -)

fgets .

Pour lire 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, de nombreuses applications ne seraient plus compiler ou exécuter du tout.

Ceci est la raison pour laquelle une référence donne:

  

Lecture d'une ligne qui déborde la   tableau pointé par les résultats en   un comportement non défini. L'utilisation de fgets ()   il est recommandé.

J'ai lu récemment, dans un poste USENET à comp.lang.c , qui est gets() se retire de la norme. WOOHOO

  

Vous serez heureux de savoir que le   comité vient de voter (à l'unanimité, comme   il se trouve) pour enlever gets () de   le projet ainsi.

Dans C11 (ISO / CEI 9899: 201x), gets() a été supprimée. (Il est dépréciée dans la norme ISO / IEC 9899: 1999 / Cor.3: 2007 (E))

En plus de fgets(), C11 introduit une nouvelle gets_s() alternative sûre:

  

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 le Pratique recommandée section, fgets() est encore préféré.

  

La fonction fgets permet aux programmes correctement écrits pour traiter en toute sécurité les lignes d'entrée trop   longue pour stocker dans le tableau de résultats. En général, cela exige que les appelants de fgets payer   attention à la présence ou l'absence d'un caractère de nouvelle ligne dans le tableau de résultats. Considérer   en utilisant fgets (ainsi que tout traitement nécessaire sur la base de caractères de nouvelle ligne) au lieu de   gets_s.

Je voudrais adresser une invitation sincère à toutes les mainteneurs de la bibliothèque C là-bas qui incluent encore gets dans leurs bibliothèques « juste au cas où tout le monde est encore en dépendent »: S'il vous plaît remplacer votre mise en œuvre avec l'équivalent de

char *gets(char *str)
{
    strcpy(str, "Never use gets!");
    return str;
}

Cela contribuera à rendre ce que personne ne reste dépend. Merci.

gets() est dangereux, car il est possible pour l'utilisateur de plantage du programme en tapant trop dans l'invite. Il ne peut pas détecter la fin de la mémoire disponible, donc si vous allouez une quantité de mémoire trop petit pour le but, il peut provoquer un défaut de SEG et accident. Parfois, il semble très peu probable qu'un utilisateur tapera 1000 lettres dans une invite signifiait pour le nom d'une personne, mais en tant que programmeurs, nous devons rendre nos programmes pare-balles. (Il peut aussi être un risque pour la sécurité si un utilisateur peut bloquer un programme de système en envoyant des données trop).

fgets() vous permet de spécifier le nombre de caractères sont retirés de la mémoire tampon d'entrée standard, de sorte qu'ils n'envahit pas la variable.

Le C devient fonction est dangereuse et a été une erreur très coûteuse. Tony Hoare simple il d'une mention spécifique dans son discours « Références Null: Le milliard de dollars erreur »:

http: //www.infoq. com / présentations / Null-Références-Le-milliard-dollar-erreur-Tony-Hoare

L'heure entière vaut la peine de regarder, mais pour voir ses commentaires de 30 minutes avec la critique spécifique se environ 39 minutes.

Espérons que cela ouvre l'appétit pour toute la conférence, qui attire l'attention sur la façon dont nous avons besoin de plus de preuves correction formelle dans les langues et la façon dont les concepteurs de langue devraient être blâmés pour les erreurs dans leurs langues, pas le programmeur. Cela semble avoir été toute raison douteuse pour les concepteurs de mauvaises langues pour pousser le blâme aux programmeurs sous la forme de la « liberté de programmation ».

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top