Forum Programmation.c Sécurité lors de la déclaration d'un pointeur

Posté par  . Licence CC By‑SA.
Étiquettes : aucune
1
8
nov.
2019

Bonjour,

J'ai ce petit code ou je déclare deux pointeurs tableau je veux savoir si juste la déclaration et l'affectation peut donner le mauvais accès à la mémoire réservée par ce pointeur ou c'est après la mauvaise utilisation de malloc qui peut causer le vol de données ou l'injection de mauvaise données.

Mon code C:

void mafonction(int* tab1, int* tab2, int len) 
{ 

int i; 
for(i = 0; i < len; i++) 
tab1[i] = tab2[i] ; 

}

Merci d'avance.

  • # Pointeur

    Posté par  . Évalué à 1.

    Ton code est l'implémentation de la fonction memcpy de la librairie standard du C. Elle ne contient pas d'erreur.
    C'est au développeur qui va l'utiliser de faire attention à ne pas faire de conneries en utilisant cette fonction. Il doit allouer correctement la mémoire, et passer le bon paramètre "len" sinon effectivement ça peut poser des problèmes de sécurité.

    • [^] # Re: Pointeur

      Posté par  (site web personnel) . Évalué à 2.

      Attention à son emploi cependant. Citons la page de manuel memcpy(3) :

      SYNOPSIS
             #include <string.h>
      
             void *memcpy(void *dest, const void *src, size_t n);
      
      DESCRIPTION
             The  memcpy()  function  copies  n bytes from memory area src to memory
             area dest.  The memory areas must not overlap.  Use memmove(3)  if  the
             memory areas do overlap.
      

      Debian Consultant @ DEBAMAX

  • # 'pouvez répéter la questioooon ?

    Posté par  . Évalué à 3. Dernière modification le 08 novembre 2019 à 18:17.

    donner le mauvais accès à la mémoire réservée par ce pointeur

    un pointeur en soi ne réserve pas de mémoire :

    int main()
    {
        int * input = NULL, 
        int *output = NULL;
        mafonction(output, input, 42);
    }

    Ici aucune mémoire n'a été allouée aux pointeurs input et output donc l'appel à mafonction va déréférencer un pointeur NULL ce qui va engendrer un comportement non-défini, ce qui est à-peu-près passible de la peine capitale pour un programmeur C.

    (Là je suis sympa, il me semble que Linux est pas trop méchant et plante le programme direct quand le pointeur est NULL, par contre si le pointeur n'avait pas été initialisé du tout, c'était des feux d'artifice)
    Tu peux décider d'utiliser de la mémoire sur la pile pour appeler ta fonction:

    int main()
    {
        int input[42]; 
        int output[42];
        mafonction(output, input, 42);
    }

    Ici, on a bien de la mémoire pour l'input et l'output (mémoire de la pile), mais l'appel à mafonction va engendrer la lecture du tableau input qui n'a pas été initialisé, ce qui va provoquer un comportement non-défini, ce qui est à-peu-près passible de la peine capitale pour un programmeur C.

    Tu peux corriger en initialisant les données lues :

    int main()
    {
        int input[] = {0, 1 , 42}; 
        int output[42];
        mafonction(output, input, 42);
    }

    Là, c'est bien on a initialisé les données, mais on a dépassé la taille du tableau input, et on commence à lire et à écrire des données en débordant du tableau , ce qui va provoquer un comportement non-défini, ce qui est à-peu-près passible de la peine capitale pour un programmeur C.

    On peut s'en sortir en contraignant la taille de ton tableau de sortie.

    int main()
    {
        int input[] = {0, 1 , 42}; 
        int output[sizeof(input) / sizeof(int)];//does C11+ have constexpr?
        mafonction(output, input, sizeof(input) / sizeof(int);
    }

    Tu peux aussi décider d'allouer un tableau sur le tas et bien penser à initialiser les données et tout et tout.

    int main()
    {
        cont size_t length = 42;
        int *input  = malloc(length * sizeof(int)}; 
        for (size_t i = 0; i!= length;++i){input[i] = length - i;}    
        int output  = malloc(length * sizeof(int)}; 
        mafonction(output, input, length);
    }

    Mais on aura oublié d'invoquer free afin de libérer la mémoire allouée pour input et output, créant une fuite de mémoire qui est à-peu-près passible de la peine capitale pour un programmeur C.

    C'est le côté fun du C, on donne au programmeur une infinité d'opportunités de se prendre les pieds dans le tapis.

    Bon 'dredi !

    • [^] # Re: 'pouvez répéter la questioooon ?

      Posté par  . Évalué à 2.

      le dernière exemple est faux, output devrait être un pointeur vers int pas juste un int, mea maxima culpa.

    • [^] # Re: 'pouvez répéter la questioooon ?

      Posté par  (site web personnel) . Évalué à 3.

      Mais on aura oublié d'invoquer free afin de libérer la mémoire allouée pour input et output, > créant une fuite de mémoire qui est à-peu-près passible de la peine capitale pour un programmeur C.

      Ben non.
      Vous pouvez lancer ce programme autant de fois que vous voulez sous Linux, sous Windows ou BSD, vous ne verrez pas la moindre fuite de mémoire.

      Attention, sizeof est un opérateur, pas une fonction. Il ne devrait y avoir de parenthèses que si l'on cible un type et non une variable (unary-expression).

      sizeof array / sizeof array[0]
      • [^] # Re: 'pouvez répéter la questioooon ?

        Posté par  . Évalué à 3.

        Vous pouvez lancer ce programme autant de fois que vous voulez sous Linux, sous Windows ou BSD, vous ne verrez pas la moindre fuite de mémoire

        J'insiste, il y aura une fuite de mémoire à chaque exécution te ce programme, cette fuite sera résorbée par le système d'exploitation à chaque fin d'exécution, mais fuite de mémoire il y aura.

        Une fuite de mémoire est caractérisée par l'incapacité du programme a libérer lui-même la mémoire qu'il a allouée. Dans l'exemple que je donne, une fois qu'on a quitté la fonction main() on perd l'adresse de la mémoire allouée, ce qui caractérise cette fuite. Je reconnais que l'exemple est un peu tordu dans le sens où, de toutes façons, on ne peut pas faire grand chose en C une fois que le main() est terminé, mais si le code dans le main() était dans une fonction f() qu'on appelait à la chaîne, peut-être serais-tu plus enclin à la voir.

        • [^] # Re: 'pouvez répéter la questioooon ?

          Posté par  (site web personnel) . Évalué à 3.

          J'insiste, il y aura une fuite de mémoire à chaque exécution te ce programme, cette fuite sera résorbée par le système d'exploitation à chaque fin d'exécution, mais fuite de mémoire il y aura.

          Vous essayez surtout de trouver une définition de la "fuite mémoire" qui vous donnerait raison.

          Je reconnais que l'exemple est un peu tordu dans le sens où, de toutes façons, on ne peut pas faire grand chose en C une fois que le main() est terminé,

          On peut laisser traîner des choses comme des tubes nommés.

          Attention, j'ai bien dit "Sous Linux etc.", ce n'est absolument pas le cas sur la plupart des OS dit "temps-réel" - surtout ceux qui tournent sur des archi sans MMU. -

          mais si le code dans le main() était dans une fonction f() qu'on appelait à la chaîne, peut-être serais-tu plus enclin à la voir.

          T'inquiète, je sais ce qu'est une fuite mémoire, une vraie. J'ai vu des gens coder en Java.

    • [^] # Re: 'pouvez répéter la questioooon ?

      Posté par  (site web personnel) . Évalué à 3.

      Ici, on a bien de la mémoire pour l'input et l'output (mémoire de la pile), mais l'appel à mafonction va engendrer la lecture du tableau input qui n'a pas été initialisé, ce qui va provoquer un comportement non-défini,

      Non, la valeur est «indeterminée», ce n'est pas un cas d'«undefined behavior».

      • [^] # Re: 'pouvez répéter la questioooon ?

        Posté par  . Évalué à 1.

        J'insiste, section A.6.2 Undefined behavior

        The behavior in the following circumstances is undefined:
        […]
        * The value of an uninitialized object that has automatic storage duration is used before a value is assigned (3.5.7).

        Pour les modernes, c'est aussi valable en C++ (j'ai la flemme de sortir le draft pour 17, mais c'est pareil), 8.5 alinea 12

        If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases:

        les exceptions en question sont des chars.

        • [^] # Re: 'pouvez répéter la questioooon ?

          Posté par  (site web personnel) . Évalué à 2.

          J'insiste, section A.6.2 Undefined behavior

          Qui se réfère à ceci,

          if an object that has automatic storage duration is not initialized explicitly, its value is indeterminate

          même formule en C11:

          6.7.8 Initialization
          10 If an object that has automatic storage duration is not initialized explicitly, its value is
          indeterminate.

          La "synthèse" liste ce cas, tout en faisant référence à des articles … où l'UB n'est pas indiquée.

          Je ne connais aucun compilo qui s'autorise à invoquer un démon nasal dans ce cas, et où dev/mem ne fonctionnerait pas.

          • [^] # Re: 'pouvez répéter la questioooon ?

            Posté par  . Évalué à 1.

            La "synthèse" liste ce cas, tout en faisant référence à des articles … où l'UB n'est pas indiquée.

            Je n'aime pas la façon dont le standard C89 est rédigé, mais je ne pense pas qu'A.6.2 soit une synthèse, mais plutôt une espèce de section spéciale UB (bien que UB soit mentionné à plein d'autres endroits)

            Je ne connais aucun compilo qui s'autorise à invoquer un démon nasal dans ce cas, et où dev/mem ne fonctionnerait pas.

            Je ne prendrais pas ça à la légère, ce type d'UB a été défini avec beaucoup d'attention et si même les dernières version du standard C++ la gardent, c'est que les compilateurs en profitent pour faire des optimisations de code assez tordues. Donc, pas de démon nasal, mais un code qui risque d'être surprenant au moment où tu t'y attends le moins.

            • [^] # Re: 'pouvez répéter la questioooon ?

              Posté par  (site web personnel) . Évalué à 2.

              Je n'aime pas la façon dont le standard C89 est rédigé,

              Justement dans C11, c'est rédigé différemment, lorsque l'on a affaire à un UB, c'est dit explicitement dans l'article concerné.

              Je ne prendrais pas ça à la légère,

              Ce n'est certes pas à prendre à la légère, le contenu des tableaux dépend du sens du vent. Mais c'est parfois voulu.

              c'est que les compilateurs en profitent pour faire des optimisations de code assez tordues.

              C'est le cas de la plupart, voire toutes, les UB déclarées sous GCC. Il se permet souvent de zapper tout le code qui contient une UB, mais jamais dans ce cas précis, c'est à dire avec un tableau.
              Lorsque la variable peut-être collée dans un registre, il va se comporter différemment.
              - mais je crois que la norme mentionne ce cas précis. -

              • [^] # Re: 'pouvez répéter la questioooon ?

                Posté par  . Évalué à 1. Dernière modification le 09 novembre 2019 à 10:23.

                Justement dans C11, c'est rédigé différemment, lorsque l'on a affaire à un UB, c'est dit explicitement dans l'article concerné.

                Le standard C89 a 89 occurences (😃) de l'expression behavior is undefined, et ce tout au long du document, par exemple en 3.3.3.2 (Address and indirection operators) alinéa 2

                If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

                Par ailleurs l'annexe A.6.2 du C89 (Portability Issues -> Undefined behavior) est aussi présente dans C11, elle a simplement été déplacée en J.2. Le paragraphe sur l'utilisation des variables automatiques dont la valeur est indéterminée, y est toujours présent.
                Par contre, là où tu sembles avoir raison, c'est que l'annexe J est informative, pas normative : elle devrait permettre d'interpréter le standard mais n'a pas valeur de norme. Étrange quand même de garder ça pendant plus de 30 ans dans le document si c'est faux ou douteux (c'est dans le dernier draft de C18)

                Lorsque la variable peut-être collée dans un registre, il va se comporter différemment.
                - mais je crois que la norme mentionne ce cas précis. -

                je pense que tu fais référence à 6.3.2.1, alinéa 2 :

                if the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.