In einem Buch, das ich lese, steht, dass printf
mit einem einzigen Argument (ohne Konvertierungsspezifizierer) veraltet ist. Es wird empfohlen, zu ersetzen
printf("Hello World!");
mit
puts("Hello World!");
oder
printf("%s", "Hello World!");
Kann mir jemand sagen warum printf("Hello World!");
das falsch ist? In dem Buch steht, dass es Schwachstellen enthält. Was sind diese Schwachstellen?
printf("Hello World!")
ist nicht dasselbe wieputs("Hello World!")
.puts()
fügt a hinzu'\n'
. Vergleichen Sie stattdessenprintf("abc")
mitfputs("abc", stdout)
printf
es auf die gleiche Weisegets
veraltet ist , wie es beispielsweise in C99 veraltet ist. Sie können daher in Betracht ziehen, Ihre Frage genauer zu bearbeiten.Antworten:
printf("Hello World!");
ist meiner Meinung nach nicht verwundbar, aber bedenken Sie Folgendes:Wenn Sie
str
zufällig auf eine Zeichenfolge mit Formatspezifizierern verweisen%s
, zeigt Ihr Programm ein undefiniertes Verhalten (meistens ein Absturz), währendputs(str)
die Zeichenfolge nur so angezeigt wird, wie sie ist.Beispiel:
quelle
puts
dies vermutlich schneller sein wird.puts
ist "vermutlich" schneller, und dies ist wahrscheinlich ein weiterer Grund, warum die Leute es empfehlen, aber es ist nicht wirklich schneller. Ich habe gerade"Hello, world!"
1.000.000 Mal gedruckt , in beide Richtungen. Damitprintf
dauerte es 0,92 Sekunden. Damitputs
dauerte es 0,93 Sekunden. Es gibt Dinge, über die man sich Sorgen machen muss, wenn es um Effizienz geht, aberprintf
vs.puts
gehört nicht dazu.puts
ist schneller" falsch ist, sie ist immer noch falsch.puts
aus diesem Grund anzurufen. (Aber wenn Sie darüber streiten wollten: Ich wäre überrascht, wenn Sie einen modernen Compiler für eine moderne Maschine finden könnten, derputs
deutlich schneller ist alsprintf
unter allen Umständen.)printf("Hello world");
ist in Ordnung und hat keine Sicherheitslücke.
Das Problem liegt bei:
Dabei
p
handelt es sich um einen Zeiger auf eine Eingabe, die vom Benutzer gesteuert wird. Es ist anfällig für Formatierungsangriffe : Benutzer können Konvertierungsspezifikationen einfügen, um die Kontrolle über das Programm zu übernehmen, z. B.%x
um Speicher zu sichern oder%n
um Speicher zu überschreiben.Beachten Sie, dass das
puts("Hello world")
Verhalten nicht gleichbedeutend ist mit,printf("Hello world")
sondern mitprintf("Hello world\n")
. Compiler sind normalerweise intelligent genug, um den letzteren Aufruf zu optimieren und durch ihn zu ersetzenputs
.quelle
printf(p,x)
Genauso problematisch wäre es natürlich , wenn der Benutzer die Kontrolle darüber hättep
. Das Problem ist also nicht die Verwendungprintf
mit nur einem Argument, sondern mit einer benutzergesteuerten Formatzeichenfolge.printf(p)
, weil sie nicht erkennen, dass es sich um eine Formatzeichenfolge handelt, denken sie nur, dass sie ein Literal drucken.Neben den anderen Antworten
printf("Hello world! I am 50% happy today")
ist ein Fehler leicht zu machen, der möglicherweise alle möglichen unangenehmen Speicherprobleme verursacht (es ist UB!).Es ist einfach, einfacher und robuster, von Programmierern zu verlangen, dass sie absolut klar sind, wenn sie eine wörtliche Zeichenfolge und nichts anderes wollen .
Und das
printf("%s", "Hello world! I am 50% happy today")
bringt dich. Es ist völlig kinderleicht.(Steve ist natürlich
printf("He has %d cherries\n", ncherries)
absolut nicht dasselbe. In diesem Fall ist die Programmiererin nicht in der Einstellung "wörtliche Zeichenfolge"; sie ist in der Einstellung "Formatzeichenfolge".)quelle
printf
" ist genau das Gleiche wie "Immer schreiben"if(NULL == p)
. Diese Regeln können für einige Programmierer nützlich sein, aber nicht für alle. In beiden Fällen (nicht übereinstimmendeprintf
Formate und Yoda-Bedingungen) warnen moderne Compiler ohnehin vor Fehlern. Daher sind die künstlichen Regeln noch weniger wichtig.printf("%s", "hello")
wird langsamer sein alsprintf("hello")
, also gibt es einen Nachteil. Ein kleines, weil IO fast immer viel langsamer ist als solch eine einfache Formatierung, aber ein Nachteil.gcc -Wall -W -Werror
verhindert schlimme Konsequenzen aus solchen Fehlern.Ich werde hier nur ein paar Informationen zum Schwachstellenteil hinzufügen .
Es soll wegen der Sicherheitslücke im Format printf string format anfällig sein. In Ihrem Beispiel ist die Zeichenfolge, in der die Zeichenfolge fest codiert ist, harmlos (selbst wenn eine solche Festcodierung von Zeichenfolgen niemals vollständig empfohlen wird). Die Angabe der Parametertypen ist jedoch eine gute Angewohnheit. Nehmen Sie dieses Beispiel:
Wenn jemand anstelle einer regulären Zeichenfolge ein Formatzeichenfolgenzeichen in Ihre printf einfügt (z. B. wenn Sie das Programm stdin drucken möchten), nimmt printf alles, was er kann, auf den Stapel.
Es wurde (und wird) sehr häufig verwendet, um Programme zum Erkunden von Stapeln auszunutzen, um beispielsweise auf verborgene Informationen zuzugreifen oder die Authentifizierung zu umgehen.
Beispiel (C):
wenn ich als Eingabe dieses Programms setze
"%08x %08x %08x %08x %08x\n"
Dies weist die printf-Funktion an, fünf Parameter vom Stapel abzurufen und als 8-stellige aufgefüllte Hexadezimalzahlen anzuzeigen. Eine mögliche Ausgabe könnte also so aussehen:
Sehen Sie dies für eine vollständigere Erklärung und andere Beispiele.
quelle
Das Aufrufen
printf
mit Zeichenfolgen im Literalformat ist sicher und effizient, und es gibt Tools, die Sie automatisch warnen, wenn Ihr Aufrufprintf
mit vom Benutzer angegebenen Formatzeichenfolgen nicht sicher ist.Die schwersten Angriffe
printf
nutzen den%n
Formatbezeichner. Im Gegensatz zu allen anderen Formatbezeich zB%d
,%n
schreibt eigentlich einen Wert in einer Speicheradresse in eines der Format Argumente zur Verfügung gestellt. Dies bedeutet, dass ein Angreifer den Speicher überschreiben und somit möglicherweise die Kontrolle über Ihr Programm übernehmen kann. Wikipedia bietet mehr Details.Wenn Sie
printf
mit einer Literalformatzeichenfolge aufrufen , kann sich ein Angreifer nicht%n
in Ihre Formatzeichenfolge einschleichen , und Sie sind somit sicher. Tatsächlich ändert gcc Ihren Anrufprintf
in einen Anruf inputs
, sodass es keinen Unterschied gibt (testen Sie dies durch Ausführengcc -O3 -S
).Wenn Sie
printf
mit einer vom Benutzer angegebenen Formatzeichenfolge aufrufen , kann sich ein Angreifer möglicherweise%n
in Ihre Formatzeichenfolge einschleichen und die Kontrolle über Ihr Programm übernehmen. Ihr Compiler wird Sie normalerweise warnen, dass sein Computer unsicher ist-Wformat-security
. Es gibt auch erweiterte Tools, die sicherstellen, dass ein Aufruf vonprintf
auch mit vom Benutzer bereitgestellten Formatzeichenfolgen sicher ist, und sie können sogar überprüfen, ob Sie die richtige Anzahl und Art von Argumenten an übergebenprintf
. Für Java gibt es beispielsweise Google's Error Prone und das Checker Framework .quelle
Dies ist ein fehlgeleiteter Rat. Ja, wenn Sie eine Laufzeitzeichenfolge zum Drucken haben,
ist ziemlich gefährlich, und Sie sollten immer verwenden
stattdessen, weil man im Allgemeinen nie wissen kann, ob
str
ein%
Zeichen enthalten sein könnte . Wenn Sie jedoch eine konstante Zeichenfolge zur Kompilierungszeit haben , ist daran überhaupt nichts auszusetzen(Unter anderem ist dies das klassischste C-Programm aller Zeiten, buchstäblich aus dem C-Programmierbuch von Genesis. Jeder, der diese Verwendung ablehnt, ist also eher ketzerisch, und ich jedenfalls wäre etwas beleidigt!)
quelle
because printf's first argument is always a constant string
Ich bin mir nicht ganz sicher, was du damit meinst."He has %d cherries\n"
ist eine konstante Zeichenfolge, was bedeutet, dass es sich um eine Konstante zur Kompilierungszeit handelt. Um fair zu sein, lautete der Rat des Autors nicht "Übergeben Sie keine konstanten Zeichenfolgen alsprintf
erstes Argument", sondern "Übergeben Sie keine Zeichenfolgen ohne%
alsprintf
erstes Argument".literally from the C programming book of Genesis. Anyone deprecating that usage is being quite offensively heretical
- Sie haben K & R in den letzten Jahren nicht wirklich gelesen. Es gibt eine Menge Ratschläge und Codierungsstile, die heutzutage nicht nur veraltet, sondern einfach nur schlecht sind.int
" zu verwenden, kommt mir in den Sinn.)Ein ziemlich unangenehmer Aspekt
printf
ist, dass selbst auf Plattformen, auf denen das Lesen des Streuspeichers nur begrenzten (und akzeptablen) Schaden verursachen kann, eines der Formatierungszeichen%n
bewirkt, dass das nächste Argument als Zeiger auf eine beschreibbare Ganzzahl interpretiert wird, und das Anzahl der bisher ausgegebenen Zeichen, die in der dadurch identifizierten Variablen gespeichert werden sollen. Ich habe diese Funktion selbst nie verwendet, und manchmal verwende ich einfache Methoden im Printf-Stil, die ich so geschrieben habe, dass sie nur die Funktionen enthalten, die ich tatsächlich verwende (und die eine oder ähnliche nicht enthalten), aber die empfangenen Standard-Printf-Funktionszeichenfolgen einspeisen aus nicht vertrauenswürdigen Quellen können Sicherheitslücken aufdecken, die über das Lesen von beliebigem Speicher hinausgehen.quelle
Da niemand erwähnt hat, möchte ich einen Hinweis zu ihrer Leistung hinzufügen.
Unter normalen Umständen würde ich unter der Annahme, dass keine Compiler-Optimierungen verwendet werden (dh
printf()
tatsächlich Aufrufeprintf()
und nichtfputs()
),printf()
eine weniger effiziente Leistung erwarten , insbesondere bei langen Zeichenfolgen. Dies liegt daranprintf()
, dass die Zeichenfolge analysiert werden muss, um zu überprüfen, ob Konvertierungsspezifizierer vorhanden sind.Um dies zu bestätigen, habe ich einige Tests durchgeführt. Der Test wird unter Ubuntu 14.04 mit gcc 4.8.4 durchgeführt. Mein Computer verwendet eine Intel i5-CPU. Das getestete Programm lautet wie folgt:
Beide sind kompiliert mit
gcc -Wall -O0
. Die Zeit wird mit gemessentime ./a.out > /dev/null
. Das Folgende ist das Ergebnis eines typischen Laufs (ich habe sie fünf Mal ausgeführt, alle Ergebnisse liegen innerhalb von 0,002 Sekunden).Für die
printf()
Variante:Für die
fputs()
Variante:Dieser Effekt wird verstärkt, wenn Sie eine sehr lange Saite haben.
Für die
printf()
Variante (dreimal gelaufen, echtes Plus / Minus 1,5s):Für die
fputs()
Variante (dreimal ausgeführt, real plus / minus 0,2 s):Hinweis: Nachdem ich die von gcc generierte Assembly überprüft hatte, stellte ich fest, dass gcc den
fputs()
Aufruf einesfwrite()
Aufrufs auch mit optimiert-O0
. (Derprintf()
Aufruf bleibt unverändert.) Ich bin nicht sicher, ob dies meinen Test ungültig macht, da der Compiler die Zeichenfolgenlänge zurfwrite()
Kompilierungszeit berechnet .quelle
fputs()
häufig bei Zeichenfolgenkonstanten verwendet wird, und diese Optimierungsmöglichkeit ist Teil des Punktes, den Sie ansprechen möchten. Das heißt, das Hinzufügen eines Testlaufs mit einer dynamisch generierten Zeichenfolge mitfputs()
undfprintf()
wäre ein netter zusätzlicher Datenpunkt ./dev/null
Dies macht dies zu einem Spielzeug. Normalerweise besteht Ihr Ziel beim Generieren einer formatierten Ausgabe darin, dass die Ausgabe irgendwohin geht und nicht verworfen wird. Wie vergleichen sie die Zeit, wenn Sie die Zeit hinzufügen, in der die Daten tatsächlich nicht verworfen werden?Kompiliert automatisch auf das Äquivalent
Sie können dies überprüfen, indem Sie Ihre ausführbare Datei zerlegen:
mit
wird zu Sicherheitsproblemen führen, verwenden Sie printf niemals so!
Ihr Buch ist also tatsächlich korrekt. Die Verwendung von printf mit einer Variablen ist veraltet. Sie können jedoch weiterhin printf ("my string \ n") verwenden, da es automatisch zu Puts wird
quelle
A compiles to B
, in Wirklichkeit aber Sie bedeutenA and B compile to C
.Für gcc ist es möglich, bestimmte Warnungen zur Überprüfung
printf()
und zu aktivierenscanf()
.In der gcc-Dokumentation heißt es:
Das,
-Wformat
was in der-Wall
Option aktiviert ist, aktiviert nicht mehrere spezielle Warnungen, die beim Auffinden dieser Fälle helfen:-Wformat-nonliteral
wird warnen, wenn Sie keinen String litteral als Formatbezeichner übergeben.-Wformat-security
wird warnen, wenn Sie eine Zeichenfolge übergeben, die möglicherweise ein gefährliches Konstrukt enthält. Es ist eine Teilmenge von-Wformat-nonliteral
.Ich muss zugeben, dass das Aktivieren
-Wformat-security
mehrere Fehler in unserer Codebasis aufgedeckt hat (Protokollierungsmodul, Fehlerbehandlungsmodul, XML-Ausgabemodul). Alle hatten einige Funktionen, die undefinierte Dinge tun konnten, wenn sie mit% Zeichen in ihrem Parameter aufgerufen wurden. Unsere Codebasis ist jetzt ungefähr 20 Jahre alt und selbst wenn wir uns dieser Art von Problemen bewusst waren, waren wir äußerst überrascht, als wir diese Warnungen aktivierten, wie viele dieser Fehler sich noch in der Codebasis befanden.quelle
Neben den anderen gut erläuterten Antworten mit allen Nebenanliegen möchte ich eine präzise und präzise Antwort auf die gestellte Frage geben.
Ein
printf
Funktionsaufruf mit einem einzigen Argumente wird im Allgemeinen nicht als veraltet und hat auch keine Schwachstellen , wenn sie ordnungsgemäß verwendet wird, wie Sie immer kodieren sollen.C Benutzer auf der ganzen Welt, vom Statusanfänger bis zum Statusexperten
printf
, geben auf diese Weise eine einfache Textphrase als Ausgabe an die Konsole.Außerdem muss jemand unterscheiden, ob dieses einzige Argument ein Zeichenfolgenliteral oder ein Zeiger auf eine Zeichenfolge ist, die gültig ist, aber häufig nicht verwendet wird. Für letztere kann es natürlich zu unbequemen Ausgaben oder zu undefiniertem Verhalten kommen , wenn der Zeiger nicht richtig auf eine gültige Zeichenfolge verweist, aber diese Dinge können auch auftreten, wenn die Formatspezifizierer nicht mit den jeweiligen Argumenten übereinstimmen mehrere Argumente.
Natürlich ist es auch nicht richtig und richtig, dass die Zeichenfolge, die als einziges Argument bereitgestellt wird, Format- oder Konvertierungsspezifizierer hat, da keine Konvertierung stattfinden wird.
Das heißt, ein einfaches String-Literal wie
"Hello World!"
als einziges Argument ohne Formatbezeichner in diesem String anzugeben, wie Sie es in der Frage angegeben haben:ist weder veraltet oder " schlechte Praxis " noch hat es keine Schwachstellen.
Tatsächlich haben viele C-Programmierer begonnen, C oder sogar Programmiersprachen im Allgemeinen mit diesem HelloWorld-Programm und dieser
printf
Aussage als erste ihrer Art zu lernen und zu verwenden .Das wären sie nicht, wenn sie veraltet wären.
Dann würde ich mich auf das Buch oder den Autor selbst konzentrieren. Wenn ein Autor meiner Meinung nach wirklich solche falschen Behauptungen aufstellt und dies sogar lehrt, ohne explizit zu erklären, warum er / sie dies tut (wenn diese Behauptungen in diesem Buch wirklich buchstäblich gleichwertig sind), würde ich es als schlechtes Buch betrachten. Ein gutes Buch soll im Gegensatz dazu erklären, warum bestimmte Arten von Programmiermethoden oder -funktionen vermieden werden sollten.
Nach dem, was ich oben gesagt habe, wird die Verwendung
printf
mit nur einem Argument (einem String-Literal) und ohne Formatbezeichner auf keinen Fall als "schlechte Praxis" missbilligt oder angesehen .Sie sollten den Autor fragen, was er damit gemeint hat oder noch besser, und ihn bitten, den entsprechenden Abschnitt für die nächste Ausgabe oder die Abdrücke im Allgemeinen zu klären oder zu korrigieren.
quelle
printf("Hello World!");
ist nicht äquivalentputs("Hello World!");
sowieso, was etwas über den Autor der Empfehlung erzählt.