int func(char* str)
{
char buffer[100];
unsigned short len = strlen(str);
if(len >= 100)
{
return (-1);
}
strncpy(buffer,str,strlen(str));
return 0;
}
Dieser Code ist anfällig für einen Pufferüberlaufangriff, und ich versuche herauszufinden, warum. Ich denke, es hat damit zu tun, len
dass man als a short
statt als deklariert wird int
, aber ich bin mir nicht sicher.
Irgendwelche Ideen?
c
security
buffer-overflow
Jason
quelle
quelle
strncpy
. In diesem Fall ist es nicht.strlen
berechnet, für die Gültigkeitsprüfung verwendet und dann wieder absurd berechnet wird - es ist ein DRY-Fehler. Wenn die zweitestrlen(str)
durch ersetztlen
würde, gäbe es keine Möglichkeit eines Pufferüberlaufs, unabhängig von der Art derlen
. Die Antworten sprechen diesen Punkt nicht an, sie schaffen es nur, ihn zu vermeiden.Antworten:
Bei den meisten Compilern beträgt der Maximalwert von a
unsigned short
65535.Jeder darüber liegende Wert wird umbrochen, sodass 65536 zu 0 und 65600 zu 65 wird.
Dies bedeutet, dass lange Zeichenfolgen mit der richtigen Länge (z. B. 65600) die Prüfung bestehen und den Puffer überlaufen.
Verwenden Sie
size_t
diese Option , um das Ergebnis vonstrlen()
nicht zu speichernunsigned short
undlen
mit einem Ausdruck zu vergleichen , der die Größe von direkt codiertbuffer
. Also zum Beispiel:quelle
len
als drittes Argument von strncpy, um einen Pufferüberlauf zu verhindern . Strlen wieder zu benutzen ist auf jeden Fall dumm./ sizeof(buffer[0])
- Beachten Sie, dasssizeof(char)
in C immer 1 ist (auch wenn ein Zeichen eine Unmenge Bits enthält). Dies ist überflüssig, wenn keine Möglichkeit besteht, einen anderen Datentyp zu verwenden. Trotzdem ... ein großes Lob für eine vollständige Antwort (und danke, dass Sie auf Kommentare reagiert haben).char[]
undchar*
sind nicht dasselbe. Es gibt viele Situationen, in denen achar[]
implizit in a umgewandelt wirdchar*
. Dies ist beispielsweisechar[]
genau das Gleiche wiechar*
bei Verwendung als Typ für Funktionsargumente. Die Konvertierung erfolgt jedoch nicht fürsizeof()
.buffer
irgendwann ändern, wird der Ausdruck automatisch aktualisiert. Dies ist für die Sicherheit von entscheidender Bedeutung, da die Deklaration vonbuffer
möglicherweise einige Zeilen vom Einchecken des tatsächlichen Codes entfernt ist. Es ist also einfach, die Größe des Puffers zu ändern, aber vergessen Sie, an jedem Ort, an dem die Größe verwendet wird, zu aktualisieren.Das Problem ist hier:
Wenn die Zeichenfolge größer als die Länge des Zielpuffers ist, kopiert strncpy sie weiterhin. Sie verwenden die Anzahl der Zeichen der Zeichenfolge als die zu kopierende Anzahl anstelle der Größe des Puffers. Der richtige Weg, dies zu tun, ist wie folgt:
Dadurch wird die Menge der kopierten Daten auf die tatsächliche Größe des Puffers minus eins für das Null-Abschlusszeichen begrenzt. Dann setzen wir das letzte Byte im Puffer als zusätzlichen Schutz auf das Nullzeichen. Der Grund dafür ist, dass strncpy bis zu n Bytes kopiert, einschließlich der abschließenden Null, wenn strlen (str) <len - 1. Wenn nicht, wird die Null nicht kopiert und Sie haben ein Absturzszenario, da Ihr Puffer jetzt nicht abgeschlossen ist Zeichenfolge.
Hoffe das hilft.
BEARBEITEN: Nach weiterer Prüfung und Eingabe durch andere folgt eine mögliche Codierung für die Funktion:
Da wir die Länge der Zeichenfolge bereits kennen, können wir memcpy verwenden, um die Zeichenfolge von der Position, auf die str verweist, in den Puffer zu kopieren. Beachten Sie, dass auf der Handbuchseite für strlen (3) (auf einem FreeBSD 9.3-System) Folgendes angegeben ist:
Was ich so interpretiere, dass die Länge der Zeichenfolge nicht die Null enthält. Aus diesem Grund kopiere ich len + 1 Bytes, um die Null einzuschließen, und der Test prüft, ob die Länge <Größe des Puffers - 2 ist. Minus eins, da der Puffer an Position 0 beginnt, und minus eins, um sicherzustellen, dass Platz vorhanden ist für die Null.
BEARBEITEN: Es stellt sich heraus, dass die Größe von etwas mit 1 beginnt, während der Zugriff mit 0 beginnt. Daher war die -2 vorher falsch, da sie einen Fehler für alles> 98 Bytes zurückgeben würde, aber> 99 Bytes sein sollte.
BEARBEITEN: Obwohl die Antwort auf eine vorzeichenlose Kurzform im Allgemeinen korrekt ist, da die maximal darstellbare Länge 65.535 Zeichen beträgt, spielt dies keine Rolle, da der Wert umbrochen wird, wenn die Zeichenfolge länger ist. Es ist, als würde man 75.231 (das ist 0x000125DF) nehmen und die oberen 16 Bits maskieren, was 9695 (0x000025DF) ergibt. Das einzige Problem, das ich dabei sehe, sind die ersten 100 Zeichen nach 65.535, da die Längenprüfung das Kopieren zulässt, aber in allen Fällen nur bis zu den ersten 100 Zeichen der Zeichenfolge kopiert und die Zeichenfolge mit Null beendet wird . Selbst mit dem Wraparound-Problem wird der Puffer immer noch nicht überlaufen.
Dies kann an sich ein Sicherheitsrisiko darstellen oder nicht, abhängig vom Inhalt der Zeichenfolge und dem, wofür Sie sie verwenden. Wenn es sich nur um geraden Text handelt, der von Menschen gelesen werden kann, gibt es im Allgemeinen kein Problem. Sie erhalten nur eine abgeschnittene Zeichenfolge. Wenn es sich jedoch um eine URL oder sogar eine SQL-Befehlssequenz handelt, liegt möglicherweise ein Problem vor.
quelle
func
... und jeder anderen jemals geschriebenen C-Funktion, die NUL-terminierte Zeichenfolgen als Argumente verwendet , unvermeidbar ist . Es ist völlig ahnungslos, die Möglichkeit zu erwähnen, dass der Eingang nicht NUL-terminiert ist.len >= 100
) wurde für einen Wert durchgeführt, aber die Länge der Kopie wurde mit einem anderen Wert versehen ... dies ist eine Verletzung des DRY-Prinzips. Ein einfacher Aufrufstrncpy(buffer, str, len)
vermeidet die Möglichkeit eines Pufferüberlaufs und macht weniger Arbeit alsstrncpy(buffer,str,sizeof(buffer) - 1)
... obwohl es hier nur ein langsameres Äquivalent zu istmemcpy(buffer, str, len)
.Auch wenn Sie verwenden
strncpy
, hängt die Länge des Cutoffs immer noch vom übergebenen Zeichenfolgenzeiger ab. Sie haben keine Ahnung, wie lang diese Zeichenfolge ist (dh die Position des Nullterminators relativ zum Zeiger). Wenn Siestrlen
alleine anrufen, sind Sie anfällig für Sicherheitslücken. Wenn Sie sicherer sein möchten, verwenden Siestrnlen(str, 100)
.Vollständiger Code korrigiert wäre:
quelle
strlen
über das Ende des Puffers hinaus zugreifen?strnlen
nicht, warum das Problem nicht gelöst wird, wenn das, was orlp vorschlägt, angeblich sowieso richtig ist.buffer
. "da str auf einen Puffer von 2 Bytes verweisen könnte, von denen keines NUL ist." - das ist irrelevant, da es für jede Implementierung von giltfunc
. Die Frage hier ist über Pufferüberlauf, nicht UB, weil der Eingang nicht NUL-terminiert ist.Die Antwort mit der Verpackung ist richtig. Aber es gibt ein Problem, von dem ich denke, dass es nicht erwähnt wurde, wenn (len> = 100)
Nun, wenn Len 100 wäre, würden wir 100 Elemente kopieren und hätten kein nachfolgendes \ 0. Dies würde eindeutig bedeuten, dass jede andere Funktion in Abhängigkeit von der richtigen Endzeichenfolge weit über das ursprüngliche Array hinausgehen würde.
Die Zeichenfolge problematisch von C ist meiner Meinung nach unlösbar. Sie sollten immer einige Grenzen vor dem Anruf haben, aber selbst das wird nicht helfen. Es gibt keine Überprüfung der Grenzen und so können und werden Pufferüberläufe immer passieren ....
quelle
strncpy()
und Freunde, aber die Speicherzuweisung funktioniert wiestrdup()
und Freunde. Sie sind im POSIX-2008-Standard enthalten, daher sind sie ziemlich portabel, obwohl sie auf einigen proprietären Systemen nicht verfügbar sind.buffer
ist lokal für diese Funktion und wird an keiner anderen Stelle verwendet. In einem realen Programm müssten wir untersuchen, wie es verwendet wird ... manchmal ist eine NUL-Terminierung nicht korrekt (die ursprüngliche Verwendung von strncpy bestand darin, die 14-Byte-Verzeichniseinträge von UNIX zu erstellen - NUL-aufgefüllt und nicht NUL-terminiert). "Die von C problematische Zeichenfolge ist meiner Meinung nach unlösbar" - während C eine krasse Sprache ist, die von einer weitaus besseren Technologie übertroffen wurde, kann sicherer Code darin geschrieben werden, wenn genügend Disziplin angewendet wird.if (len >= 100)
ist die Bedingung, wenn die Prüfung fehlschlägt , nicht wenn sie bestanden wird, was bedeutet, dass es keinen Fall gibt, in dem genau 100 Bytes ohne NUL-Terminator kopiert werden, da diese Länge in der Fehlerbedingung enthalten ist.Abgesehen von den Sicherheitsproblemen, die mit dem mehrmaligen Aufrufen verbunden
strlen
sind, sollte man im Allgemeinen keine String-Methoden für Strings verwenden, deren Länge genau bekannt ist [für die meisten String-Funktionen gibt es nur einen wirklich engen Fall, in dem sie verwendet werden sollten - für Strings, für die maximal Länge kann garantiert werden, aber die genaue Länge ist nicht bekannt]. Sobald die Länge der Eingabezeichenfolge bekannt ist und die Länge des Ausgabepuffers bekannt ist, sollte man herausfinden, wie groß eine Region kopiert werden soll, und dann verwendenmemcpy()
, um die betreffende Kopie tatsächlich durchzuführen. Obwohl es möglich ist, dass beim Kopieren einer Zeichenfolge mit nur etwa 1 bis 3 Byte einestrcpy
Outperformancememcpy()
erzielt wird, ist dies auf vielen Plattformenmemcpy()
bei größeren Zeichenfolgen wahrscheinlich mehr als doppelt so schnell.Obwohl es einige Situationen gibt, in denen Sicherheit zu Lasten der Leistung gehen würde, ist dies eine Situation, in der der sichere Ansatz auch der schnellere ist. In einigen Fällen kann es sinnvoll sein, Code zu schreiben, der nicht gegen Eingaben mit seltsamem Verhalten gesichert ist, wenn der Code, der die Eingaben liefert, sicherstellen kann, dass sie sich gut verhalten, und wenn der Schutz vor Eingaben mit schlechtem Verhalten die Leistung beeinträchtigen würde. Wenn Sie sicherstellen, dass die Zeichenfolgenlängen nur einmal überprüft werden, verbessern Sie sowohl die Leistung als auch die Sicherheit. Sie können jedoch eine zusätzliche Maßnahme ergreifen, um die Sicherheit auch bei manueller Verfolgung der Zeichenfolgenlänge zu gewährleisten: Schreiben Sie für jede Zeichenfolge, für die eine nachfolgende Null erwartet wird, die nachfolgende Null explizit als zu erwarten, dass die Quellzeichenfolge es hat. Wenn man also ein
strdup
Äquivalent schreibt :Beachten Sie, dass die letzte Anweisung im Allgemeinen weggelassen werden kann, wenn der Memcpy
len+1
Bytes verarbeitet hat. Wenn jedoch ein anderer Thread die Quellzeichenfolge ändert, kann das Ergebnis eine nicht NUL-terminierte Zielzeichenfolge sein.quelle
strlen
erklären, die mit mehr als einmaligen Anrufen verbunden sind ?strlen
und eine Aktion basierend auf dem zurückgegebenen Wert ausgeführt hat (was vermutlich der Grund für den Aufruf war), liefert ein wiederholter Aufruf entweder (1) immer die gleiche Antwort wie der erste. In diesem Fall ist es einfach verschwendete Arbeit, oder (2) kann manchmal (weil etwas anderes - vielleicht ein anderer Thread - die Zeichenfolge in der Zwischenzeit geändert hat) eine andere Antwort ergeben. In diesem Fall Code, der einige Dinge mit der Länge macht (z Zuweisen eines Puffers) kann eine andere Größe annehmen als Code, der andere Aufgaben ausführt (Kopieren in den Puffer).