Quelles fonctions de la bibliothèque standard doivent (devraient) être évitées?
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 la raison pourquoi?
quelles sont les solutions de rechange à ces fonctions?
Pouvons-nous les utiliser en toute sécurité - tout de bonnes pratiques?
13 réponses
Fonctions Dépréciées
Non Sécurisé
Un exemple parfait d'une telle fonction est gets () , parce qu'il n'y a aucun moyen de lui dire quelle est la taille du tampon de destination. Par conséquent, tout programme qui lit des entrées à l'aide de gets() a une vulnérabilité de débordement de tampon "1519110920 . Pour des raisons similaires, on devrait utiliser strncpy () au lieu de strcpy () et strncat () à la place de strcat () .
Encore quelques autres exemples incluent le tmpfile() et mktemp() "1519100920 fonction" en raison de les problèmes de sécurité potentiels avec d'écraser les fichiers temporaires et qui sont remplacées par les plus sécurisées mkstemp() de la fonction.
Non-Rentrant
D'autres exemples incluent gethostbyaddr() et gethostbyname() qui sont non-rentrant (et, par conséquent, pas garanti d'être threadsafe) et ont été remplacés par le getaddrinfo() et freeaddrinfo() .
vous pouvez remarquer un motif ici... soit un manque de sécurité (peut-être pour inclure suffisamment d'informations dans la signature pour éventuellement la mettre en œuvre en toute sécurité) ou non-réintégration sont des sources courantes de dépréciation.
Obsolète, Non Portable
Certaines autres fonctions deviennent tout simplement obsolètes parce qu'elles dupliquent des fonctionnalités et ne sont pas aussi portables que d'autres variantes. Par exemple, bzero () est déprécié en faveur de memset () .
Filet de Sécurité et de re-ouverture
Vous avez demandé, dans votre post, à propos de la sécurité du fil et de la rentrée. Il y a une légère différence. Une fonction est rentrante si elle n'utilise aucun état mutable partagé. Ainsi, par exemple, si toutes les informations dont il a besoin sont passées dans la fonction, et si les tampons nécessaires sont également passés dans la fonction (plutôt que partagés par tous les appels à la fonction), alors il est rentrant. Cela signifie que des fils différents, en utilisant des paramètres indépendants, ne risquez pas de partager accidentellement l'état. La rentrée est une garantie plus forte que la sécurité du fil. Une fonction est sûre si elle peut être utilisée par plusieurs threads simultanément. Une fonction est sûre si:
- Il est réentrant (c'est à dire qu'il ne partage pas tout de l'état entre les appels), ou:
- il n'est pas réentrant, mais il utilise la synchronisation/verrouillage si nécessaire pour l'état partagé.
en général, dans la Spécification UNIX simple et IEEE 1003.1 (c.-à-d." POSIX"), toute fonction qui n'est pas garantie d'être rentrant n'est pas garantie d'être sans fil. Ainsi, en d'autres termes, seules les fonctions qui sont garanties pour être réentrées peuvent être utilisées de manière soutenable dans des applications multithread (sans verrouillage externe). Cela ne signifie pas, cependant, que la mise en œuvre de ces normes ne peut pas choisir de rendre un non-rentrant la fonction de thread-safe. Par exemple, Linux ajoute fréquemment la synchronisation à des fonctions non-reentrant afin d'ajouter une garantie (au-delà de celle de la spécification UNIX unique) de threadsafety.
cordes (et tampons de mémoire, en général)
Vous avez aussi demandé s'il y avait un défaut fondamental avec les cordes/tableaux. Certains diront que c'est le cas, mais je dirais que non, il n'y a pas de faille fondamentale dans la langue. C et C++ vous demande de passer la longueur/capacité d'un tableau séparément (ce n'est pas un ".length " comme dans d'autres langues). Ce n'est pas un défaut, per se. N'importe quel développeur C et c++ peut écrire du code correct simplement en passant la longueur comme paramètre là où c'est nécessaire. Le problème est que plusieurs API qui nécessitaient cette information ne l'ont pas spécifiée comme paramètre. Ou supposé qu'une constante MAX_BUFFER_SIZE serait utilisée. Ces IPA ont maintenant été dépréciées et remplacées par d'autres IPA qui permettent le tableau/tampon/string taille spécifiée.
Scanf (en réponse à votre dernière Question)
Personnellement, j'utilise la bibliothèque C++ iostream (std:: cin, std:: cout, les opérateurs << et>>, std::getline, std::istringstream, std::ostringstream, etc.), donc je n'ai généralement pas le gérer. Si j'étais forcé d'utiliser du C pur, cependant, je n'utiliserais personnellement que fgetc () ou getchar() en combinaison avec strtol () , strtoul () , etc. et analysez les choses manuellement, puisque je ne suis pas un grand fan des varargs ou des 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 passez jamais de chaînes de format arbitraires ou permettez à l'utilisateur d'ENTRER pour être utilisé comme chaînes de format, et vous utilisez les macros de formatage définies dans
une fois de plus, les gens répètent, comme un mantra, l'affirmation ridicule que la version" n " des fonctions str sont des versions sûres.
si c'était ce à quoi ils étaient destinés, alors ils seraient toujours nuls terminer les chaînes.
les versions " n " des fonctions ont été écrites pour être utilisées avec des champs de longueur fixe (tels que les entrées de répertoire dans les premiers systèmes de fichiers) où le terminator 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 inutilement inefficaces si seulement utilisé comme remplacements - prenez strncpy() par exemple:
Si le tableau pointé par s2 est un chaîne plus courte que n octets, des octets nuls sont annexées à la copie le tableau pointé vers s1, jusqu'à n les octets en tout sont écrits.
comme tampons alloués pour gérer les noms de fichiers sont généralement 4kbytes ce peut conduire à une détérioration massive de la performance.
si vous voulez des versions" prétendument " sûres, alors obtenez - ou écrivez vos propres routines strl (strlcpy, strlcat etc) qui n'ont pas d'effets secondaires. S'il vous plaît noter que ceux - ci ne sont pas vraiment sûrs car ils peuvent silencieusement tronquer la corde-ce est rarement la meilleure voie d'action dans un programme du monde réel. Il y a des occasions où c'est OK, mais il y a aussi beaucoup de circonstances où il pourrait entraîner des résultats catastrophiques (p. ex., l'impression d'ordonnances médicales).
plusieurs réponses ici suggèrent d'utiliser strncat()
au lieu de strcat()
; je suggère que strncat()
(et strncpy()
) devrait également être évitée. 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 exactement - voir le 3ème point) au nombre maximum de caractères pouvant être copiés vers la destination plutôt qu'à la taille du tampon de destination. Ce rendstrncat()
plus difficile à utiliser qu'il ne devrait l'être, en particulier si plusieurs articles 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 un tout-en-un message d'erreur. Comme le note le standard C99, " ainsi, le nombre maximum de caractères pouvant se retrouver dans le tableau pointé par
s1
eststrlen(s1)+n+1
pour un appel qui ressemble àstrncat( s1, s2, n)
strncpy()
a également un problème qui peut provoquer des bugs que vous essayez de l'utiliser de manière intuitive - il ne garantit pas que la destination est null. Pour vous assurer que vous devez vous assurer que vous manipulez spécifiquement ce cas de coin en laissant tomber un '"1519100920"'
dans le dernier emplacement du tampon vous-même (au moins dans certaines situations).
je suggère D'utiliser quelque chose comme strlcat()
D'OpenBSD et strlcpy()
(même si je sais que certaines personnes n'aiment pas ces fonctions; je crois 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 ont dit au sujet des problèmes avec strncat()
et strncpy()
:
il y a plusieurs problèmes rencontrés lorsque
strncpy()
etstrncat()
sont utilisés comme versions sûres destrcpy()
etstrcat()
. Les deux fonctions traiter avec la terminaison NUL et le paramètre length de manière différente et non intuitive, ce qui trouble même les programmeurs expérimentés. Ils ne permettent pas non plus de détecter facilement les cas de troncature. ... De toutes ces questions, la confusion causée par les paramètres de longueur et la question connexe de la terminaison du NUL sont les plus importantes. Lorsque nous avons vérifié L'arborescence des sources D'OpenBSD pour y déceler des failles de sécurité potentielles, nous avons découvert une mauvaise utilisation généralisée destrncpy()
etstrncat()
. Bien que pas tous ces problèmes ont entraîné des trous de sécurité exploitables, ils ont indiqué clairement que les règles d'utilisation destrncpy()
etstrncat()
dans les opérations de chaîne de sécurité sont largement mal comprises.
L'audit de sécurité D'OpenBSD a trouvé que les bogues avec ces fonctions étaient "rampants". À la différence de gets()
, ces fonctions peuvent être utilisées en toute sécurité, mais dans la pratique Il ya beaucoup de problèmes parce que l'interface est déroutante, peu intuitive et difficile à utiliser correctement. Je sais que Microsoft a également fait des analyses (bien que je ne sais pas combien de leurs données ils peuvent avoir publié), et en conséquence ont interdit (ou au moins très fortement découragé - l '"interdiction" pourrait ne pas être absolue) l'utilisation de strncat()
et strncpy()
(entre autres fonctions).
quelques liens avec plus d'information:
- http://www.usenix.org/events/usenix99/full_papers/millert/millert_html /
- http://en.wikipedia.org/wiki/Off-by-one_error#Security_implications
- http://blogs.msdn.com/michael_howard/archive/2004/10/29/249713.aspx
- http://blogs.msdn.com/michael_howard/archive/2004/11/02/251296.aspx
- http://blogs.msdn.com/michael_howard/archive/2004/12/10/279639.aspx
- http://blogs.msdn.com/michael_howard/archive/2006/10/30/something-else-to-look-out-for-when-reviewing-code.aspx
certaines personnes prétendent que strcpy
et strcat
devraient être évités, en faveur de strncpy
et strncat
. C'est un peu subjectif, à mon avis.
ils devraient certainement être évités lorsque l'on traite de la contribution de l'utilisateur - sans doute ici.
dans le code "loin" de l'utilisateur, quand vous juste savoir les tampons sont assez longs, strcpy
et strcat
peut être un peu plus efficace parce que calculer le n
pour passer à leurs cousins peut être superflu.
éviter
-
strtok
pour les programmes multithread comme sa pas thread-safe. -
gets
car il pourrait causer un débordement de tampon
les fonctions de bibliothèque Standard qui devrait jamais :
setjmp.h
-
setjmp()
. Aveclongjmp()
, ces fonctions sont largement reconnues comme incroyablement dangereuses à utiliser: ils conduisent à la programmation spaghetti, ils viennent avec de nombreuses formes de comportement non défini, ils peuvent causer des effets secondaires involontaires dans le programme environnement, comme affecter les valeurs stockées sur la pile. Références: MISRA-C: 2012 rule 21.4, CERT C MSC22-c . -
longjmp()
. Voirsetjmp()
.
stdio.h
-
gets()
. La fonction a été retirée du langage C (Selon C11), car elle n'était pas sécuritaire selon la conception. Cette fonction était déjà considérée comme obsolète en C99. Utiliserfgets()
à la place. 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 totalement superflues qui peuvent être remplacées par la famille de fonctionsstrtol()
. Références: MISRA-C: règle 21.7 de 2012.
de la chaîne.h
-
strncat()
. A une interface maladroite qui est souvent mal utilisée. C'est surtout un superflu la fonction. Voir aussi les remarques pourstrncpy()
. -
strncpy()
. L'intention de cette fonction n'a jamais été d'être une version plus sûre destrcpy()
. Son seul but était toujours de gérer un ancien format de chaîne sur les systèmes Unix, et qu'il soit inclus dans la bibliothèque standard est une erreur connue. Ce la fonction est dangereuse parce qu'elle peut laisser la chaîne sans terminaison nulle et les programmeurs sont connus pour l'utiliser souvent incorrectement. Références: pourquoi strlcpy et strlcat sont-ils considérés comme peu sûrs? .
les fonctions de bibliothèque Standard qui doit être utilisé avec prudence:
affirmer.h
-
assert()
. Livré avec les frais généraux 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.
du signal.h
-
signal()
. Références: MISRA-C: règle 21.5 de 2012, CERT C SIG32-c .
stdarg.h
-
va_arg()
famille de fonctions. La présence de longueur variable des fonctions dans un programme C est presque toujours un signe de mauvaise conception du programme. Doit être évitée sauf si vous avez des exigences très spécifiques.
stdio.h
En général, cette bibliothèque entière n'est pas recommandée pour le code de production , car il est livré avec de nombreux cas de comportement mal défini et de sécurité de type pauvre.
-
fflush()
. Parfaitement bien utiliser pour les flux de sortie. Invoque un comportement non défini s'il est utilisé pour les flux d'entrée. -
gets_s()
. Version sécurisée degets()
inclus dans l'interface de vérification des limites C11. Il est préférable d'utiliserfgets()
à la place, conformément à la recommandation standard C. Références: ISO 9899: 2011 K. 3.5.4.1. -
printf()
famille de fonctions. Fonctions lourdes de ressources qui viennent avec beaucoup de comportement indéfini et une mauvaise sécurité de type.sprintf()
a aussi des vulnérabilités. Ces fonctions doivent être évitées dans le code de production. Références: MISRA-C: règle 21.6 de 2012. -
scanf()
famille de fonctions. Voir les remarques surprintf()
. De plus, -scanf()
est vulnérable aux dépassements de tampon s'il n'est pas utilisé correctement. Il est préférable d'utiliserfgets()
lorsque cela est possible. Référence: 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 bien utiliser dans les systèmes hébergés, mais être conscient des problèmes bien connus en C90 et par conséquent ne jetez pas le résultat . La famille de fonctionsmalloc()
ne doit jamais être utilisée dans des applications autonomes. Références: MISRA-C: règle 21.3 de 2012.aussi noter que
realloc()
est dangereux dans le cas où vous écraser le vieux pointeur avec le résultat derealloc()
. En cas de défaillance de la fonction, vous créez une fuite. -
system()
. Vient avec beaucoup de frais généraux et bien que portable, il est il est souvent préférable d'utiliser des fonctions API spécifiques au système. Livré avec divers comportements mal définis. Références: CERT C ENV33-c .
de la chaîne.h
-
strcat()
. Voir les remarques pourstrcpy()
. -
strcpy()
. Parfaitement fin à utiliser, sauf si la taille des données à copier est inconnue ou plus grande que le tampon de destination. Si aucun vérification de la taille des données entrantes est fait, il peut y avoir des dépassements de tampon. Ce qui n'est pas la faute destrcpy()
lui - même, mais de l'application appelant-questrcpy()
est dangereux est la plupart du temps un mythe créé par Microsoft . -
strtok()
. Modifie la chaîne d'appel et utilise des variables d'état internes, ce qui pourrait le rendre dangereux dans un environnement multi-threadé.
il est probablement intéressant d'ajouter encore que strncpy()
n'est pas le remplacement universel de 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é conçu à l'origine pour être utilisé avec les entrées de répertoire UNIX, mais peut être utile pour des choses comme les champs de clé de cryptage).
il est facile, cependant, d'utiliser strncat()
comme un remplacement pour strcpy()
:
if (dest_size > 0)
{
dest[0] = '"151900920"';
strncat(dest, source, dest_size - 1);
}
(Le test if
peut évidemment être abandonné dans le cas commun, où vous savez que dest_size
est certainement non zéro).
également consulter la liste de Microsoft de API bannies . Ce sont des API (dont beaucoup sont déjà listées ici) qui sont bannies du code Microsoft parce qu'elles sont souvent mal utilisées et conduisent à des problèmes de sécurité.
vous pouvez ne pas être d'accord avec tous, mais ils sont tous à considérer. Ils ajoutent une API à la liste lorsque son utilisation abusive a conduit à un certain nombre de bogues de sécurité.
presque toutes les fonctions qui traitent des chaînes à terminaison NUL sont potentiellement dangereuses. Si vous recevez des données du monde extérieur et que vous les manipulez via les fonctions str* (), alors vous vous préparez pour la catastrophe
n'oubliez pas sprintf - c'est la cause de nombreux problèmes. C'est vrai parce que l'alternative, snprintf a parfois des implémentations différentes qui peuvent vous rendre le code non transportable.
dans le cas 1 (linux) la valeur de retour est la quantité de données nécessaires pour stocker le tampon entier (si elle est plus petite que la taille du tampon donné alors la sortie a été tronquée)
dans le cas 2 (windows) la valeur de retour est un nombre négatif dans le cas où la sortie est tronquée.
en général, vous devez éviter les fonctions qui ne sont pas:
-
débordement de la mémoire tampon coffre-fort (beaucoup de les fonctions sont déjà mentionnées ici)
-
thread-safe/non réentrant (strtok par exemple)
dans le manuel de chaque fonction vous devez rechercher des mots clés comme: safe, sync, async, thread, buffer, bugs
il est très difficile d'utiliser scanf
en toute sécurité. Une bonne utilisation de scanf
peut éviter les débordements de tampon, mais vous êtes toujours vulnérable à un comportement non défini lors de la lecture de nombres qui ne correspondent pas au type demandé. Dans la plupart des cas, fgets
suivi par auto-parsing (en utilisant sscanf
, strchr
, etc.) est une meilleure option.
Mais je ne dirais pas "éviter scanf
tout le temps". scanf
a ses usages. Comme un exemple, disons que vous voulez lire entrée de l'utilisateur dans un tableau char
qui fait 10 octets de long. Vous souhaitez supprimer la fin de ligne, le cas échéant. Si l'utilisateur entre plus de 9 caractères avant une nouvelle ligne, vous voulez stocker les 9 premiers caractères dans le tampon et tout jeter jusqu'à la nouvelle ligne suivante. Vous pouvez 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;
}
}
dans tous les scénarios string-copy/move - strcat(), strncat(), strcpy(), strncpy(), etc. - les choses vont beaucoup mieux ( plus sûr ) si un couple heuristiques simples sont appliquées:
1. Toujours remplir votre(s) tampon (s) avant d'ajouter des données.
2. Déclarer les tampons de caractères comme [SIZE+1], avec une macro-constante.
Par exemple:
#define BUFSIZE 10
char Buffer[BUFSIZE+1] = { 0x00 }; /* The compiler NUL-fills the rest */
nous pouvons utiliser le code comme:
memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");
relativement sûr. Le memset () devrait apparaître avant le strncpy (), même si nous avons initialisé le Buffer au moment de la compilation, parce que nous ne savons pas quel autre code poubelle y a placé avant que notre fonction ne soit appelée. Le strncpy () tronquera les données copiées en "1234567890", et et non NUL-terminera. Toutefois, étant donné que nous avons déjà rempli la totalité du tampon - sizeof(Buffer), plutôt que BUFSIZE - il est garanti qu'il y ait un final "out-of-scope" finissant NUL de toute façon, aussi longtemps que nous limitons nos Écritures en utilisant la constante BUFSIZE, au lieu de sizeof (Buffer).
tampon et BUFSIZE aussi travailler amende 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 des caractères BUFIZE-1, suivis de NUL, cela fonctionne en toute sécurité. Donc nous "gaspillons" un octet nul à la fin du tampon...nous prévient à la fois le débordement de tampon et les conditions de chaîne non-descendante, pour un coût de mémoire assez faible.
mon appel sur strcat() et strncat () est plus hard-line: ne les utilisez pas. Il est difficile d'utiliser strcat() en toute sécurité, et l'API de strncat() est tellement 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 strcat (), mais pas une bonne idée:
#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)
parce que target 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 chez les programmeurs" strfunc()-aware " est une tentative de protection contre les dépassements de tampon en utilisant strlen(). C'est très bien, si le contenu est garanti d'être NUL-terminé. Sinon, strlen () lui-même peut causer une erreur de dépassement de tampon (conduisant généralement à une violation de segmentation ou à une autre situation de vidage du noyau), avant que vous n'atteigniez le code "problématique" que vous essayez de protéger.
atoi n'est pas thread-safe. J'utilise plutôt strtol, selon la recommandation de la page de manuel.