98 votes

Pourquoi la terminaison de strncpy n'est pas nulle ?

strncpy() est censé protéger contre les débordements de tampon. Mais s'il empêche un débordement sans terminaison nulle, il est fort probable qu'une opération de chaîne ultérieure déborde. Donc pour se protéger contre cela, je me retrouve à faire :

strncpy( dest, src, LEN );
dest[LEN - 1] = '\0';

man strncpy donne :

El strncpy() est similaire, sauf que pas plus de n octets de src sont copiés. Ainsi, s'il n'y a pas d'octet nul parmi les premiers n octets de src le résultat ne sera pas terminé par un caractère nul.

Sans mettre fin à quelque chose d'apparemment innocent comme.. :

   printf( "FOO: %s\n", dest );

...pourrait s'écraser.


Existe-t-il des alternatives meilleures et plus sûres pour strncpy() ?

1 votes

Notez que sur MacOS X (BSD), la page de manuel dit (de ' extern char *strncpy(char * restrict s1, const char * restrict s2, size_t n); ') : La fonction strncpy() copie au maximum n caractères de s2 dans s1. Si la longueur de s2 est inférieure à n caractères, le reste de s1 est rempli de ``. \0 des personnages. Sinon, s1 n'est pas terminé.

0 votes

Cela ne devrait-il pas être dest[LEN-1] = ' ? \0 ' ; ?

2 votes

Voici comment je pense que nous ferions une copie de la chaîne:int LEN = src.len ; str* dest = new char[LEN+1] ; strncpy( dest, src, LEN ) ; dest[LEN] = ' \0 ' ;

61voto

Ernelli Points 2133

strncpy() n'est pas destiné à être utilisé comme un moyen de sécurité strcpy() Il est censé être utilisé pour insérer une chaîne de caractères au milieu d'une autre.

Toutes ces fonctions "sûres" de manipulation des chaînes de caractères, telles que snprintf() y vsnprintf() sont des correctifs qui ont été ajoutés dans des normes ultérieures pour atténuer les exploits de débordement de tampon, etc.

Wikipedia mentionne strncat() comme une alternative à l'écriture de votre propre coffre-fort strncpy() :

*dst = '\0';
strncat(dst, src, LEN);

EDIT

J'ai manqué ça. strncat() dépasse LEN caractères lors de la terminaison nulle de la chaîne si elle est plus longue ou égale à LEN caractères.

De toute façon, l'intérêt d'utiliser strncat() au lieu d'une solution maison telle que memcpy(..., strlen(...)) /ou autre est que l'implémentation de strncat() peuvent être optimisés pour une cible/plateforme dans la bibliothèque.

Bien sûr, vous devez vérifier que dst contient au moins le nullchar, donc l'utilisation correcte de strncat() serait quelque chose comme :

if (LEN) {
    *dst = '\0'; strncat(dst, src, LEN-1);
}

J'admets aussi que strncpy() n'est pas très utile pour copier une sous-chaîne dans une autre chaîne, si le src est plus court que n chars, la chaîne de destination sera tronquée.

34 votes

"il est censé être utilisé pour insérer une chaîne au milieu d'une autre" - non, il est destiné à écrire une chaîne dans un champ de largeur fixe, comme dans une entrée de répertoire. C'est pourquoi il remplit le tampon de sortie avec NUL si (et seulement si) la chaîne source est trop courte.

5 votes

Comment le fait de définir *dst=' \0 rendrait la situation plus sûre ? Il y a toujours le problème original de vous permettre d'écrire au-delà de la fin du tampon cible.

0 votes

Parce qu'il utilise strncat qui concatène sur la chaîne de destination.

40voto

Jonathan Leffler Points 299946

À l'origine, le 7ème édition UNIX (voir DIR(5)) avait des entrées de répertoire qui limitaient les noms de fichiers à 14 octets ; chaque entrée dans un répertoire consistait en 2 octets pour le numéro d'inode plus 14 octets pour le nom, avec un padding nul à 14 caractères, mais pas nécessairement terminé par un null. Je pense que strncpy() a été conçu pour fonctionner avec ces structures de répertoire - ou, du moins, il fonctionne parfaitement pour cette structure.

Pensez-y :

  • Un nom de fichier de 14 caractères n'avait pas de terminaison nulle.
  • Si le nom était plus court que 14 octets, il était paddé de manière à obtenir la longueur totale (14 octets).

C'est exactement ce que l'on obtiendrait en :

strncpy(inode->d_name, filename, 14);

Donc, strncpy() était parfaitement adapté à son application de niche originale. Ce n'est que par coïncidence qu'il s'agissait d'empêcher les débordements de chaînes de caractères à terminaison nulle.

(Notez que le remplissage de null jusqu'à la longueur 14 n'est pas une surcharge sérieuse - si la longueur du tampon est de 4 KB et que tout ce que vous voulez est d'y copier 20 caractères en toute sécurité, alors les 4075 null supplémentaires sont une surcharge sérieuse, et peuvent facilement conduire à un comportement quadratique si vous ajoutez de façon répétée du matériel à un long tampon).

4 votes

Cette situation particulière peut être obscure, mais il n'est pas vraiment rare d'avoir des structures de données avec des champs de chaînes de longueur fixe qui sont à remplissage nul mais pas à terminaison nulle. En effet, si l'on stocke des données à format fixe, c'est souvent la manière la plus efficace de le faire.

27voto

StampedeXV Points 1221

Il existe déjà des implémentations open source comme strlcpy qui font des copies sûres.

http://en.wikipedia.org/wiki/Strlcpy

Dans les références, il y a des liens vers les sources.

1 votes

Sans oublier qu'il est portable, rapide et fiable. Vous pouvez toujours en faire un mauvais usage, mais le risque est beaucoup plus faible. IMO, strncpy devrait être déprécié et remplacé par la même fonction appelée dirnamecpy ou quelque chose comme ça. strncpy n'est pas une copie de chaîne sûre et ne l'a jamais été.

8voto

Adam Liss Points 27815

Certaines nouvelles alternatives sont spécifiées dans le document ISO/IEC TR 24731 (Check https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/coding/317-BSI.html pour plus d'informations). La plupart de ces fonctions prennent un paramètre supplémentaire qui spécifie la longueur maximale de la variable cible, s'assurent que toutes les chaînes de caractères sont terminées par des caractères nuls et ont des noms qui se terminent en _s (pour "safe" ?) pour les différencier de leurs versions antérieures "non sécurisées". 1

Malheureusement, ils sont encore en train de gagner du support et peuvent ne pas être disponibles avec votre ensemble d'outils particulier. Les versions ultérieures de Visual Studio afficheront des avertissements si vous utilisez les anciennes fonctions non sécurisées.

Si vos outils Ne le fais pas. supportent les nouvelles fonctions, il devrait être assez facile de créer vos propres enveloppes pour les anciennes fonctions. Voici un exemple :

errCode_t strncpy_safe(char *sDst, size_t lenDst,
                       const char *sSrc, size_t count)
{
    // No NULLs allowed.
    if (sDst == NULL  ||  sSrc == NULL)
        return ERR_INVALID_ARGUMENT;

   // Validate buffer space.
   if (count >= lenDst)
        return ERR_BUFFER_OVERFLOW;

   // Copy and always null-terminate
   memcpy(sDst, sSrc, count);
   *(sDst + count) = '\0';

   return OK;
}

Vous pouvez modifier la fonction en fonction de vos besoins, par exemple, pour toujours copier la plus grande partie possible de la chaîne sans déborder. En fait, l'implémentation de VC++ peut le faire si vous passez la fonction _TRUNCATE comme le count .


1 Bien sûr, vous devez toujours être précis quant à la taille du tampon cible : si vous fournissez un tampon de 3 caractères mais que vous dites à strcpy_s() il y a de la place pour 25 caractères, vous avez encore des problèmes.

0 votes

Vous ne pouvez pas légalement définir une fonction dont le nom commence par str*, cet "espace de noms" est réservé en C.

2 votes

Mais le comité ISO C le peut - et l'a fait. Voir aussi : stackoverflow.com/questions/372980/

0 votes

@Jonathan : Merci pour la référence croisée à votre propre question, qui fournit beaucoup d'informations supplémentaires utiles.

8voto

Liran Orevi Points 2126

Strncpy est plus sûr contre les attaques par débordement de pile par le utilisateur de votre programme, il ne vous protège pas contre les erreurs vous le programmeur, comme l'impression d'une chaîne de caractères non nulle, de la manière que vous avez décrite.

Vous pouvez éviter le plantage dû au problème que vous avez décrit en limitant le nombre de caractères imprimés par printf :

char my_string[10];
//other code here
printf("%.9s",my_string); //limit the number of chars to be printed to 9

0 votes

L'utilisation du champ de précision pour limiter le nombre de caractères imprimés par l'utilisateur. %s doit être l'une des caractéristiques les plus obscures du C.

0 votes

@DavidThornley C'est documenté très clairement dans K&R sous sprintf.

0 votes

@weston : Et dans Harbison & Steele, qui est ce que j'ai ici au travail. Maintenant, dans quels livres populaires de C, autres que ces deux-là, est-ce que ceci est mentionné ? Chaque caractéristique devrait être mentionnée dans K&R et H&S (et est mentionnée dans la norme), donc si c'est la norme de l'obscurité, il n'y a pas de caractéristiques obscures.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X