Auch `strcpy` ist verboten
(daniel.haxx.se)- Das cURL-Projekt hat nach der früheren Entfernung von
strncpy()nun auchstrcpy()vollständig in der Codebasis verboten - Die API von
strcpy()ist zwar einfach, birgt aber das Risiko, dass die Prüfung der Puffergröße vom Kopiervorgang getrennt wird, was bei langfristiger Wartung unsicher ist - Als Ersatz wurde die neue Funktion
curlx_strcopy()eingeführt, die sowohl die Größe des Zielpuffers als auch die Länge der Zeichenkette als Argumente erhält und vor dem Kopieren prüft, ob der Vorgang möglich ist - Intern verwendet die Funktion
memcpy()und garantiert auch die Behandlung des Null-Terminators - Durch diese Änderung sollen Sicherheit und Konsistenz des Codes verbessert und zugleich Probleme reduziert werden, bei denen KI fälschlich Schwachstellenmeldungen erzeugt
Hintergrund zur Entfernung von strcpy
- cURL hat in der Vergangenheit bereits alle Aufrufe von
strncpy()entfernt und auf die Probleme dieser Funktion hingewiesen: eine unintuitive API, keine garantierte Null-Terminierung und unnötiges Auffüllen mit 0-Werten- Wenn Teilstrings kopiert werden müssen, wurde auf
memcpy()umgestellt und die Null-Terminierung direkt behandelt
- Wenn Teilstrings kopiert werden müssen, wurde auf
- Die API von
strcpy()ist zwar simpel, nennt aber keine Puffergröße ausdrücklich, wodurch bei der Wartung das Risiko entsteht, dass Prüfcode und Kopieraufruf voneinander getrennt werden- Wenn Code über Jahrzehnte von verschiedenen Entwicklern geändert wird, besteht die Möglichkeit, dass die Prüfung der Puffergröße ausgehebelt wird
Einführung einer neuen String-Kopierfunktion
- Um dieses Risiko zu vermeiden, wurde die Ersatzfunktion
curlx_strcopy()eingeführt- Sie nimmt als Argumente Zielpuffer, Puffergröße, Quellpuffer und Länge der Quellzeichenkette entgegen
- Sie führt den Kopiervorgang nur dann mit
memcpy()aus, wenn sowohl das Kopieren als auch die Null-Terminierung möglich sind - Im Fehlerfall wird der Zielpuffer als leerer String initialisiert
- Diese Funktion benötigt im Vergleich zu
strcpy()mehr Argumente und mehr Code, stellt die Sicherheit jedoch dadurch sicher, dass die Pufferprüfung eng an den Kopiervorgang gekoppelt ist - Die Verwendung von
strcpy()wurde in der cURL-Codebasis vollständig verboten und damit ebenso entfernt wiestrncpy()
Implementierungsdetails
- Ein Beispiel für die Funktionsdefinition sieht wie folgt aus
void curlx_strcopy(char *dest, size_t dsize, const char *src, size_t slen) { DEBUGASSERT(slen < dsize); if(slen < dsize) { memcpy(dest, src, slen); dest[slen] = 0; } else if(dsize) dest[0] = 0; } - Mit
DEBUGASSERTwerden Fehler während der Entwicklung früh erkannt, während die Funktion für echte Deployment-Umgebungen so ausgelegt ist, dass sie immer erfolgreich arbeitet - Wie
strcpygibt sie keinen Rückgabewert zurück; stattdessen setzt man darauf, Fehler in Test- und Fuzzing-Phasen zu finden
Reaktionen aus der Community
- Einige Entwickler merkten an, dass dies
strcpy_s()(C11 Annex K) ähnele, cURL verwende jedoch weiterhin den C89-Standard - Andere Stimmen schlugen vor, einen Rückgabewert hinzuzufügen oder die Behandlung bei Pufferfehlern zu verbessern
- Darauf erklärte cURL, ein Rückgabewert sei unnötig, weil die Funktion „so entworfen wurde, dass sie immer erfolgreich ist“
Zusätzlicher Effekt im Zusammenhang mit KI
- Durch diese Änderung lässt sich verhindern, dass KI-Chatbots die Verwendung von strcpy im cURL-Code fälschlich erkennen und als „verwundbar“ bezeichnen
- Der Autor merkte jedoch an, dass KI weiterhin andere falsche Meldungen erzeugen könnte, und verwies damit auf die Grenzen KI-basierter Codeanalyse
5 Kommentare
Statt
strcpysollte man bessersnprintfverwenden. Wenn im Codestrcpyvorkommt, sollte man die Adresse des Entwicklers herausfinden, der das eingebaut hat.Das war eine Arbeitsweise, die ich vor 25 Jahren, als ich bei einer Spielefirma arbeitete, mit Debug-Code umgesetzt habe – und es war sicher nicht nur
strcpy. Im Release wurde das für mehr Geschwindigkeit wieder gelockert und so in den Service übernommen. Tatsächlich ist man gerade im Games-Bereich bei Speicherkonflikten am empfindlichsten, deshalb arbeitet man auch extrem sorgfältig und aufmerksam, und wir haben sogar eigene Memory-Debugger gebaut und verwendet. Aber wenn ich heute darüber nachdenke, merke ich: Das war im Grunde schon dabei, eine Garbage Collection zu bauen. Eine wehmütige Erinnerung.Fehler C4996:
'strcpy': Diese Funktion oder Variable ist möglicherweise unsicher. Erwägen Sie stattdessen die Verwendung vonstrcpy_s. Um die Veraltungsmeldung zu deaktivieren, verwenden Sie_CRT_SECURE_NO_WARNINGS. Weitere Details finden Sie in der Onlinehilfe.Hacker-News-Kommentare
strcpy()ist nicht nur in puncto Sicherheit problematisch, sondern auch leistungstechnisch schlechtFrüher dachte man,
strcpy()sei effizient, wenn man die Stringlänge nicht kennt, aber tatsächlich kopiert es Byte für Byte, sodass die CPU Branch Prediction betreiben muss, was ineffizient iststrcpyeine skalare Schleife verwendet. Ich frage mich, ob das nur auf der ARM-Architektur so istDie String-Routinen in C haben alle große Einschränkungen, sodass sie sich kaum sinnvoll verwenden lassen
Deshalb halte ich eine Bibliothek für nötig, die zusammen mit dem String-Pointer auch die Größe des allokierten Speichers festhält
Als Beispiel kann man die bstring-Bibliothek ansehen
strncpywurde eingeführt, um Dateinamen mit fester Länge zu kopieren. Siehe dazu diese StackOverflow-Antwortstrncpywar ursprünglich für Strings mit fester Feldbreite gedacht. Zum Beispiel, um ein Feld wiechar username[20]mit NUL-Zeichen aufzufüllen. Siehe dazu das Manualstring_copying.7Es ist seltsam, dass
curlx_strcopykeinen Erfolgsstatus zurückgibtMan könnte zwar
dest[0]prüfen, aber das ist fehleranfällig und unintuivDEBUGASSERT(slen < dsize);durchläuft, aber in Release-Builds kannassertentfernt sein. Ein expliziter Fehlercode wäre besserstrncpy()war ursprünglich nicht für null-terminierte Strings gedacht, sondern für Felder fester LängeDas Problem begann, als statische Analysewerkzeuge empfahlen, statt
strcpyeinfachstrncpyzu verwenden. Die tatsächlichen Alternativen warensnprintfoderstrlcpystrlcpyist eine BSD-Funktion und nicht Teil von POSIX. Offiziell empfohlen wirdstpecpy, aber real existierende Implementierungen gibt es kaum. Siehe die zugehörige Dokumentationstrncpynach dem NUL-Zeichen auffüllt, diente effizienten Vergleichen bei Namensfeldern fester Länge wie Verzeichniseinträgen. So steht es auch in den Begründungsdokumenten zum ANSI-C-StandardDiese API fühlt sich ein wenig wie Annex-K an. Im Zielpuffer ist der Platz für NUL in der Größenangabe enthalten, bei der Quellgröße jedoch nicht
Ich denke, es wäre besser, einfach direkt
memcpyzu verwendenIm Artikel blieb mir die Formulierung in Erinnerung, dass „
strcpyein Köder für von AI erzeugte falsche Schwachstellenberichte“ seistrcpyals Problem hin, sondern erzeugt komplexe Begründungen mit logischen Fehlern, die Maintainer dann mühsam überprüfen müssenDas Prinzip „in der Nähe des Codes prüfen“ ist gut, aber es wird unklar, wenn man früh im Lebenszyklus der Daten prüfen muss
Es wäre gut, wenn man wie beim
Result-Typ in Rust per Typ ausdrücken könnte, dass es sich um „validierte Daten“ handeltResultenthält nur Erfolg oder Fehler, garantiert aber keinen validierten Zustand. Besser wäre ein eigener Typ, der nur nach einem Validierungsschritt erzeugt werden kann. Das ist die Philosophie „parse, don’t validate“Der Off-by-one-Unterschied zwischen Puffergröße und Stringlänge ist ein schreckliches Usability-Problem. Er wird wahrscheinlich auch künftig Fehler verursachen
Die neu vorgeschlagene String-Kopierfunktion leert den Zielpuffer, wenn das Kopieren nicht möglich ist, und gibt
voidzurückIn so einem Fall wäre es meiner Meinung nach besser, dies als Fehler zu behandeln und den Puffer unberührt zu lassen. Sich nur auf
DEBUGASSERTzu verlassen, wirkt unsicherGlückwunsch zum Abschluss des Projekts. Auch in C/C++ kann man sich mit genügend Aufwand Speichersicherheit erarbeiten
Allerdings ist in der mobilen Ansicht die Schriftgröße der Grafiken zu klein, was die Lesbarkeit verschlechtert
strcpyentfernt wurde, bedeutet nicht, dass der Code damit automatisch speichersicher istEs ist auch eine gute Idee, komplett auf die Sprache C3 umzusteigen. Es ist ein Projekt, das die C-Syntax nur minimal verändert und moderne Funktionen hinzufügt, sodass die Umstellung leichtfällt.