web-dev-qa-db-fra.com

Pourquoi strncpy ne se termine pas nul?

strncpy() protège soi-disant des 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 suivante va déborder. Donc, pour me protéger contre cela, je me retrouve à faire:

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

man strncpy Donne:

La fonction 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 n premiers octets de src, le résultat ne se terminera pas par null.

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

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

... pourrait se bloquer.


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

71
Timothy Pratley

strncpy n'est pas destiné à être utilisé comme un coffre-fort strcpy, il est censé être utilisé pour insérer une chaîne au milieu d'une autre.

Toutes ces fonctions de gestion de chaînes "sûres" telles que snprintf et vsnprintf sont des correctifs qui ont été ajoutés dans des normes ultérieures pour atténuer les exploits de dépassement de tampon, etc.

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

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

[~ # ~] modifier [~ # ~]

J'ai raté que strncat dépasse les caractères LEN lors de la terminaison null de la chaîne si elle est plus longue ou égale à celle du caractère LEN.

Quoi qu'il en soit, l'intérêt d'utiliser strncat au lieu de toute solution locale telle que memcpy (..., strlen (...))/quoi que ce soit, c'est que l'implémentation de strncat pourrait être optimisée pour la cible/la plate-forme 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 également 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 char, la chaîne de destination sera tronquée.

41
Ernelli

Il existe déjà des implémentations open source comme strlcpy qui effectuent une copie en toute sécurité.

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

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

24
StampedeXV

À l'origine, le système de fichiers 7th Edition 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 se composait de 2 octets pour le numéro d'inode plus de 14 octets pour le nom, null complété à 14 caractères, mais pas nécessairement terminé par null. Je pense que strncpy() a été conçu pour fonctionner avec ces structures de répertoires - ou, au moins, cela fonctionne parfaitement pour cette structure.

Considérer:

  • Un nom de fichier de 14 caractères n'était pas terminé par null.
  • Si le nom était plus court que 14 octets, il était null rempli à pleine longueur (14 octets).

C'est exactement ce qui serait réalisé par:

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

Ainsi, strncpy() était idéalement adaptée à son application de niche d'origine. Il ne s'agissait que par coïncidence d'empêcher les débordements de chaînes terminées par null.

(Notez que le remplissage nul jusqu'à la longueur 14 n'est pas une surcharge sérieuse - si la longueur du tampon est de 4 Ko et que tout ce que vous voulez est de copier en toute sécurité 20 caractères dedans, alors les 4075 null supplémentaires sont une surcharge excessive et peuvent facilement conduire à un comportement quadratique si vous ajoutez à plusieurs reprises du matériel à un long tampon.)

24
Jonathan Leffler

Strncpy est plus sûr contre les attaques de débordement de pile par le tilisateur de votre programme, il ne vous protège pas contre les erreurs vous le programmeur, comme l'impression d'une terminaison non nulle chaîne, comme vous l'avez décrit.

Vous pouvez éviter le plantage du 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
8
Liran Orevi

Utilisez strlcpy(), spécifié ici: http://www.courtesan.com/todd/papers/strlcpy.html

Si votre libc n'a pas d'implémentation, essayez celle-ci:

size_t strlcpy(char* dst, const char* src, size_t bufsize)
{
  size_t srclen =strlen(src);
  size_t result =srclen; /* Result is always the length of the src string */
  if(bufsize>0)
  {
    if(srclen>=bufsize)
       srclen=bufsize-1;
    if(srclen>0)
       memcpy(dst,src,srclen);
    dst[srclen]='\0';
  }
  return result;
}

(Écrit par moi en 2004 - dédié au domaine public.)

5
alex tingle

Au lieu de strncpy(), vous pouvez utiliser

snprintf(buffer, BUFFER_SIZE, "%s", src);

Voici un one-liner qui copie au maximum size-1 caractères non nuls de src à dest et ajoute un terminateur nul:

static inline void cpystr(char *dest, const char *src, size_t size)
{ if(size) while((*dest++ = --size ? *src++ : 0)); }
3
Christoph

strncpy fonctionne directement avec les tampons de chaîne disponibles, si vous travaillez directement avec votre mémoire, vous DEVEZ maintenant les tailles de tampon et vous pouvez définir le "\ 0" manuellement.

Je crois qu'il n'y a pas de meilleure alternative en C simple, mais ce n'est pas si mal si vous êtes aussi prudent que vous devriez l'être lorsque vous jouez avec de la mémoire brute.

3
Arkaitz Jimenez

J'ai toujours préféré:

 memset(dest, 0, LEN);
 strncpy(dest, src, LEN - 1);

à la solution par la suite, mais ce n'est vraiment qu'une question de préférence.

3
stonemetal

Sans compter sur des extensions plus récentes, j'ai fait quelque chose comme ça dans le passé:

/* copy N "visible" chars, adding a null in the position just beyond them */
#define MSTRNCPY( dst, src, len) ( strncpy( (dst), (src), (len)), (dst)[ (len) ] = '\0')

et peut-être même:

/* pull up to size - 1 "visible" characters into a fixed size buffer of known size */
#define MFBCPY( dst, src) MSTRNCPY( (dst), (src), sizeof( dst) - 1)

Pourquoi les macros au lieu des nouvelles fonctions "intégrées" (?)? Parce qu'il y avait auparavant un certain nombre d'unités différentes, ainsi que d'autres environnements non-unix (non-windows) que je devais porter pour sauvegarder lorsque je faisais du C quotidiennement.

2
Roboprog

Ces fonctions ont évolué plus que d'être conçues, il n'y a donc vraiment pas de "pourquoi". Il vous suffit d'apprendre "comment". Malheureusement, les pages de manuel Linux au moins sont dépourvues d'exemples de cas d'utilisation courants pour ces fonctions, et j'ai remarqué beaucoup d'abus dans le code que j'ai examiné . J'ai pris quelques notes ici: http://www.pixelbeat.org/programming/gcc/string_buffers.html

2
pixelbeat