Quelles fonctions de la bibliothèque standard doivent (devraient) être évitées?

90

J'ai lu sur Stack Overflow que certaines fonctions C sont "obsolètes" ou "devraient être évitées". Pouvez-vous me donner quelques exemples de ce type de fonction et pourquoi?

Quelles alternatives à ces fonctions existent?

Pouvons-nous les utiliser en toute sécurité - des bonnes pratiques?

Andrei Ciobanu
la source
3
Je pense que c'est une très bonne chose à savoir. Certaines fonctions de C devraient vraiment être évitées et utilisées uniquement à des fins éducatives.
INS le
@Felix, il serait plus facile de modifier une seule réponse (marquée correcte) à l'avenir. Il est vraiment question de réponses qui changeront probablement avec le temps. Qui sait, peut-être que Jeff fournira un badge de «concierge» aux personnes qui garderont les réponses à jour au cours des prochaines années.
Tim Post
1
@Tim Post: Ok, je vais supprimer mes commentaires.
Felix Kling le
3
Je n'utiliserais pas strncpy()comme remplacement général pour strcpy(), et je n'utiliserais strncat()jamais parce qu'il a l'interface la plus peu intuitive imaginable - savez-vous ce que le paramètre de longueur spécifie?
Jonathan Leffler le
2
Utiliser strncpy et strncat est presque toujours une erreur. Ils ne doivent certainement pas être utilisés à la place de strcpy et strcat !! scanf et sprintf sont également parfaitement utilisables si vous savez comment les utiliser ...
R .. GitHub STOP HELPING ICE

Réponses:

58

Fonctions
obsolètes non sécurisées
Un exemple parfait d'une telle fonction est gets () , car il n'y a aucun moyen de lui indiquer la taille du tampon de destination. Par conséquent, tout programme qui lit l'entrée à l'aide de gets () a une vulnérabilité de dépassement de mémoire tampon . Pour des raisons similaires, il faut utiliser strncpy () à la place de strcpy () et strncat () à la place de strcat () .

Pourtant, d'autres exemples incluent les fonctions tmpfile () et mktemp () en raison de problèmes de sécurité potentiels liés à l'écrasement des fichiers temporaires et qui sont remplacées par la fonction plus sécurisée mkstemp () .

Non-réentrant
D'autres exemples incluent gethostbyaddr () et gethostbyname () qui ne sont pas réentrants (et, par conséquent, ne sont pas garantis d'être threadsafe) et ont été remplacés par les réentrants getaddrinfo () et freeaddrinfo () .

Vous remarquerez peut-être un modèle ici ... soit le manque de sécurité (éventuellement en omettant d'inclure suffisamment d'informations dans la signature pour éventuellement l'implémenter en toute sécurité) ou la non-réentrance sont des sources courantes de dépréciation.

Désuet, non portable
Certaines autres fonctions deviennent simplement obsolètes car elles dupliquent des fonctionnalités et ne sont pas aussi portables que d'autres variantes. Par exemple, bzero () est obsolète au profit de memset () .

Sécurité des fils et réentrance
Vous avez posé une question, dans votre message, sur la sécurité des fils et la réentrance. Il y a une légère différence. Une fonction est réentrante si elle n'utilise aucun état partagé et mutable. Ainsi, par exemple, si toutes les informations dont elle a besoin sont transmises à la fonction et que tous les tampons nécessaires sont également passés à la fonction (plutôt que partagés par tous les appels à la fonction), alors il est réentrant. Cela signifie que différents threads, en utilisant des paramètres indépendants, ne risquent pas de partager accidentellement l'état. La réentrance est une garantie plus solide que la sécurité des fils. Une fonction est thread-safe si elle peut être utilisée par plusieurs threads simultanément. Une fonction est thread-safe si:

  • Il est réentrant (c'est-à-dire qu'il ne partage aucun état entre les appels), ou:
  • Il n'est pas réentrant, mais il utilise la synchronisation / verrouillage selon les besoins pour l'état partagé.

En général, dans la spécification UNIX unique et IEEE 1003.1 (c'est-à-dire "POSIX"), toute fonction qui n'est pas garantie d'être réentrante n'est pas garantie d'être thread-safe. Ainsi, en d'autres termes, seules les fonctions qui sont garanties réentrantes peuvent être utilisées de manière portative dans des applications multithread (sans verrouillage externe). Cela ne signifie pas, cependant, que les implémentations de ces normes ne peuvent pas choisir de rendre une fonction non réentrante threadsafe. Par exemple, Linux ajoute fréquemment la synchronisation aux fonctions non réentrantes afin d'ajouter une garantie (au-delà de celle de la spécification UNIX unique) de la sécurité des threads.

Chaînes (et tampons mémoire, en général)
Vous avez également demandé s'il y avait un défaut fondamental avec les chaînes / tableaux. Certains pourraient prétendre que c'est le cas, mais je dirais que non, il n'y a pas de défaut fondamental dans la langue. C et C ++ vous obligent à transmettre la longueur / capacité d'un tableau séparément (ce n'est pas une propriété ".length" comme dans certains autres langages). Ce n'est pas un défaut en soi. Tout développeur C et C ++ peut écrire du code correct simplement en passant la longueur en tant que paramètre si nécessaire. Le problème est que plusieurs API qui nécessitaient ces informations n'ont pas réussi à les spécifier en tant que paramètre. Ou supposé qu'une certaine constante MAX_BUFFER_SIZE serait utilisée. Ces API sont désormais obsolètes et remplacées par des API alternatives qui permettent de spécifier les tailles de tableau / tampon / chaîne.

Scanf (En réponse à votre dernière question)
Personnellement, j'utilise la librairie C ++ iostreams (std :: cin, std :: cout, les opérateurs << et >>, std :: getline, std :: istringstream, std :: ostringstream , etc.), donc je ne m'occupe généralement pas de cela. Si j'étais obligé d'utiliser du C pur, personnellement, j'utiliserais simplement fgetc () ou getchar () en combinaison avec strtol () , strtoul () , etc. et analyserais les choses manuellement, car je ne suis pas un grand fan de varargs ou chaînes de format. Cela dit, à ma connaissance, il n'y a pas de problème avec [f] scanf () , [f] printf (), etc. tant que vous créez vous-même les chaînes de format, vous ne transmettez jamais de chaînes de format arbitraires ou n'autorisez pas l'utilisation des entrées utilisateur comme chaînes de format, et vous utilisez les macros de formatage définies dans <inttypes.h> le cas échéant. (Remarque, snprintf () doit être utilisé à la place de sprintf () , mais cela a à voir avec l'échec de spécifier la taille du tampon de destination et non l'utilisation de chaînes de format). Je dois également souligner que, en C ++, boost :: format fournit un formatage de type printf sans varargs.

Michael Aaron Safyan
la source
4
«Déprécié» est un mot fort, qui a une signification spécifique lors de l'examen de la norme C ++. En ce sens, gets (), strcpy () etc. ne sont pas obsolètes.
4
Tant que vous faites la distinction entre «obsolète par le standard C», «obsolète par Michael Aaron Safyan» et «obsolète par une personne ou des personnes inconnues qui, espérons-le, savent de quoi elles parlent [citation nécessaire]». La question porte sur le style de codage préféré, pas sur le standard C, donc les deux seconds sont appropriés. Mais comme Neil, j'ai exigé une double prise avant de réaliser que vos déclarations n'avaient pas l'intention d'impliquer le premier sens.
Steve Jessop
11
strncpydevrait généralement être évité. Il ne fait pas ce que la plupart des programmeurs supposent. Il ne garantit pas la terminaison (conduisant à des dépassements de mémoire tampon), et il remplit des chaînes plus courtes (pouvant dégrader les performances dans certains cas).
Adrian McCarthy
2
@Adrian: Je suis d'accord avec vous - ni strncpy()ni le pire strncat()n'est un remplacement judicieux pour les variantes n-less.
Jonathan Leffler
4
Cette réponse répand un dogme absurde sur "remplacer strcpy par strncpy - je ne sais pas pourquoi mais Microsoft me le dit". strncpy n'a jamais été conçu pour être une version sûre de strcpy! En face, c'est beaucoup plus dangereux. Consultez Pourquoi strlcpy et strlcat sont-ils considérés comme non sécurisés? .
Lundin
24

Une fois de plus, les gens répètent, comme un mantra, l'affirmation ridicule selon laquelle la version "n" des fonctions str sont des versions sûres.

Si c'était ce à quoi ils étaient destinés, ils finiraient toujours par null.

Les versions «n» des fonctions ont été écrites pour être utilisées avec des champs de longueur fixe (comme les entrées de répertoire dans les premiers systèmes de fichiers) où le terminateur nul n'est requis que si la chaîne ne remplit pas le champ. C'est aussi la raison pour laquelle les fonctions ont des effets secondaires étranges qui sont inefficaces si elles sont simplement utilisées comme remplacements - prenez strncpy () par exemple:

Si le tableau pointé par s2 est une chaîne plus courte que n octets, les octets nuls sont ajoutés à la copie du tableau pointé par s1, jusqu'à ce que n octets en tout soient écrits.

Les tampons alloués pour gérer les noms de fichiers étant généralement de 4 Ko, cela peut entraîner une détérioration massive des performances.

Si vous voulez des versions "soi-disant" sûres, procurez-vous - ou écrivez vos propres - routines strl (strlcpy, strlcat etc.) qui terminent toujours les chaînes et n'ont pas d'effets secondaires. Veuillez noter cependant que ceux-ci ne sont pas vraiment sûrs car ils peuvent tronquer la chaîne en silence - c'est rarement le meilleur plan d'action dans un programme réel. Il y a des occasions où cela est acceptable, mais il y a aussi de nombreuses circonstances où cela pourrait conduire à des résultats catastrophiques (par exemple, l'impression d'ordonnances médicales).

Jauge
la source
1
Vous avez raison strncpy(), mais vous avez tort strncat(). strncat()n'a pas été conçu pour être utilisé avec des champs de longueur fixe - il a en fait été conçu pour être un strcat()qui limite le nombre de caractères concaténés. Il est assez facile de l'utiliser comme un "coffre-fort strcat()" en gardant une trace de l'espace restant dans le tampon lors de plusieurs concaténations, et encore plus facile de l'utiliser comme un "coffre-fort strcpy()" (en définissant le premier caractère du tampon de destination sur '\0'avant l'appelant). termine strncat() toujours la chaîne de destination et n'écrit pas de '\0's supplémentaires .
caf
2
@caf - oui mais strncat () est totalement inutile car il prend comme paramètre la longueur maximale à copier - pas la longueur du tampon de destination. Pour éviter le débordement de la mémoire tampon, vous devez connaître la longueur de la chaîne de destination actuelle, et si vous savez pourquoi utiliser strncat () - qui doit à nouveau calculer la longueur de destination - plutôt que juste strlcat () la chaîne source pour la fin de la chaîne dest.
Dipstick
Cela ne change toujours pas le fait que vous impliquez que strncat()la destination n'est pas toujours nulle et qu'elle a été conçue pour être utilisée avec des champs de longueur fixe, qui sont tous deux erronés.
caf
2
@chrisharris: strncat()fonctionnera correctement quelle que soit la longueur de la chaîne source, mais strcat()pas. Le problème avec strlcat()ici est que ce n'est pas une fonction C standard.
David Thornley
@caf - vous avez raison à propos de strncat (). Je ne l'ai jamais utilisé car - comme je l'ai souligné plus haut - il est totalement inutile. Il faut donc encore l'éviter.
Dipstick
19

Plusieurs réponses suggèrent ici d'utiliser strncat()over strcat(); Je suggère que strncat()(et strncpy()) devrait également être évité. Il a des problèmes qui le rendent difficile à utiliser correctement et conduisent à des bugs:

  • le paramètre de longueur à strncat()est lié (mais pas tout à fait exactement - voir le 3ème point) au nombre maximum de caractères qui peuvent être copiés vers la destination plutôt qu'à la taille du tampon de destination. Cela rend strncat()plus difficile à utiliser qu'il ne devrait l'être, en particulier si plusieurs éléments seront concaténés à la destination.
  • il peut être difficile de déterminer si le résultat a été tronqué (ce qui peut être important ou non)
  • il est facile d'avoir une erreur ponctuelle. Comme le note la norme C99, "Ainsi, le nombre maximum de caractères qui peuvent se retrouver dans le tableau pointé par s1est strlen(s1)+n+1" pour un appel qui ressemble àstrncat( s1, s2, n)

strncpy()a également un problème qui peut entraîner des bogues que vous essayez de l'utiliser de manière intuitive - cela ne garantit pas que la destination est terminée par null. Pour vous assurer que vous devez vous assurer de gérer spécifiquement ce cas d'angle en déposant vous-même un '\0'dans le dernier emplacement du tampon (au moins dans certaines situations).

Je suggère d'utiliser quelque chose comme OpenBSD strlcat()et strlcpy()(bien que je sache que certaines personnes n'aiment pas ces fonctions; je pense qu'elles sont beaucoup plus faciles à utiliser en toute sécurité que strncat()/ strncpy()).

Voici un peu de ce que Todd Miller et Theo de Raadt avaient à dire sur les problèmes avec strncat()et strncpy():

Il existe plusieurs problèmes rencontrés lorsque strncpy()et strncat()sont utilisés comme versions sûres de strcpy()et strcat(). Les deux fonctions traitent de la terminaison NUL et du paramètre de longueur de manière différente et non intuitive qui confond même les programmeurs expérimentés. Ils ne fournissent pas non plus un moyen facile de détecter quand une troncature se produit. ... De tous ces problèmes, la confusion causée par les paramètres de longueur et la question connexe de la terminaison NUL sont les plus importantes. Lorsque nous avons audité l'arborescence des sources d'OpenBSD pour détecter d'éventuelles failles de sécurité, nous avons constaté une utilisation abusive généralisée de strncpy()et strncat(). Bien que tous ces éléments n'aient pas abouti à des failles de sécurité exploitables, ils ont clairement montré que les règles d'utilisation strncpy()et d' strncat()exploitation des chaînes de sécurité sont largement mal comprises.

L'audit de sécurité d'OpenBSD a révélé que les bogues avec ces fonctions étaient «endémiques». Contrairement gets(), ces fonctions peuvent être utilisées en toute sécurité, mais dans la pratique, il y a beaucoup de problèmes car l'interface est déroutante, peu intuitive et difficile à utiliser correctement. Je sais que Microsoft a également fait des analyses (bien que je ne sache pas combien de leurs données ils ont pu publier), et en conséquence a interdit (ou du moins très fortement découragé - l'interdiction pourrait ne pas être absolue) le utilisation de strncat()et strncpy()(entre autres fonctions).

Quelques liens avec plus d'informations:

Michael Burr
la source
1
Il est préférable de garder une trace de la longueur des chaînes en dehors de la chaîne elle-même. Comme ça, concaténer deux chaînes (-with-length) devient en toute sécurité une question d'un simple calcul (pour obtenir la taille de tampon requise) une réallocation possible, et a memmove(). (Eh bien, vous pouvez utiliser memcpy()dans le cas normal lorsque les chaînes sont indépendantes.)
Donal Fellows
1
Votre deuxième point est complètement faux - termine strncat() toujours la chaîne de destination.
caf
1
Votre deuxième point est faux strncat(). Cependant, il est correct pour strncpy(), ce qui présente d'autres problèmes. strncat()est un remplacement raisonnable pour strcat(), mais strncpy()n'est pas un remplacement raisonnable pour strcpy().
David Thornley
2
caf et David ont raison à 100% de ma réclamation qui strncat()n'est pas toujours nulle. Je confondais un peu les comportements de strncat()et strncpy()(une autre raison pour laquelle ils sont des fonctions à éviter - ils ont des noms qui impliquent des comportements similaires, mais en fait se comportent différemment de manière importante ...). J'ai modifié ma réponse pour corriger cela et ajouter des informations supplémentaires.
Michael Burr
1
Notez qu'il char str[N] = ""; strncat(str, "long string", sizeof(str));s'agit d'un débordement de tampon si N n'est pas assez grand. La strncat()fonction est trop facile à utiliser à mauvais escient; il ne doit pas être utilisé. Si vous pouvez utiliser en strncat()toute sécurité, vous auriez pu utiliser memmove()ou à la memcpy()place (et ceux-ci seraient plus efficaces).
Jonathan Leffler
9

Fonctions de bibliothèque standard qui ne doivent jamais être utilisées:

setjmp.h

  • setjmp(). Ensemble longjmp(), ces fonctions sont largement reconnues comme étant incroyablement dangereuses à utiliser: elles mènent à la programmation spaghetti, elles sont accompagnées de nombreuses formes de comportement indéfini, elles peuvent provoquer des effets secondaires involontaires dans l'environnement du programme, comme affecter les valeurs stockées sur la pile. Références: MISRA-C: 2012 rule 21.4, CERT C MSC22-C .
  • longjmp(). Voir setjmp().

stdio.h

  • gets(). La fonction a été supprimée du langage C (selon C11), car elle n'était pas sûre selon la conception. La fonction était déjà signalée comme obsolète dans C99. Utilisez fgets()plutôt. Références: ISO 9899: 2011 K.3.5.4.1, voir également la note 404.

stdlib.h

  • atoi()famille de fonctions. Ceux-ci n'ont pas de gestion des erreurs mais invoquent un comportement non défini chaque fois que des erreurs se produisent. Fonctions complètement superflues qui peuvent être remplacées par la strtol()famille de fonctions. Références: MISRA-C: 2012 rule 21.7.

string.h

  • strncat(). A une interface maladroite qui est souvent mal utilisée. C'est surtout une fonction superflue. Voir également les remarques pour strncpy().
  • strncpy(). L'intention de cette fonction n'a jamais été d'être une version plus sûre de strcpy(). Son seul but a toujours été de gérer un ancien format de chaîne sur les systèmes Unix, et le fait qu'il soit inclus dans la bibliothèque standard est une erreur connue. Cette fonction est dangereuse car elle peut laisser la chaîne sans terminaison nulle et les programmeurs sont connus pour l'utiliser souvent de manière incorrecte. Références: Pourquoi strlcpy et strlcat sont-ils considérés comme non sécurisés? .

Fonctions de bibliothèque standard à utiliser avec précaution:

assert.h

  • assert(). Livré avec une surcharge et ne doit généralement pas être utilisé dans le code de production. Il est préférable d'utiliser un gestionnaire d'erreurs spécifique à l'application qui affiche les erreurs mais ne ferme pas nécessairement l'ensemble du programme.

signal.h

stdarg.h

  • va_arg()famille de fonctions. La présence de fonctions de longueur variable dans un programme C est presque toujours une indication d'une mauvaise conception du programme. Doit être évité sauf si vous avez des exigences très spécifiques.

stdio.h
En général, toute cette bibliothèque n'est pas recommandée pour le code de production , car elle est accompagnée de nombreux cas de comportement mal défini et de sécurité de type médiocre.

  • fflush(). Parfaitement bien à utiliser pour les flux de sortie. Appelle un comportement non défini s'il est utilisé pour les flux d'entrée.
  • gets_s(). Version sûre de gets()incluse dans l'interface de vérification des limites C11. Il est préférable d'utiliser à la fgets()place, conformément à la recommandation standard C. Références: ISO 9899: 2011 K.3.5.4.1.
  • printf()famille de fonctions. Fonctions lourdes en ressources qui s'accompagnent de nombreux comportements indéfinis et d'une mauvaise sécurité de type. sprintf()a également des vulnérabilités. Ces fonctions doivent être évitées dans le code de production. Références: MISRA-C: 2012 rule 21.6.
  • scanf()famille de fonctions. Voir les remarques sur printf(). De plus, - scanf()est vulnérable aux dépassements de tampon s'il n'est pas utilisé correctement. fgets()est préférable d'utiliser lorsque cela est possible. Références: CERT C INT05-C , MISRA-C: 2012 rule 21.6.
  • tmpfile()famille de fonctions. Livré avec divers problèmes de vulnérabilité. Références: CERT C FIO21-C .

stdlib.h

  • malloc()famille de fonctions. Parfaitement utilisable dans les systèmes hébergés, mais soyez conscient des problèmes bien connus de C90 et ne diffusez donc pas le résultat . La malloc()famille de fonctions ne doit jamais être utilisée dans des applications autonomes. Références: MISRA-C: 2012 rule 21.3.

    Notez également que cela realloc()est dangereux si vous écrasez l'ancien pointeur avec le résultat de realloc(). En cas d'échec de la fonction, vous créez une fuite.

  • system(). Livré avec beaucoup de frais généraux et bien que portable, il est souvent préférable d'utiliser des fonctions API spécifiques au système à la place. Livré avec divers comportements mal définis. Références: CERT C ENV33-C .

string.h

  • strcat(). Voir les remarques pour strcpy().
  • strcpy(). Parfaitement bien à utiliser, sauf si la taille des données à copier est inconnue ou supérieure à la mémoire tampon de destination. Si aucune vérification de la taille des données entrantes n'est effectuée, il peut y avoir des dépassements de tampon. Ce qui n'est pas une faute en strcpy()soi, mais de l'application appelante - qui strcpy()n'est pas sûre est principalement un mythe créé par Microsoft .
  • strtok(). Modifie la chaîne de l'appelant et utilise des variables d'état internes, ce qui pourrait la rendre dangereuse dans un environnement multi-thread.
Lundin
la source
Je suggère de recommander static_assert()à la place de assert(), si la condition peut être résolue au moment de la compilation. En outre, sprintf()peut presque toujours être remplacé, snprintf()ce qui est un peu plus sûr.
user694733
1
Utiliser strtok()dans une fonction A signifie que (a) la fonction ne peut pas appeler une autre fonction qui utilise également strtok()pendant que A l'utilise, et (b) signifie qu'aucune fonction qui appelle A ne peut être utilisée strtok()lorsqu'elle appelle A. En d'autres termes, utiliser strtok()empoisonne la chaîne d'appel; il ne peut pas être utilisé en toute sécurité dans le code de la bibliothèque car il doit documenter qu'il utilise strtok()pour empêcher les autres utilisateurs d' strtok()appeler le code de la bibliothèque.
Jonathan Leffler
Le strncpyest la fonction appropriée à utiliser lors de l'écriture d'un tampon de chaîne à zéro remplissage avec des données provenant soit d'une chaîne terminée par zéro, soit d'un tampon de remplissage à zéro dont la taille est au moins aussi grande que la destination. Les tampons sans rembourrage ne sont pas très courants, mais ils ne sont pas non plus vraiment obscurs.
supercat
7

Certains prétendraient cela strcpyet strcatdevraient être évités, en faveur de strncpyet strncat. C'est quelque peu subjectif, à mon avis.

Ils doivent absolument être évités lors du traitement des entrées utilisateur - sans aucun doute ici.

Dans le code "loin" de l'utilisateur, quand on sait juste que les tampons sont assez longs, strcpyet strcatpeuvent être un peu plus efficaces car le calcul du npassage à leurs cousins ​​peut être superflu.

Eli Bendersky
la source
4
Et si disponible, mieux encore strlcatet strlcpy, puisque la version 'n' ne garantit pas la terminaison NULL de la chaîne de destination.
Dan Andreatta
Cela me semble une optimisation prématurée. Mais IIRC strncpy()écrira exactement des noctets, en utilisant des caractères nuls si nécessaire. Comme Dan, utiliser une version sûre est le meilleur choix de l'OMI.
Bastien Léonard
@Dan, ces fonctions ne sont malheureusement pas standard et n'appartiennent donc pas à cette discussion
Eli Bendersky
@Eli: mais le fait que cela strncat()peut être difficile à utiliser correctement et en toute sécurité (et doit être évité) est sur le sujet.
Michael Burr
@Michael: Je ne dirais pas que c'est difficile à utiliser correctement et en toute sécurité. Avec soin, ça marche très bien
Eli Bendersky
6

Éviter

  • strtok pour les programmes multithread car ce n'est pas thread-safe.
  • gets car cela pourrait provoquer un débordement de tampon
codaddict
la source
2
Ce sont des cas légèrement différents. Vous pouvez utiliser strtok en toute sécurité si vous savez que votre programme n'est pas multithread ou si vous en verrouillez l'accès d'une manière ou d'une autre, mais que vous ne pouvez pas utiliser gets en toute sécurité, ne devez donc jamais l'utiliser.
jcoder le
9
Le problème avec strtok()va un peu au-delà de la sécurité des threads - ce n'est pas sûr même dans un programme à thread unique, à moins que vous ne sachiez avec certitude qu'aucune fonction que votre code pourrait appeler lors de l'utilisation strtok()ne l' utilise pas également (ou ils gâcheront strtok()l'état de sous vous). En fait, la plupart des compilateurs qui ciblent les plates-formes multi-thread prennent en charge strtok()les problèmes potentiels de threads en utilisant le stockage local des threads pour strtok()les données statiques de. Mais cela ne résout toujours pas le problème des autres fonctions qui l'utilisent pendant que vous êtes (dans le même thread).
Michael Burr
En effet et je me tais maintenant car je ne veux encourager personne à utiliser strtok car il souffre de nombreux problèmes. Je voulais juste souligner que c'est différent de get, car il est possible de l'utiliser en toute sécurité alors qu'il est impossible d'utiliser get en toute sécurité.
jcoder
@Jimmy: Il existe souvent des extensions de bibliothèques non standard, ou vous pouvez écrire les vôtres. Le gros problème avec strtok()est qu'il ne conserve précisément qu'un seul tampon pour travailler, donc la plupart des bons remplacements nécessitent de conserver une valeur de tampon et de la transmettre.
David Thornley
strcspnfait l'essentiel de ce dont vous avez besoin - trouvez le prochain séparateur de jetons. Vous pouvez réimplémenter une variante sensée de strtokavec.
bluss
5

Il vaut probablement la peine d'ajouter à nouveau que ce strncpy()n'est pas le remplacement à usage général de ce strcpy()que son nom pourrait suggérer. Il est conçu pour les champs de longueur fixe qui n'ont pas besoin d'un nul-terminator (il a été initialement conçu pour être utilisé avec les entrées de répertoire UNIX, mais peut être utile pour des choses comme les champs de clé de chiffrement).

Il est cependant facile de l'utiliser strncat()en remplacement de strcpy():

if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

(Le iftest peut évidemment être abandonné dans le cas courant, où vous savez que dest_sizec'est certainement différent de zéro).

caf
la source
5

Consultez également la liste des API interdites de Microsoft . Ce sont des API (dont beaucoup sont déjà répertoriées ici) qui sont interdites du code Microsoft car elles sont souvent mal utilisées et entraînent des problèmes de sécurité.

Vous n'êtes peut-être pas d'accord avec tous, mais ils valent tous la peine d'être pris en considération. Ils ajoutent une API à la liste lorsque son utilisation abusive a conduit à un certain nombre de bogues de sécurité.

Adrian McCarthy
la source
2

Presque toutes les fonctions qui traitent des chaînes terminées par NUL sont potentiellement dangereuses. Si vous recevez des données du monde extérieur et que vous les manipulez via les fonctions str * (), vous vous préparez à une catastrophe

rep_movsd
la source
2

N'oubliez pas sprintf - c'est la cause de nombreux problèmes. Cela est vrai car l'alternative, snprintf a parfois des implémentations différentes qui peuvent rendre votre code non portable.

  1. linux: http://linux.die.net/man/3/snprintf

  2. fenêtres: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

Dans le cas 1 (linux), la valeur de retour est la quantité de données nécessaire pour stocker la totalité du tampon (si elle est plus petite que la taille du tampon donné, la sortie a été tronquée)

Dans le cas 2 (fenêtres), la valeur de retour est un nombre négatif au cas où la sortie serait tronquée.

En général, vous devez éviter les fonctions qui ne sont pas:

  1. buffer overflow safe (beaucoup de fonctions sont déjà mentionnées ici)

  2. thread safe / non réentrant (strtok par exemple)

Dans le manuel de chaque fonction, vous devez rechercher des mots-clés tels que: safe, sync, async, thread, buffer, bugs

INS
la source
2
_sprintf()a été créé par Microsoft avant l' snprintf()arrivée de la norme , IIRC. ´StringCbPrintf () ´ est assez similaire à snprintf()bien.
Bastien Léonard
vous pouvez utiliser en sprintfquelque sorte en toute sécurité , dans certains cas: sprintf(buffer,"%10s",input);limite le nb copié octets à 10 (si bufferest char buffer[11]c'est sûr , même si les données peuvent liquider tronquée.
Jean-François Fabre
2

Il est très difficile de l'utiliser en scanftoute sécurité. Une bonne utilisation de scanfpeut éviter les débordements de tampon, mais vous êtes toujours vulnérable à un comportement indéfini lors de la lecture de nombres qui ne correspondent pas au type demandé. Dans la plupart des cas, fgetssuivie par l' auto-analyse syntaxique ( à l' aide sscanf, strchretc.) est une meilleure option.

Mais je ne dirais pas "éviter scanftout le temps". scanfa ses utilisations. À titre d'exemple, disons que vous souhaitez lire l'entrée utilisateur dans un chartableau de 10 octets de long. Vous souhaitez supprimer la nouvelle ligne de fin, le cas échéant. Si l'utilisateur entre plus de 9 caractères avant une nouvelle ligne, vous souhaitez stocker les 9 premiers caractères dans la mémoire tampon et tout ignorer jusqu'à la prochaine nouvelle ligne. Tu peux faire:

char buf[10];
scanf("%9[^\n]%*[^\n]", buf));
getchar();

Une fois que vous vous êtes habitué à cet idiome, il est plus court et à certains égards plus propre que:

char buf[10];
if (fgets(buf, sizeof buf, stdin) != NULL) {
    char *nl;
    if ((nl = strrchr(buf, '\n')) == NULL) {
        int c;
        while ((c = getchar()) != EOF && c != '\n') {
            ;
        }
    } else {
        *nl = 0;
    }
}
Alok Singhal
la source
0

Dans tous les scénarios de copie / déplacement de chaîne - strcat (), strncat (), strcpy (), strncpy (), etc. - les choses se passent beaucoup mieux ( plus sûres ) si quelques heuristiques simples sont appliquées:

   1. Toujours NUL-fill votre (vos) tampon (s) avant d'ajouter des données.
   2. Déclarez les tampons de caractères comme [SIZE + 1], avec une macro-constante.

Par exemple, étant donné:

#define   BUFSIZE   10
char      Buffer[BUFSIZE+1] = { 0x00 };  /* The compiler NUL-fills the rest */

nous pouvons utiliser du code comme:

memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");

relativement sûr. Le memset () devrait apparaître avant strncpy (), même si nous avons initialisé Buffer au moment de la compilation, car nous ne savons pas ce que les autres codes placés dedans avant l'appel de notre fonction. Le strncpy () tronquera les données copiées en "1234567890" et ne les terminera pas par NUL. Cependant, comme nous avons déjà rempli tout le tampon NUL - sizeof (Buffer), plutôt que BUFSIZE - il est garanti qu'il y aura une terminaison finale NUL "hors de portée" de toute façon, tant que nous contraignons nos écritures à l'aide de BUFSIZE constante, au lieu de sizeof (Buffer).

Buffer et BUFSIZE fonctionnent également très bien pour snprintf ():

memset(Buffer,0x00,sizeof(Buffer));
if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) {
    /* Do some error-handling */
}   /* If using MFC, you need if(... < 0), instead */

Même si snprintf () n'écrit spécifiquement que les caractères BUFIZE-1, suivis de NUL, cela fonctionne en toute sécurité. Donc, nous "gaspillons" un octet NUL superflu à la fin de Buffer ... nous évitons à la fois les conditions de dépassement de mémoire tampon et de chaînes non terminées, pour un coût mémoire assez faible.

Mon appel sur strcat () et strncat () est plus dur: ne les utilisez pas. Il est difficile d'utiliser strcat () en toute sécurité, et l'API pour strncat () est si contre-intuitive que l'effort nécessaire pour l'utiliser correctement annule tout avantage. Je propose le drop-in suivant:

#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)

Il est tentant de créer un drop-in strcat (), mais ce n'est pas une bonne idée:

#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)

car la cible peut être un pointeur (donc sizeof () ne renvoie pas les informations dont nous avons besoin). Je n'ai pas de bonne solution "universelle" aux instances de strcat () dans votre code.

Un problème que je rencontre fréquemment de la part des programmeurs «sensibles à strFunc ()» est une tentative de protection contre les débordements de tampon en utilisant strlen (). C'est très bien si le contenu est garanti pour être terminé par NUL. Sinon, strlen () lui-même peut provoquer une erreur de dépassement de tampon (conduisant généralement à une violation de segmentation ou à une autre situation de vidage de mémoire), avant que vous n'atteigniez le code "problématique" que vous essayez de protéger.

TLR
la source
-2

atoi n'est pas thread-safe. J'utilise strtol à la place, conformément aux recommandations de la page de manuel.

Fred
la source
5
Cela semble s'appliquer à une implémentation particulière. Il n'y a aucune raison pour laquelle strtol()serait thread-safe et atoi()ne le serait pas.
David Thornley
2
La recommandation d'utiliser strtol n'a rien à voir avec la sécurité des threads, mais avec la gestion des erreurs. Je ne sais pas non plus de quelle page de manuel vous avez obtenu ces informations - je ne trouve aucune recommandation ci-dessous man atoi(il devrait y en avoir, cependant).
Lundin