• [^] # Re: Dépassement de tampon

    Posté par . En réponse à la dépêche Naissance d'un géant : Java. Évalué à 4.

    C'est d'ailleurs pour ça qu'entre la page de manuel de (disons) scanf, et ce qu'on peut trouver sur le net pour expliquer son fonctionnement, ça peut donner ça : http://xrenault.developpez.com/tutoriels/c/scanf/

    Par là je veux dire: certaines fonctions de la bibliothèque standard sont juste mal foutues, point. À l'époque elles avaient peut-être du sens, mais le langage C est loin d'être simple (enfin, sa syntaxe et sa grammaire le sont, mais pas son utilisation).

    Ensuite, concernant strncpy/strncat: « il suffit de lire la doc » ? Vraiment ? C'est pour ça que, malgré la ressemblance entre les deux noms, chaque fonction a une page de manuel super étendue je suppose, avec des comportements différents pour que ce soit encore plus rigolo (et encore, elles étaient bien plus succintes il y a dix ans) ?

    Ça donne de joyeux bouts de code du genre :

    char *src;
    char dst[size+1];
    char buf[K*size];
    size_t len = 0;
    /* ... src est alloué, initialisé, etc. quelque part ici ... */
    /* strncpy() copie au maximum "size" caractères de la chaîne src vers la chaîne dst. 
     * Mais attention ! Si la longueur de src dépasse "size", alors il n'y aura pas de '2878b2550166f39398d09567f242898acfb5ed97' 
     * final ! Il faudra le rajouter à la main du coup.
     */
    len = strlen(src); /* il faut espérer que src est bien terminé par '0円'... */
    strncpy(dst,src,size); 
    if (size <= len) 
     dst[size] = '\0';
    /* ... */
    process(buf,...); /* traitement de buf, qui contient une certaine chaîne de caractères au retour de l'appel */
    len = strlen(buf); /* il faut espérer que buf est bien terminé par '0円'... */
    /* Rigolons un bon coup. strncat() prend un taille en troisième paramètre. 
     * Si celle-ci est plus grande que la taille de ce qu'il faut concaténer, 
     * "tout va bien" : strncat rajoute un '0円' juste après le dernier caractère copié. 
     * Bon bien sûr, maintenant, j'ai beau avoir demandé N caractères max à concaténer, 
     * si ma chaîne source contient plus que n caractères, alors strncat va écrire n+1 
     * caractères.
     */
    strncat(buf,dst,K*size-len-1); /* Comme quoi, c'était pas si compliqué ! */
    

    et faire un truc du genre
    char *src;
    char dst[size+1];
    char buf[K*size];
    size_t len1 = 0, len2 = 0;
    /* ... src est alloué, initialisé, etc. quelque part ici ... */
    len1 = strlen(src);
    memcpy(dst,src,size);
    dst[size] = '\0';
    /* ... */
    process(buf,...); 
    len2 = strlen(buf); 
    size_t tmp = len1+len2+1 <= K*size ? len1+len2 : K*size-1;
    memcpy(buf,dst,tmp);
    buf[tmp] = '\0';
    

    Ou encore :

    char *src;
    char dst[size+1];
    char buf[K*size];
    size_t len1 = 0, len2 = 0;
    /* ... src est alloué, initialisé, etc. quelque part ici ... */
    len1 = strlen(src);
    memcpy(dst,src,size);
    dst[size] = '\0';
    /* ... */
    process(buf,...); 
    len2 = strlen(buf); 
    if (len2+len1 < K*size)
     strcat(buf,dst); /* La concaténation est sûre */
    else /* K*size < len1+len2 */
    {
     memcpy(buf+len2,dst,K*size-len2-1);
     buf[K*size-1] = '\0';
    }
    

    Ah si: au moins, dans le deuxième cas, tu ne te poses pas la question de « est-ce que c'est strncat ou strncpy qui rajoute un 0円 additionnel ? »: dans tous les cas tu as un comportement éprouvé (i.e. tu ne manipules que des bytes). Bon bien entendu, t'es niqué quand le byte fait 1 octet, mais que le char en fait 2 (comme ça peut arriver sur certains DSP).

    Ce n'est pas pour rien que les dévs OpenBSD on rajouté strlcat/strlcpy: non seulement le comportement des fonctions est homogène, mais en plus elles renvoient une vraie information utile : le nombre de caractères copiés (au lieu du pointeur destination que tu as toi-même passé en paramètre au moment de l'appel...).

    char *src;
    char dst[size+1];
    char buf[K*size];
    /* ... src est alloué, initialisé, etc. quelque part ici ... */
    size_t src_len = strlen(src);
    size_t cpy_res = strlcpy(dst,src,size); /* J'ai la garantie que dst est a un '0円' */
    if (cpy_res < src_len) {
     /* Gestion de la troncature */
    }
    /* ... */
    process(buf,...);
    size_t buf_len = strlen(buf); 
    size_t cat_res = strlcat(buf, dst, K*size);
    if (cat_res >= buf_len) {
     /* Gestion de la troncature */
    }
    

    (Il est tard au moment où j'écris ces lignes, et il y a de fortes chances que j'aie fait une erreur : je n'ai pas compilé pour vérifier)

    Donc voilà. Je suis surpris que tu te permettes de dire que « y'à qu'à bien savoir compter », alors que même les développeurs d'environnements qui se veulent sécurisés disent que strcpy/strcat Vs strncpy/strncat est une fausse « sécurité ». Je me souviens avoir entendu et lu Marc Espie à plusieurs reprises concernant le fait que, même plein de bonne volonté, le dév qui cherche à remplacer ses strcpy/strcat par strncpy/strncat va souvent se planter à cause de la différence de sémantique dans les deux fonctions. Alors qu'au moins avec strcpy/strcat, la règle est simple : tu vérifies la taille de tes buffers avant de faire quoi que ce soit (et bien entendu, avec strlcat/strlcpy, c'est ENCORE plus simple).