Dans un livre que je lis, il est écrit printf
qu'avec un seul argument (sans spécificateurs de conversion) est obsolète. Il recommande de remplacer
printf("Hello World!");
avec
puts("Hello World!");
ou
printf("%s", "Hello World!");
Quelqu'un peut-il me dire pourquoi printf("Hello World!");
est faux? Il est écrit dans le livre qu'il contient des vulnérabilités. Quelles sont ces vulnérabilités?
printf("Hello World!")
n'est pas la même chose queputs("Hello World!")
.puts()
ajoute un'\n'
. Comparez plutôtprintf("abc")
àfputs("abc", stdout)
printf
soit obsolète de la même manière que, par exemple,gets
dans C99, vous pouvez donc envisager de modifier votre question pour être plus précis.Réponses:
printf("Hello World!");
IMHO n'est pas vulnérable mais considérez ceci:S'il
str
arrive à pointer vers une chaîne contenant des%s
spécificateurs de format, votre programme affichera un comportement indéfini (principalement un plantage), alorsputs(str)
qu'il affichera simplement la chaîne telle quelle.Exemple:
la source
puts
sera probablement plus rapide.puts
est "vraisemblablement" plus rapide, et c'est probablement une autre raison pour laquelle les gens le recommandent, mais ce n'est pas vraiment plus rapide. Je viens d'imprimer"Hello, world!"
1 000 000 fois, dans les deux sens. Avecprintf
cela a pris 0,92 secondes. Avecputs
cela a pris 0,93 secondes. Il y a des choses à craindre en matière d'efficacité, maisprintf
vs.puts
n'en fait pas partie.puts
est plus rapide" est fausse, c'est toujours faux.puts
pour cette raison. (Mais si vous vouliez en discuter: je serais surpris si vous pouviez trouver un compilateur moderne pour toute machine moderne où ilputs
est nettement plus rapide queprintf
dans toutes les circonstances.)printf("Hello world");
est bien et n'a aucune vulnérabilité de sécurité.
Le problème réside dans:
où
p
est un pointeur vers une entrée contrôlée par l'utilisateur. Il est sujet aux attaques de chaînes de formatage : l'utilisateur peut insérer des spécifications de conversion pour prendre le contrôle du programme, par exemple%x
pour vider la mémoire ou%n
pour écraser la mémoire.Notez que son
puts("Hello world")
comportement n'est pas équivalent àprintf("Hello world")
mais àprintf("Hello world\n")
. Les compilateurs sont généralement assez intelligents pour optimiser ce dernier appel pour le remplacer parputs
.la source
printf(p,x)
serait tout aussi problématique si l'utilisateur avait le contrôle surp
. Le problème n'est donc pas l'utilisation deprintf
avec un seul argument, mais plutôt avec une chaîne de format contrôlée par l'utilisateur.printf(p)
, c'est parce qu'ils ne se rendent pas compte que c'est une chaîne de format, ils pensent juste qu'ils impriment un littéral.En plus des autres réponses, il
printf("Hello world! I am 50% happy today")
y a un bug facile à faire, causant potentiellement toutes sortes de problèmes de mémoire désagréables (c'est UB!).Il est simplement plus simple, plus facile et plus robuste de «demander» aux programmeurs d'être absolument clairs lorsqu'ils veulent une chaîne textuelle et rien d'autre .
Et c'est ce qui
printf("%s", "Hello world! I am 50% happy today")
vous attire. C'est totalement infaillible.(Steve, bien sûr, ce
printf("He has %d cherries\n", ncherries)
n'est absolument pas la même chose; dans ce cas, le programmeur n'est pas dans l'état d'esprit "chaîne verbatim"; elle est dans l'état d'esprit "chaîne de formatage".)la source
printf
" revient exactement à dire "toujours écrireif(NULL == p)
. Ces règles peuvent être utiles pour certains programmeurs, mais pas pour tous. Et dans les deux cas (printf
formats incompatibles et conditions Yoda), les compilateurs modernes avertissent quand même des erreurs, donc les règles artificielles sont encore moins importantes.printf("%s", "hello")
va être plus lent queprintf("hello")
, donc il y a un inconvénient. Un petit, car IO est presque toujours beaucoup plus lent qu'un formatage aussi simple, mais un inconvénient.gcc -Wall -W -Werror
évitera les mauvaises conséquences de telles erreurs.Je vais juste ajouter quelques informations concernant la partie vulnérabilité ici.
On dit qu'il est vulnérable en raison de la vulnérabilité du format de chaîne printf. Dans votre exemple, où la chaîne est codée en dur, elle est inoffensive (même si le codage en dur de telles chaînes n'est jamais entièrement recommandé). Mais spécifier les types de paramètres est une bonne habitude à prendre. Prenons cet exemple:
Si quelqu'un met un caractère de chaîne de format dans votre printf au lieu d'une chaîne régulière (par exemple, si vous voulez imprimer le programme stdin), printf prendra tout ce qu'il peut sur la pile.
Il était (et est toujours) très utilisé pour exploiter des programmes en explorant des piles pour accéder à des informations cachées ou contourner l'authentification par exemple.
Exemple (C):
si je mets en entrée de ce programme
"%08x %08x %08x %08x %08x\n"
Cela indique à la fonction printf de récupérer cinq paramètres de la pile et de les afficher sous forme de nombres hexadécimaux remplis à 8 chiffres. Ainsi, une sortie possible peut ressembler à:
Voir ceci pour une explication plus complète et d'autres exemples.
la source
L'appel
printf
avec des chaînes de format littérales est sûr et efficace, et il existe des outils pour vous avertir automatiquement si votre appelprintf
avec les chaînes de format fournies par l'utilisateur n'est pas sûr.Les attaques les plus sévères
printf
tirent parti du%n
spécificateur de format. Contrairement à tous les autres spécificateurs de format, par exemple%d
,%n
écrit en fait une valeur dans une adresse mémoire fournie dans l'un des arguments de format. Cela signifie qu'un attaquant peut écraser la mémoire et ainsi potentiellement prendre le contrôle de votre programme. Wikipedia fournit plus de détails.Si vous appelez
printf
avec une chaîne de format littérale, un attaquant ne peut pas se faufiler%n
dans votre chaîne de format et vous êtes donc en sécurité. En fait, gcc transformera votre appelprintf
en un appel àputs
, donc il n'y a littéralement aucune différence (testez ceci en exécutantgcc -O3 -S
).Si vous appelez
printf
avec une chaîne de format fournie par l'utilisateur, un attaquant peut potentiellement insérer un%n
dans votre chaîne de format et prendre le contrôle de votre programme. Votre compilateur vous avertira généralement que le sien n'est pas sûr, voir-Wformat-security
. Il existe également des outils plus avancés qui garantissent qu'un appel deprintf
est sûr, même avec des chaînes de format fournies par l'utilisateur, et ils peuvent même vérifier que vous passez le bon nombre et le bon type d'arguments àprintf
. Par exemple, pour Java, il existe Google's Error Prone et Checker Framework .la source
Ce sont des conseils mal avisés. Oui, si vous avez une chaîne d'exécution à imprimer,
est assez dangereux et vous devriez toujours utiliser
au lieu de cela, car en général, vous ne pouvez jamais savoir si
str
peut contenir un%
signe. Cependant, si vous avez une chaîne constante au moment de la compilation , il n'y a rien de mal avec(Entre autres choses, c'est le programme C le plus classique de tous les temps, littéralement du livre de programmation C de Genesis. Donc, quiconque désapprouve cet usage est plutôt hérétique, et moi, je serais quelque peu offensé!)
la source
because printf's first argument is always a constant string
Je ne sais pas exactement ce que vous entendez par là."He has %d cherries\n"
est une chaîne constante, ce qui signifie qu'il s'agit d'une constante au moment de la compilation. Mais, pour être honnête, le conseil de l'auteur n'était pas "Ne passez pas les chaînes constantes commeprintf
premier argument", mais "Ne passez pas les chaînes sans%
commeprintf
premier argument".literally from the C programming book of Genesis. Anyone deprecating that usage is being quite offensively heretical
- vous n'avez pas lu K&R ces dernières années. Il y a une tonne de conseils et de styles de codage qui sont non seulement obsolètes, mais tout simplement une mauvaise pratique de nos jours.int
" me vient à l'esprit.)Un aspect plutôt désagréable de
printf
est que même sur les plates-formes où la lecture de la mémoire parasite ne peut causer que des dommages limités (et acceptables), l'un des caractères de formatage%n
, fait que l'argument suivant est interprété comme un pointeur vers un entier inscriptible, et provoque le nombre de caractères sortis jusqu'à présent pour être stockés dans la variable ainsi identifiée. Je n'ai jamais utilisé cette fonctionnalité moi-même, et parfois j'utilise des méthodes légères de style printf que j'ai écrites pour n'inclure que les fonctionnalités que j'utilise réellement (et n'inclut pas celle-là ou quoi que ce soit de similaire) mais alimentant les chaînes de fonctions printf standard reçues provenant de sources non fiables peuvent exposer des failles de sécurité au-delà de la capacité de lire un stockage arbitraire.la source
Puisque personne ne l'a mentionné, j'ajouterais une note concernant leurs performances.
Dans des circonstances normales, en supposant qu'aucune optimisation du compilateur ne soit utilisée (c'est-à-dire en
printf()
fait des appelsprintf()
et nonfputs()
), je m'attendraisprintf()
à être moins efficace, en particulier pour les longues chaînes. En effet,printf()
il faut analyser la chaîne pour vérifier s'il existe des spécificateurs de conversion.Pour confirmer cela, j'ai effectué quelques tests. Les tests sont effectués sur Ubuntu 14.04, avec gcc 4.8.4. Ma machine utilise un processeur Intel i5. Le programme testé est le suivant:
Les deux sont compilés avec
gcc -Wall -O0
. Le temps est mesuré à l'aide detime ./a.out > /dev/null
. Ce qui suit est le résultat d'une exécution typique (je les ai exécutés cinq fois, tous les résultats sont dans les 0,002 secondes).Pour la
printf()
variante:Pour la
fputs()
variante:Cet effet est amplifié si vous avez une très longue corde.
Pour la
printf()
variante (exécutée trois fois, réel plus / moins 1,5 s):Pour la
fputs()
variante (exécutée trois fois, réel plus / moins 0,2 s):Remarque: Après avoir inspecté l'assembly généré par gcc, j'ai réalisé que gcc optimise l'
fputs()
appel à unfwrite()
appel, même avec-O0
. (L'printf()
appel reste inchangé.) Je ne suis pas sûr que cela invalidera mon test, car le compilateur calcule la longueur de la chaînefwrite()
au moment de la compilation.la source
fputs()
est souvent utilisé avec des constantes de chaîne et cette opportunité d'optimisation fait partie du point que vous vouliez faire.Cela dit, ajouter un test avec une chaîne générée dynamiquement avecfputs()
etfprintf()
serait un bon point de données supplémentaire ./dev/null
en fait un jouet, dans la mesure où généralement lors de la génération d'une sortie formatée, votre objectif est que la sortie aille quelque part et ne soit pas rejetée. Une fois que vous avez ajouté le temps "ne pas supprimer les données", comment se comparent-ils?compile automatiquement vers l'équivalent
vous pouvez le vérifier en désassemblant votre exécutable:
en utilisant
entraînera des problèmes de sécurité, n'utilisez jamais printf de cette façon!
votre livre est donc correct, l'utilisation de printf avec une variable est obsolète mais vous pouvez toujours utiliser printf ("ma chaîne \ n") car elle deviendra automatiquement put
la source
A compiles to B
dites, mais en réalité vous voulez direA and B compile to C
.Pour gcc, il est possible d'activer des avertissements spécifiques pour vérifier
printf()
etscanf()
.La documentation gcc indique:
Le
-Wformat
qui est activé dans l'-Wall
option n'active pas plusieurs avertissements spéciaux qui aident à trouver ces cas:-Wformat-nonliteral
vous avertira si vous ne passez pas une chaîne littérale comme spécificateur de format.-Wformat-security
vous avertira si vous passez une chaîne qui pourrait contenir une construction dangereuse. C'est un sous-ensemble de-Wformat-nonliteral
.Je dois admettre que l'activation a
-Wformat-security
révélé plusieurs bogues que nous avions dans notre base de code (module de journalisation, module de gestion des erreurs, module de sortie xml, tous avaient des fonctions qui pouvaient faire des choses indéfinies si elles avaient été appelées avec% caractères dans leur paramètre. Pour info, notre base de code a maintenant environ 20 ans et même si nous étions conscients de ce genre de problèmes, nous avons été extrêmement surpris lorsque nous avons activé ces avertissements, combien de ces bogues étaient encore dans la base de code).la source
En plus des autres réponses bien expliquées avec toutes les préoccupations secondaires couvertes, je voudrais donner une réponse précise et concise à la question posée.
Un
printf
appel de fonction avec un seul argument en général n'est pas obsolète et n'a pas non plus de vulnérabilités lorsqu'il est utilisé correctement comme vous coderez toujours.C Les utilisateurs du monde entier, du statut de débutant à celui d'expert en statut, l'utilisent
printf
pour donner une simple phrase de texte en sortie à la console.De plus, quelqu'un doit distinguer si ce seul et unique argument est une chaîne littérale ou un pointeur vers une chaîne, ce qui est valide mais généralement pas utilisé. Pour ce dernier, bien sûr, il peut se produire des sorties gênantes ou tout type de comportement non défini, lorsque le pointeur n'est pas correctement défini pour pointer vers une chaîne valide, mais ces choses peuvent également se produire si les spécificateurs de format ne correspondent pas aux arguments respectifs en donnant arguments multiples.
Bien sûr, il n'est pas non plus juste et correct que la chaîne, fournie comme un seul et unique argument, ait des spécificateurs de format ou de conversion, car il n'y aura pas de conversion.
Cela dit, donner un littéral de chaîne simple comme
"Hello World!"
comme seul argument sans aucun spécificateur de format à l'intérieur de cette chaîne comme vous l'avez fourni dans la question:n'est pas du tout obsolète ou « mauvaise pratique » et n'a aucune vulnérabilité.
En fait, de nombreux programmeurs C commencent et ont commencé à apprendre et à utiliser C ou même les langages de programmation en général avec ce programme HelloWorld et cette
printf
déclaration comme les premiers du genre.Ils ne le seraient pas s'ils étaient obsolètes.
Eh bien, alors je me concentrerais sur le livre ou sur l'auteur lui-même. Si un auteur fait vraiment de telles affirmations , à mon avis, des affirmations incorrectes et même enseigne cela sans expliquer explicitement pourquoi il / elle le fait (si ces affirmations sont vraiment littéralement équivalentes fournies dans ce livre), je considérerais cela comme un mauvais livre. Un bon livre, par opposition à cela, expliquera pourquoi éviter certains types de méthodes ou de fonctions de programmation.
D'après ce que j'ai dit ci-dessus, l'utilisation
printf
avec un seul argument (une chaîne littérale) et sans aucun spécificateur de format n'est en aucun cas déconseillée ou considérée comme une "mauvaise pratique" .Vous devriez demander à l'auteur ce qu'il voulait dire par là ou, mieux encore, lui demander de clarifier ou de corriger la section relative pour la prochaine édition ou les empreintes en général.
la source
printf("Hello World!");
n'est pas équivalent à deputs("Hello World!");
toute façon, ce qui en dit long sur l'auteur de la recommandation.