J'ai lu sur le débordement de la pile que certaines fonctions C sont "obsolètes" ou "doivent être évitées". Pouvez-vous s'il vous plaît me donner quelques exemples de ce genre de fonction et la raison pour laquelle?
Quelles alternatives à ces fonctions existent?
Pouvons-nous les utiliser en toute sécurité - toutes les bonnes pratiques?
Fonctions obsolètes
Peu sûr
[. .____] Un parfait exemple d'une telle fonction est gets () , parce qu'il n'y a aucun moyen de le dire la taille de la mémoire tampon de destination est. Par conséquent, tout programme qui lit l'entrée à l'aide gets () a buffer vulnérabilité de trop-plein. Pour des raisons similaires, on doit utiliser strncpy () au lieu de strcpy () et strncat () à la place de strcat () .
Pourtant, quelques exemples comprennent le tmpfile () et mktemp () fonction due à problèmes potentiels de sécurité avec les fichiers temporaires écraser et qui sont remplacées par le plus sûr mkstemp () fonction.
Non réentrant
[. .____] D'autres exemples incluent gethostbyaddr () et gethostbyname () qui sont non rentrante (et donc pas garantie threadsafe) et ont été remplacées par la réentrée getaddrinfo () et freeaddrinfo () .
Vous remarquez peut-être un motif ici ... L'absence de sécurité (éventuellement en ne comprenant pas assez d'informations dans la signature pour la mettre en œuvre en toute sécurité) ou la non-reentrance sont des sources courantes de dépréciation.
Obsolète, non portable
[.____] Certaines autres fonctions deviennent simplement obsolètes car elles en double fonctionnalité et ne sont pas aussi portables que d'autres variantes. Par exemple, bzero () est dépréciée en faveur de memset () .
Sécurité du fil et réentruisement
[.____] Vous avez demandé, dans votre message, à propos de la sécurité et de la réentruisement du fil. 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 il a besoin est passée dans la fonction, et que les tampons nécessaires sont également passés dans la fonction (plutôt que partagés par tous les appels de 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éentrancement est une garantie plus forte que la sécurité du thread. Une fonction est la sécurité du fil si elle peut être utilisée simultanément par plusieurs threads. Une fonction est le fil de sécurité si:
En général, dans la Single UNIX Specification et IEEE 1003.1 (ie "POSIX"), toute fonction qui est pas garantie rentrante n'est pas garanti d'être thread-safe. Ainsi, en d'autres termes, seules les fonctions garanties à être réentrantes peuvent être utilisées de manière portante dans des applications multithreadées (sans verrouillage externe). Cela ne signifie toutefois pas que les implémentations de ces normes ne puissent pas choisir de faire une fonction de fonction non réentrante. Par exemple, Linux ajoute fréquemment la synchronisation à des fonctions non réentrantes afin d'ajouter une garantie (au-delà de la spécification UNIX unique) de la threadsafety.
Cordes (et tampons de mémoire, en général)
[.____] Vous avez également demandé s'il y a une faille fondamentale avec des chaînes/des tableaux. Certains pourraient affirmer que c'est le cas, mais je dirais que non, il n'y a pas de faille fondamentale dans la langue. C et C++ exigent que vous puissiez transmettre la longueur/la capacité d'un tableau séparément (ce n'est pas une propriété "Longueur" comme dans d'autres langues). Ce n'est pas une faille, en soi. Tout développeur C et C++ peut écrire du code correct simplement en passant la longueur comme paramètre si nécessaire. Le problème est que plusieurs API nécessitant ces informations ne l'ont pas permis de spécifier comme paramètre. Ou supposé que max_buffer_size constante serait utilisée. Ces API ont maintenant été obsolètes et remplacées par des API alternatives permettant de spécifier les tailles de tableau/tampon/chaîne.
Scanf (en réponse à votre dernière question)
[.____] Personnellement, j'utilise la bibliothèque C++ iostreams (STD :: CIN, STD :: COUT, THE << ET >> Opérateurs, STD :: GETLINE, STD :: ISTRingStream, STD :: OstringingStream, etc.) , donc je ne traite généralement pas de cela. Si je forcé d'utiliser pur C, bien que, personnellement, je simplement utiliser fgetc () ou getchar () en combinaison avec strtol () , strtoul () , etc. et d'analyser les choses manuellement, depuis que je ne suis pas un grand fan de varargs ou des chaînes de format. Cela dit, au meilleur de ma connaissance, il n'y a pas de problème avec [f] scanf () , [f] printf ( ) , etc. tant que vous ouvrer les chaînes de format vous passez, vous ne chaînes de format arbitraire ou d'autoriser l'entrée d'utilisateur à utiliser en tant que chaînes de format, et que vous utilisez les macros de formatage définies dans <inttypes.h> le cas échéant. (Note, snprintf () doit être utilisé à la place de sprintf () , mais qui a à voir avec à défaut de préciser la taille de la mémoire tampon de destination et non l'utilisation de chaînes de format). Je tiens également à souligner que, en C++, boost :: format fournit printf comme le formatage sans varargs.
Encore une fois les gens répètent, comme mantra, l'affirmation ridicule que la version " n " de fonctions str sont des versions sûres.
Si tel était ce qu'ils étaient destinés alors ils Null toujours mettre fin aux chaînes.
Les versions " n " des fonctions ont été écrites pour une utilisation avec des champs de longueur fixe (tels que les entrées de répertoire dans les systèmes de fichiers) où le début de terminaison NUL est nécessaire que si la chaîne ne remplit pas le champ. C'est aussi la raison pour laquelle les fonctions ont d'étranges effets secondaires qui sont inefficaces si inutilement tout utilisé en remplacement - prendre strncpy () par exemple:
Si le tableau pointé par s2 est une chaîne qui est plus courte que n octets, octets nuls sont ajoutés à la copie du tableau pointé par s1, jusqu'à ce que n octets dans tous sont écrits.
Comme tampons alloués aux noms de fichiers de poignée sont généralement 4KOctets cela peut conduire à une détérioration massive des performances.
Si vous voulez " soi-disant " versions sûres puis obtenir - ou écrire votre propre - routines STERLING (de strlcpy, strlcat etc) qui a toujours NUL mettre fin aux chaînes et ne pas avoir des effets secondaires. S'il vous plaît noter cependant que ce ne sont pas vraiment en sécurité, car ils peuvent silencieusement tronquer la chaîne - ce qui est rarement le meilleur plan d'action dans tous les programmes dans le monde réel. Il y a des occasions où cela est OK, mais il y a aussi beaucoup de cas où elle pourrait conduire à des résultats catastrophiques (par exemple l'impression des prescriptions médicales).
Plusieurs réponses suggèrent ici d'utiliser strncat()
Over strcat()
; Je suggérerais que strncat()
(et strncpy()
) devrait également être évitée. Il a des problèmes qui rendent difficile l'utilisation correctement et conduire à des bugs:
strncat()
est associé à (mais pas tout à fait exactement - voir le 3ème point) Le nombre maximum de caractères pouvant être copié sur la destination plutôt que la taille du tampon de destination. Cela fait strncat()
plus difficile à utiliser que nécessaire, en particulier si plusieurs éléments seront concatérés à la destination.s1
Est strlen(s1)+n+1
" pour un appel qui ressemble à strncat( s1, s2, n)
strncpy()
a également un problème qui peut entraîner des bugs que vous essayez de l'utiliser de manière intuitive - il ne garantit pas que la destination est null terminée. Pour vous assurer que vous devez vous assurer que vous gérez spécifiquement ce boîtier d'angle en laissant tomber un '\0'
Dans le dernier emplacement du tampon vous-même (au moins dans certaines situations).
Je suggérerais d'utiliser quelque chose comme l'openbsd strlcat()
et strlcpy()
(Bien que je sache que certaines personnes n'aiment pas ces fonctions; Je crois qu'ils 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 dû dire des problèmes de strncat()
et strncpy()
:
Il y a plusieurs problèmes rencontrés lorsque
strncpy()
etstrncat()
sont utilisées comme des versions sûres destrcpy()
etstrcat()
. Les deux fonctions traitent du nul-résiliation et du paramètre de longueur de manière différente et non intuitive qui confondent même les programmeurs expérimentés. Ils ne fournissent également aucun moyen facile de détecter lorsque la troncature se produit. ... de toutes ces questions, la confusion causée par les paramètres de longueur et la question connexe de la nul-résiliation sont les plus importantes. Lorsque nous avons vérifié l'arborescence source OpenBSD pour des trous de sécurité potentiels, nous avons trouvé une mauvaise utilisation générale destrncpy()
etstrncat()
. Bien que tous ces éléments ontient entraîné des trous de sécurité exploitables, ils ont précisé que les règles d'utilisationstrncpy()
etstrncat()
dans des opérations de chaîne sûres sont largement mal comprises.
L'audit de sécurité de OpenBSD a révélé que les bogues avec ces fonctions étaient "rampants". Contrairement à gets()
, ces fonctions peuvent être utilisées être utilisées en toute sécurité, mais en pratique, il y a beaucoup de problèmes car l'interface est confuse, non intuitive et difficile à utiliser correctement. Je sais que Microsoft a également effectué une analyse (même si je ne sais pas combien de leurs données qu'ils ont pu publier), et en conséquence ont interdit (ou au moins très fortement découragé - l'interdiction pourrait ne pas être absolue) la Utilisation de strncat()
et strncpy()
(entre autres fonctions).
Quelques liens avec plus d'informations:
setjmp.h
setjmp()
. En collaboration avec longjmp()
, ces fonctions sont largement reconnues comme incroyablement dangereuses à utiliser: elles conduisent à une programmation spaghetti, elles sont livrées avec de nombreuses formes de comportement non défini, elles peuvent provoquer des effets secondaires inattendus dans l'environnement de programme, tels que affectant les valeurs stockées sur la pile. Références: MISRA-C: 2012 Règle 21.4, CERT C MSC22-C .longjmp()
. Voir setjmp()
.stdio.h
gets()
. La fonction a été retirée du langage C (selon C11), car elle était dangereuse selon la conception. La fonction a déjà été signalée comme obsolète en C99. Utilisez fgets()
à la place. Références: ISO 9899: 2011 K.3.5.4.1, voir également la note 404.stdlib.h
atoi()
Famille de fonctions. Celles-ci n'ont aucune manipulation d'erreur mais invoquez un comportement non défini chaque fois que des erreurs se produisent. Fonctions entièrement superflues pouvant être remplacées par la famille strtol()
Famille de fonctions. Références: MISRRA-C: Règle 21.7 2012.string.h
strncat()
. A une interface gênante qui sont souvent mal utilisées. C'est surtout une fonction superflue. Voir aussi des remarques pour strncpy()
.strncpy()
. L'intention de cette fonction n'a jamais été une version plus sûre de strcpy()
. Son seul but était toujours de gérer un format de chaîne antique sur les systèmes UNIX et qu'il est inclus dans la bibliothèque standard est une erreur connue. Cette fonction est dangereuse car elle peut laisser la chaîne sans terminaison null 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 insécurités? .Assert.h
assert()
. Vient avec des frais généraux et ne devrait généralement pas être utilisé dans le code de production. Il est préférable d'utiliser un gestionnaire d'erreur spécifique à une application qui affiche des erreurs mais ne fermez pas nécessairement tout le programme.signal.h
signal()
. Références: MISRRA-C: Règle 21.5 2012, CERT C SIG32-C .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 de programmes. Devrait être évité à moins d'avoir des exigences très spécifiques. stdio.h
Généralement, Cette bibliothèque entière n'est pas recommandée pour le code de production, car il s'agit 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. Invoque un comportement indéfini s'il est utilisé pour les flux d'entrée.gets_s()
. Version sûre de gets()
Inclus dans l'interface de vérification des limites C11. Il est préférable d'utiliser fgets()
à la place, conformément à la recommandation standard. 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 de sécurité de type médiocre. sprintf()
a également des vulnérabilités. Ces fonctions doivent être évitées dans le code de production. Références: MISRRA-C: 2012 Règle 21.6.scanf()
Famille de fonctions. Voir des remarques sur printf()
. De plus, - scanf()
est vulnérable aux dépassements tampons si non utilisés correctement. fgets()
est préférable à utiliser si possible. Références: CERT C INT05-C , MISRRA-C: 2012 Règle 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 des systèmes hébergés, bien que connaître des problèmes connus en C90 et donc ne pas lancer le résultat . La famille malloc()
La famille de fonctions ne doit jamais être utilisée dans des applications autoportantes. Références: MISRRA-C: 2012 Règle 21.3.
Notez également que realloc()
est dangereux au cas où vous écrasez l'ancien pointeur avec le résultat de realloc()
. Si la fonction échoue, vous créez une fuite.
system()
. Livré avec beaucoup de frais généraux et bien que portables, il est souvent préférable d'utiliser des fonctions d'API spécifiques au système. Vient avec divers comportements mal définis. Références: CERT C ENV33-C .string.h
strcat()
. Voir Remarques pour strcpy()
.strcpy()
. Parfaitement bien à utiliser, à moins que la taille des données à copier est inconnue ou plus grande que le tampon de destination. Si aucune vérification de la taille de données entrante n'est effectuée, des dépassements tampons peuvent être soumis à des dépassements tampons. Ce qui n'est pas une faute de strcpy()
elle-même, mais de l'application d'appel - que strcpy()
est dangereux est surtout n mythe créé par Microsoft .strtok()
. Modifie la chaîne de l'appelant et utilise des variables d'état internes, ce qui pourrait le rendre dangereux dans un environnement multi-fileté.Éviter
strtok
pour les programmes multithreads comme son sans fil.gets
comme cela pourrait provoquer un débordement de tamponDécouvrez également la liste de Microsoft - API internes . Ce sont des API (y compris de nombreuses personnes déjà énuméré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 envisagés. Ils ajoutent une API à la liste lorsque sa mauvaise utilisation a conduit à un certain nombre de bugs de sécurité.
Il vaut probablement la peine d'ajouter que strncpy()
N'EST PAS le remplacement à usage général de strcpy()
Son nom pourrait suggérer. Il est conçu pour les champs de longueur fixe qui n'ont pas besoin d'un terminateur nul (il a été conçu à l'origine destiné à être utilisé avec des entrées de répertoire UNIX, mais peut être utile pour des éléments tels que des champs de clé de cryptage).
Il est toutefois facile d'utiliser strncat()
comme remplacement de strcpy()
:
if (dest_size > 0)
{
dest[0] = '\0';
strncat(dest, source, dest_size - 1);
}
(Le test if
peut évidemment être abandonné dans l'affaire commune, où vous savez que dest_size
est définitivement non nulle).
Il est très difficile d'utiliser scanf
en toute sécurité. Bon usage de scanf
peut éviter les débordements tampons, mais vous êtes toujours vulnérable au comportement non défini lors de la lecture de nombres qui ne correspondent pas au type demandé. Dans la plupart des cas, fgets
suivi d'une analyse auto-analysée (à l'aide de sscanf
, strchr
, etc.) est une meilleure option.
Mais je ne dirais pas "éviter scanf
tout le temps". scanf
a ses utilisations. Par exemple, disons que vous souhaitez lire la saisie de l'utilisateur dans un tableau char
10 octets longs. Vous voulez supprimer la nouvelle ligne de fuite, le cas échéant. Si l'utilisateur entre dans plus de 9 caractères avant une nouvelle ligne, vous souhaitez stocker les 9 premiers caractères du tampon et supprimer tout jusqu'à la nouvelle ligne suivante. 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 de manière 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;
}
}
Presque toute fonction qui traite des chaînes terminées de Nul est potentiellement dangereuse. Si vous recevez des données du monde extérieur et que vous le manipulez via les fonctions STR * (), vous vous préparerez à la catastrophe.
N'oubliez pas Sprintf - c'est la cause de nombreux problèmes. Ceci est vrai parce que l'alternative, SnPrintf a parfois des implémentations différentes pouvant vous rendre indispossibles.
Dans le cas 1 (Linux), la valeur de retour est la quantité de données requise pour stocker tout le tampon (s'il est inférieur à la taille du tampon donné, la sortie a été tronquée)
Dans le cas 2 (Windows), la valeur de retour est un nombre négatif au cas où la sortie est tronquée.
Généralement, vous devriez éviter les fonctions qui ne sont pas:
sécurité du débordement de la mémoire tampon (beaucoup de fonctions sont déjà mentionnées ici)
fil Safe/Not Reentrant (Strtok par exemple)
Dans le manuel de chaque fonction, vous devez rechercher des mots-clés tels que: coffre-fort, synchronisation, async, fil, tampon, bugs
Dans toute la chaîne de copie/scénarios de déplacement - strcat (), strncat (), strcpy (), strncpy (), etc. - les choses vont beaucoup mieux (plus sûr) si un heuristiques simples couple sont appliquées :
1. Toujours NUL remplir le tampon (s) avant l'ajout de données.
2. Declare caractères tampons [Taille + 1], avec une macro-constante.
Par exemple, avec:
#define BUFSIZE 10
char Buffer[BUFSIZE+1] = { 0x00 }; /* The compiler NUL-fills the rest */
nous pouvons utiliser le code:
memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");
relativement en toute sécurité. Le memset () doit apparaître avant la strncpy (), même si nous avons initialisé la mémoire tampon lors de la compilation, parce que nous ne savons pas ce que les déchets tout autre code placé dans avant notre fonction a été appelée. Le strncpy () tronque les données copiées sur " 1234567890 ", et non NUL-y mettre fin. Cependant, étant donné que nous avons déjà rempli NUL l'ensemble du tampon - sizeof (tampon), plutôt que BUFSIZE - il est garanti d'être une finale " hors-champ " NUL final de toute façon, tant que nous limitons nos écritures en utilisant le BUFSIZE constant, au lieu de sizeof (Buffer).
Tampon et BUFSIZE aussi bien de travail 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 () écrit spécifiquement seulement BUFIZE-1 caractères, suivis par NUL, cela fonctionne en toute sécurité. Nous avons donc " déchets " un octet NUL étranger à la fin du tampon ... nous évitons les deux dépassements de tampons et les conditions de chaîne non terminée, pour un joli petit coût de la mémoire.
Mon appel strcat () et strncat () est plus dur en ligne: ne les utilisez pas. Il est difficile d'utiliser strcat () toute sécurité, et l'API pour strncat () est si contre-intuitif que l'effort nécessaire pour l'utiliser correctement nie tout avantage. Je propose la baisse en ce qui suit:
#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)
Il est tentant de créer un strcat () drop-in, mais 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 retourne pas les informations nécessaires). Je n'ai pas une bonne solution " universelle " aux instances de strcat () dans votre code.
Un problème que je rencontre souvent de " strFunc () - aware " programmeurs est une tentative de se protéger contre tampon en utilisant strlen déborde (). Ceci est bien si le contenu sont garantis NUL terminé. Dans le cas contraire, strlen () lui-même peut provoquer une erreur de tampon dépassement (en général conduisant à une violation de segmentation ou toute autre situation core dump), avant de vous atteindre le code " problématique " que vous essayez de protéger.