lundi 25 juin 2012

La vérité sur `strncpy' !

Edit : 60 vues depuis l'ouverture du blog, on est dans la régularité. Vous pouvez laisser un commentaire, ça ne mange pas de pain et c'est toujours sympa à voir. Même si vous n'avez rien d'intéressant à dire.

On voit très souvent, sur des forums communautaires, des membres qui conseillent aux autres d'utiliser strncpy à la place de strcpy pour des raisons de sécurité. L'article que je vais vous présenter aujourd'hui tend à prétendre le contraire. À vous de vous forger votre avis à partir des arguments des deux camps.

Un problème de débordement de tampon


Ce que reprochent ces membres à strcpy, ce sont les problèmes de dépassements de tampons (overflow) qui sont engendrés par l'utilisation naïve de cette fonction de copie.

char const *const s1 = "hello";
char              s2[3];
strcpy(s2, s1);


Dans le code ci-dessus, un dépassement mémoire est causé par la taille insuffisante de la chaîne s2.

La solution de facilité, strncpy

 

Pour prévenir ce débordement, beaucoup vont suggérer l'utilisation de strncpy, de cette manière :

char const *const s1 = "hello";
char              s2[3];
strncpy(s2, s1, sizeof s2);


Grâce au paramètre de taille, demandé par strncpy, la fonction va pouvoir vérifier que la chaîne ne déborde pas.

Un paramètre presque dérisoire...


Seulement, ce paramètre de taille est dérisoire. Tout simplement parce que vous devenez garder la trace de beaucoup de paramètres pour éviter le plantage de ces fonctions de manipulations des chaînes de caractère natives : le positionnement du caractère de fin de chaîne en est un exemple. Comme nous le verrons tout à l'heure, strncpy ne gère même pas correctement cet élément...

Par exemple, si votre chaîne de caractère est allouée statiquement, vous pouvez faire quelque chose comme le code ci-dessous pour utiliser strcpy :

if (strlen(s1) + 1 > sizeof s2)    handle_errors();
else                               strcpy(s2, s1);


Simplement, ça peut vous paraître idiot de réinventer la roue, puisque strncpy semble faire la même chose

Une fonction préjudiciable...


La gestion des erreurs

 

Le problème est que strncpy ne gère pas les erreurs. Il est impossible de savoir si la fonction a échoué car la destination était trop petite.

Le positionnement du caractère de fin de chaîne


De plus (et c'est inquiétant), aucun caractère de fin de chaîne n'est ajouté si on n'arrive pas à terme de la chaîne. Si on utilise d'autres fonctions de manipulations de chaînes, on se rend compte qu'elles prennent fortement en compte le positionnement du caractère de fin de chaîne. On a donc une forte vague d'erreurs qui s'enchaînent les unes les autres, et qui peuvent être difficiles à détecter.

On peut alors reprendre notre condition pour savoir si la taille n'est pas suffisante.

if (strlen(s1) + 1 > sizeof s2)    handle_errors();
else                               strncpy(s2, s1, sizeof s2);


Mais strncpy revient alors à utiliser strcpy. Pour remédier à ce problème de zéro, on peut le placer manuellement après l'appel de strncpy.

strncpy(s2, s1, s2_size);
s2[s2_size - 1] = 0;


Mais, là encore, on a un problème si s2_size vaut zéro... Une condition de plus à ajouter, ce qui est préjudiciable dans des cas critiques...

Les performances


strncpy est souvent contraignant pour les performances (pour que la fonction puisse tester la taille correctement, et parce qu'elle remplit les cases inutilisées par des zéros...).

La sécurité

 

Prétendu symbole de sécurité, la fonction est pourtant sujette à l'exploitation du non-positionnement du caractère de fin de chaîne. Cela pourrait faire l'objet d'un autre article. En attendant, je vous renvoie à cette page.

Conclusion

 

Vous l'avez compris, strncpy possède également des défauts. Je ne défends pas particulièrement son utilisation, mais, du moins, il vous serait intéressant d'éviter la propagation de l'idée reçu selon laquelle strncpy est une fonction sûre et efficace.

Mais alors qu'utiliser ? Reste encore snprintf (et oui, c'est du C99 !), qui a un système, certes simple, mais présent, de gestion des erreurs. Ce n'est pas très beau à utiliser, et c'est plus lent sur des petites chaînes. Mais ça me semble être une solution qui offre un bon compromis.

2 commentaires:

  1. J'ai rien à dire, mais je laisse un com'!

    Non je plaisante, merci pour cet article ça éclaircit un peu, et merci pour le lien détaillé
    J'apprends des choses alors que tu es plus jeune que moi ._. miséricorde

    RépondreSupprimer
  2. next level, le C11 :p

    //C11, safe version of strcpy
    errno_t strcpy_s(char * restrict s1,
    rsize_t s1max,
    const char * restrict s2);

    RépondreSupprimer