Partage
  • Partager sur Facebook
  • Partager sur Twitter

Scanf sur VisualStudio

Bonne pratique / sécurité / portabilité

    15 février 2022 à 16:28:28

    Bonjour, je commence à me former au langage C avec le cours de Zeste de Savoir. J'ai déjà de l'expérience dans d'autre langage de prog.

    Je code sur W10 avec Visual Studio Community 2019

    Lorsque je veux utiliser la fonction scanf, il est impossible de compiler puisque je suis bloqué par l'erreur suivante :

    Erreur	C4996
    'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
    

    Je comprend le message d'erreur qui est plutôt explicite, mais les recherche que j'ai faites via différentes discussions sur Stack Overflow ne me satisfont pas vraiment.

    En effet j'ai eu beaucoup d'explication sur pourquoi scanf n'était pas safe avec les problèmes potentiels d'overflow mais pas vraiment de solutions qui résolvent ce problème d'overflow

    Solution 1 : scanf_s

    En effet l'utilisation de scanf_s me permet de compiler ok, mais c'est spécifique à Microsoft et les autres compilateurs ne sont pas tenus de l'implémenter. J'ai d'ailleurs testé sur ma VM ubuntu 21.10 avec gcc 11.2.0 et scanf_s n'est pas reconnu

    Code non portable ==> Pas intéressé

    En plus ça ne résout pas le problème d'overflow, sur un code comme ça par exemple

    #include <stdio.h>
    
    int main()
    {
        printf("Quel age avez vous ?\n");
        int age;    
    
        if(scanf_s("%d", &age) == 1) {
            printf("Succes !\n");
            printf("age = %d\n", age);
        }
        else
            printf("Fail !");
         
        return 0;
    }


    Si l'utilisateur saisi un nombre ridiculement grand comme 1000000000000000000000000000000000 la sortie de ce programme sera

    Quel age avez vous ?
    1000000000000000000000000000000000 
    Succes !
    age = -1

    "Task successfully failed !" :ange:

    Super donc scanf et scanf_s réussissent tous les deux à lire un entier puisque la fonction renvoie 1 mais bien évidemment age se prend un variable overflow et se retrouve à -1 ... Je vois l'intéret de scanf_s et de son 3ème paramètre quand on essaye de lire des chaines de caractère et de les ranger dans des tableaux de char mais là pour de la lecture d'entier, scanf_s pose exactement les même problème que scanf ...

    Solution 2 : #define _CRT_SECURE_NO_WARNINGS

    J'ai vu qu'en ajoutant ce define en haut de mon main, j'allais désactiver tous les warning me proposant les fonctions _s de microsoft. C'est bien mais je cache juste la poussière sous le tapis ... Je peux compiler sur VS je suis content mais on reste avec les problèmes inhérents à scanf et les risques d'overflow en cas de mauvaise saisie

    Existe-t-il donc une solution 3 qui serait portable et qui permettrait de se prémunir de buffer overflow pour les tableaux de char ou de saisie trop grandes dans le cadre de nombre entier ?

    Je ne m'attend pas à ce que vous me donniez une fonction magique qui résout tous les problèmes en une ligne de code, mais si il existe une "bonne pratique" communément admise pour récupérer les entrées et vérifier leur validité je ne l'ai pas trouvé.

    Merci d'avance :)

    • Partager sur Facebook
    • Partager sur Twitter
      15 février 2022 à 16:57:58

      Hello,

      Peut-être ceci

      #include <stdio.h>
      
      int main(void) {
      	printf("> ");
      	int i;
      	scanf("%2d",&i);
      	printf("i=%d\n",i);
      	
      	return(0);
      }
      • Partager sur Facebook
      • Partager sur Twitter

      On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent

        15 février 2022 à 18:03:58

        Il y a la méthode un peu plus longue:
        + tu utilises fgets suivi possiblement de getchar si tu ne reçois pas le '\n'
        + Tu vérifies la longueur saisie.
        + tu décodes avec sscanf

        Si j'écris  22A tu fais quoi? Tu pourrais vérifier tous les caractères et pourquoi ne pas décoder toi-même?

        -
        Edité par PierrotLeFou 15 février 2022 à 18:07:38

        • Partager sur Facebook
        • Partager sur Twitter

        Le Tout est souvent plus grand que la somme de ses parties.

          15 février 2022 à 18:53:20

          ThibaultVnt a écrit:

          Existe-t-il donc une solution 3 qui serait portable et qui permettrait de se prémunir de buffer overflow pour les tableaux de char ou de saisie trop grandes dans le cadre de nombre entier ?

          Je ne m'attend pas à ce que vous me donniez une fonction magique qui résout tous les problèmes en une ligne de code, mais si il existe une "bonne pratique" communément admise pour récupérer les entrées et vérifier leur validité je ne l'ai pas trouvé.

          Il y a toujours une solution, et comme scanf() est dangereuse pour les chaînes, ne gère pas les dépassement numériques et j'ajoute le cas où l'utilisateur répond "hello" quand on attend un age entier; On voit que scanf() n'est pas adaptée.
          Mais aucune fonction existante ne peut résoudre seule tous ces cas.

          Par exemple pour détecter les dépassements numériques, il y a la fonction strtol() mais elle détecte les dépassement des long pas ceux des int!
          Par exemple pour détecter les dépassements de chaines, il y a la fonction fgets() mais si ça dépasse quoi faire? Ignorer le reste de la ligne, recommencer, le traiter plus tard... Rien n'est simple.
          Donc c'est possible pour tous les cas particuliers que l'on souhaite, mais il n'existe pas de fonction "magique". Un exemple non testé pour demander un simple entier (devrait gérer la plupart des problèmes):

          int  demander( const char* texte_a_aff, int vmin, int vmax, bool* fatal ) {
              int  res;
              for (;;) {
                  printf( "%s\n", texte_a_aff );
                  char  buf[20];
                  if ( fgets( buf, 20, stdin ) == NULL ) { // commencer par lire un texte quelconque
                      perror( "erreur console" );          // erreur semblant irrécupérable
                      *fatal = true;
                      return  0;
                  }
                  const char*  p_fin;
                  errno = 0;
                  long lres = strtol( buf, &p_fin, 10 );
                  if ( errno != 0 ) {
                      perror( "saisie invalide" ); // pas un nombre, dépassement de capacité d'un long, ...
                      continue;
                  }
                  if ( lres < vmin ) {
                      fprintf( stderr, "trop petit\n" );
                      continue;
                  }
                  if ( lres > vmax ) {
                      fprintf( stderr, "trop grand\n" );
                      continue;
                  }
                  while ( isspace( (unsigned char)*p_fin ) )
                      ++p_fin;
                  if ( *pfin != '\0' ) {
                     fprintf( stderr, "des données entrées après le nombre\n" );
                  }
                  res = (int)lres;
                  break;
              }
              *fatal = false;
              return  res;
          }

          -
          Edité par Dalfab 15 février 2022 à 18:55:31

          • Partager sur Facebook
          • Partager sur Twitter

          En recherche d'emploi.

            15 février 2022 à 19:22:58

            Dalfab a écrit:

            (....) scanf() est dangereuse pour les chaînes (....)

            Il me semblait (mais je n'utiilise scanf() que très rarement) que ce code ne risquait rien
            char buf[256];
            
            scanf("%255s",buf);
            ?

            -
            Edité par edgarjacobs 15 février 2022 à 19:23:35

            • Partager sur Facebook
            • Partager sur Twitter

            On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent

              15 février 2022 à 22:06:29

              edgarjacobs a écrit:

              Dalfab a écrit:

              (....) scanf() est dangereuse pour les chaînes (....)

              Il me semblait (mais je n'utiilise scanf() que très rarement) que ce code ne risquait rien

              char buf[256];
              
              scanf("%255s",buf);

              ?

              -
              Edité par edgarjacobs il y a environ 1 heure

              Oui, je pense aussi, avec ce format, ça doit être bon. 

              scanf est une fonction pour faire du debug sous la console de toute façon. Mais oui en effet c'est pas safe.

              Microsoft a eu d'un coté une bonne idée de progressivement le bannir (en laissant quand même la possibilité de le compiler avec un define), par contre, la ou ils n'ont pas assuré, c'est qu'il y aurait du avoir un consensus de la norme sur une fonction scanf_s ou autre, équivalente de scanf mais sécurisée, portable, standard... 

              • Partager sur Facebook
              • Partager sur Twitter

              Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

                15 février 2022 à 23:11:13

                Fvirtman a écrit:

                [...]

                Microsoft a eu d'un coté une bonne idée de progressivement le bannir (en laissant quand même la possibilité de le compiler avec un define), par contre, la ou ils n'ont pas assuré, c'est qu'il y aurait du avoir un consensus de la norme sur une fonction scanf_s ou autre, équivalente de scanf mais sécurisée, portable, standard... 


                Mais il y a … il s'agit de la f[a|u]meuse annexe K de la norme : Bonds-checking Interface. Elle est optionnelle, MS est le seul à l'implémenter (à ma connaissance) ; sans doute car MS est à l'origine de l'initiative …

                Fvirtman a écrit:

                [...]

                scanf est une fonction pour faire du debug sous la console de toute façon. [...]

                scanf est surtout prévue, comme son nom l'indique, pour lire une entrée (scan) correctement formatée (f) … le fait de l'utiliser pour une entrée user est un dommage collatéral.

                -
                Edité par White Crow 15 février 2022 à 23:11:52

                • Partager sur Facebook
                • Partager sur Twitter
                  15 février 2022 à 23:45:38

                  White Crow a écrit:

                  Fvirtman a écrit:

                  [...]

                  Microsoft a eu d'un coté une bonne idée de progressivement le bannir (en laissant quand même la possibilité de le compiler avec un define), par contre, la ou ils n'ont pas assuré, c'est qu'il y aurait du avoir un consensus de la norme sur une fonction scanf_s ou autre, équivalente de scanf mais sécurisée, portable, standard... 


                  Mais il y a … il s'agit de la f[a|u]meuse annexe K de la norme : Bonds-checking Interface. Elle est optionnelle, MS est le seul à l'implémenter (à ma connaissance) ; sans doute car MS est à l'origine de l'initiative …

                  Oui dommage que ce soit les seuls à l'implémenter, et que ce ne soit pas adopté par tous. (ça ou une autre solution à ces problèmes de débordements, qui feraient consensus)

                  J'aime bien "f[a|u]meuse", en effet, chacun pourra mettre la voyelle qu'il préfère :lol:

                  • Partager sur Facebook
                  • Partager sur Twitter

                  Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

                    16 février 2022 à 19:21:47

                    White Crow a écrit:

                    scanf est surtout prévue, comme son nom l'indique, pour lire une entrée (scan) correctement formatée (f) … le fait de l'utiliser pour une entrée user est un dommage collatéral.

                    Bien dit ! Et il me semble que souvent, ces données sont dans un fichier qui a été écrit avec 'ptintf'. Je pense par exemple à un fichier de configuration : après dialogue avec l'utilisateur (dialogue qui utilise de toutes autres fonctions), le programme écrit un fichier de configuration dans un certain format ('fprintf'), et chaque fois qu'il lit le fichier de configuration, il peut utiliser 'fscanf' sans contrôles compliqués vu que c'est lui, le programme, qui l'a écrit (sauf s'il y a un bug, mais justement, mieux vaut laisser le 'fscanf' planter dans ce cas, ça révélera le bug).

                    Sous Linux, il y a plein de petits fichier de configuration qui sont créés lorsque je modifie les paramètres de certains logiciels, ou en utilisant le panneau de configuration de l'interface graphique. Je m'attends à ce que les programmes qui gèrent ça écrivent dans et lisent les fichiers à coup de 'fprintf' et de 'fscanf' sans beaucoup de contrôles. Par exemple si j'ai paramétré la couleur des cadres des fenêtres, ces couleurs sont stockées sous forme de nombres dans un fichier. Le programme qui va lire ces couleurs avant l'affichage n'a pas besoin de vérifier que les nombres sont bien des nombres : ils le sont forcément (et probablement pas besoin de vérifier que les valeurs sont valides).

                    Après, pour de petits exercices ou de petits programmes, je trouve qu'on ne devrait pas s'embêter à blinder 'scanf'. L'été 2020, à l'occasion du rapprochement de la planète Mars, que j'observe visuellement (je la dessine), j'avais écrit un petit programme qui affiche une page imprimable comportant plusieurs disques de Mars à l'échelle (juste les contours, qui ne sont pas tout à fait des cercles à cause de la phase, et présentent une certaine inclinaison). C'était pour remettre au propre mes dessins. En gros, le disque tient compte de l'échelle, de la la phase, l'inclinaison, etc. de la planète, et je n'ai plus qu'à le remplir. Je peux dessiner plusieurs disques par ligne, et plusieurs lignes par page, et c'est moi qui lui dit combien. Bref, ce programme lit un fichier où j'inscris tous ces paramètres. Eh bien je ne me suis absolument pas embêté à blinder le 'fscanf'. S'il y a une erreur dans le fichier d'entrée, ça plante, je corrige et tout va bien. J'ai fait ça pour gagner du temps, pas me prendre la tête... ;)

                    Tout ça pour dire que 'scanf' me paraît utile au-delà de tests en console. Certes, pas dans n'importe quel contexte.

                    -
                    Edité par robun 19 février 2022 à 20:50:14

                    • Partager sur Facebook
                    • Partager sur Twitter
                      19 février 2022 à 18:18:55

                      C'est à dire que "blinder", c'est un vrai boulot.

                      Et ça veut dire qu'on passe d'une spécification genre "le programme demande un entier entre 1 et 5 et calcule ceci cela", à une longue dissertation sur ce que le programme devra faire exactement si on tape autre chose qu'un entier entre 1 et 5, ou si le nombre est suivi d'autre chose sur la même ligne, etc.

                      Donc, le plus simple pour des exercices, c'est d'acter qu'on est dans le cadre d'un exercice et qu'on suppose que l'utilisateur fera exactement ce qu'on lui dit. Sinon, comportement indéfini (en C, ça ne dépare pas).

                      -
                      Edité par michelbillaud 19 février 2022 à 18:20:50

                      • Partager sur Facebook
                      • Partager sur Twitter
                        20 février 2022 à 1:26:53

                        Ça peut même être intéressant de voir comment ça plante si on entre des conneries.
                        • Partager sur Facebook
                        • Partager sur Twitter

                        Le Tout est souvent plus grand que la somme de ses parties.

                          20 février 2022 à 6:25:59

                          C'est une expérience qui avait été menée sur les utilitaires Unix genre compilateurs, qui attendaient un texte structuré  : leur refiler des données aléatoires pour voir ce que ça fait.

                          Le résultat avait été, disons, intéressant.

                          -
                          Edité par michelbillaud 20 février 2022 à 6:26:52

                          • Partager sur Facebook
                          • Partager sur Twitter
                            20 février 2022 à 7:31:34

                            Ça finit souvent par le message: collect2.exe: error: ld returned 1 exit status
                            Mais mon compilateur C++ est intelligent. :)
                            Il compile correctement du C et ça s'exécute normalement.

                            Et comme tout le monde le sait: garbage in, garbage out.

                            -
                            Edité par PierrotLeFou 20 février 2022 à 7:34:26

                            • Partager sur Facebook
                            • Partager sur Twitter

                            Le Tout est souvent plus grand que la somme de ses parties.

                              23 février 2022 à 8:18:45

                              J'ai retrouvé la référence

                              "An Empirical Study of the Reliability of Unix Utilities", de Miller et al., CACM 1990

                              https://www.researchgate.net/publication/220422300_An_Empirical_Study_of_the_Reliability_of_UNIX_Utilities

                              Pionnier de la technique dite "fuzz testing" . On envoie des donnees aleatoires, et si ca plante, on recherche dans le code ce qui a fait planter plutôt qu'arrêter proprement.

                              Tests faits sur 88 utilitaires x 7 systèmes différents.

                              Resultat: c'est la cata.

                              Analyse des causes : buffer overflow, pointeurs invalides, etc. Les problèmes habituels des  machins  écrits en C, what did you expect?

                              -
                              Edité par michelbillaud 23 février 2022 à 8:32:10

                              • Partager sur Facebook
                              • Partager sur Twitter

                              Scanf sur VisualStudio

                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                              • Editeur
                              • Markdown