Kann ein Stapelüberlauf zu etwas anderem als einem Segmentierungsfehler führen?

Lesezeit: 8 Minuten

Benutzeravatar von einpoklum
einpoklum

In einem kompilierten Programm (sagen wir C oder C ++, aber ich denke, diese Frage könnte sich auf jede nicht-VM-artige Sprache mit einem Aufrufstapel erstrecken) – sehr oft, wenn Sie Ihren Stapel überlaufen, erhalten Sie einen Segmentierungsfehler:

Stapelüberlauf ist [a] Ursache, Segmentierungsfehler ist die Folge.

Ist das aber immer so? Kann ein Stapelüberlauf zu anderen Arten von Programm-/Betriebssystemverhalten führen?

Ich frage auch nach Nicht-Linux-, Nicht-Windows-Betriebssystemen und Nicht-X86-Hardware. (Wenn Sie keinen Hardwarespeicherschutz oder keine Betriebssystemunterstützung dafür haben (z. B. MS-DOS), gibt es natürlich keinen Segmentierungsfehler; ich frage nach Fällen, in denen Sie könnte einen Segmentierungsfehler erhalten, aber etwas anderes passiert).

Hinweis: Nehmen Sie an, dass das Programm außer dem Stapelüberlauf gültig ist und nicht versucht, auf Arrays außerhalb ihrer Grenzen zuzugreifen, ungültige Zeiger zu dereferenzieren usw.

  • Sie können über die Schutzseite springen und eine andere kartierte Region treffen.

    – StaceyGirl

    6. Juni 2018 um 19:44 Uhr

  • Dies könnte dazu führen, dass Ihr Programm zu einer ungültigen Anweisung verzweigt

    – cleblanc

    6. Juni 2018 um 19:44 Uhr

  • Das Programm könnte sich korrekt und wie erwartet verhalten.

    – Drew Dormann

    6. Juni 2018 um 19:45 Uhr

  • qualys.com/2017/06/19/stack-clash/stack-clash.txt

    – Melpomen

    6. Juni 2018 um 19:46 Uhr

  • Die Antwort ist im Wesentlichen “alles”. Sicher, die meisten (alle?) gängigen modernen Betriebssysteme haben Schutzseiten, aber es gibt nichts, was eine garantiert. Sobald Sie auf den wahlfreien Speicher zugreifen, kann fast alles passieren. In Ermangelung von Nur-Lese-Codepages oder Ausführen von Schutzbits könnten Sie Kauderwelsch-Code erstellen oder versuchen, Nicht-Code-Speicher auszuführen. Sie könnten einen Funktionszeiger beschädigen, der von einem anderen Teil des Programms gespeichert wurde, wodurch er an eine andere Stelle springt. Usw…

    – Linuxios

    6. Juni 2018 um 19:47 Uhr

Benutzeravatar von Dietrich Epp
Dietrich Ep

Ja, sogar auf einem Standardbetriebssystem (Linux) und Standardhardware (x86).

void f(void) {
    char arr[BIG_NUMBER];
    arr[0] = 0; // stack overflow
}

Beachten Sie, dass der Stack auf x86 kleiner wird, also weisen wir den Anfang des Arrays zu, um den Überlauf auszulösen. Es gelten die üblichen Haftungsausschlüsse … das genaue Verhalten hängt von mehr Faktoren ab, als in dieser Antwort besprochen werden, einschließlich der Einzelheiten Ihres C-Compilers.

Wenn die BIG_NUMBER gerade noch so groß ist, dass sie überläuft, stoßen Sie auf den Stapelwächter und erhalten einen Segmentierungsfehler. Dafür ist der Stapelwächter da, und er kann so klein wie eine einzelne 4-KiB-Seite sein (aber nicht kleiner, und diese 4-KiB-Größe wird vor Linux 4.12 verwendet) oder größer (1 MiB standardmäßig unter Linux 4.12 , sehen mm: große Stapelschutzlücke), aber es ist immer eine bestimmte Größe.

Wenn BIG_NUMBER groß genug ist, kann der Überlauf den Stapelwächter überspringen und auf einem anderen Teil des Speichers landen, der möglicherweise gültig ist. Dies kann dazu führen, dass sich Ihr Programm falsch verhält, aber nicht abstürzt, was im Grunde das Worst-Case-Szenario ist: wir wollen dass unsere Programme abstürzen, wenn sie falsch sind, anstatt etwas Unbeabsichtigtes zu tun.

  • Aus Neugier: Gibt einer der Standard-Compiler eine Warnung bei Code wie diesem aus? Irgendeine “vom Stapel zugewiesene lokale Variable … läuft wahrscheinlich über …”?

    – Linuxios

    6. Juni 2018 um 19:52 Uhr

  • Wäre (ist) das nicht ein großer potenzieller Exploit?

    – Stefan

    6. Juni 2018 um 19:53 Uhr


  • @melpomene: lol, ich habe das vor einer Minute hochgeladen und auf meine Leseliste gesetzt.

    – Stefan

    6. Juni 2018 um 19:56 Uhr

  • @Linuxios MSVC-Einfügungen _chkstk Rufen Sie Funktionen auf, deren Rahmen größer als eine einzelne Seite ist. Sein Zweck ist es, Segfaults während der Stapelerweiterung zu vermeiden (Windows-spezifische Sache), aber es erkennt auch einen Stapelüberlauf und löst eine strukturierte Ausnahme aus. Dies sollte garantieren, dass ein Stapelüberlauf immer zu einer Ausnahme führt und eine unbemerkte Datenbeschädigung vermeidet.

    – StaceyGirl

    6. Juni 2018 um 20:01 Uhr


  • Da die Stack-Zuordnung/-Größe in der Verantwortung des Linkers/Loaders liegt, kann ein Compiler nicht einfach/zuverlässig Warnungen/Fehler ausgeben.

    – Martin Jakob

    6. Juni 2018 um 20:21 Uhr


Benutzeravatar von Jesper Juhl
Jesper Juhl

Eine Sache ist, was zur Laufzeit passiert, wenn Sie den Stapel überlaufen lassen, was viele Dinge sein können. Einschließlich, aber nicht beschränkt auf; Segmentierungsfehler, Überschreiben von Variablen, was auch immer Sie überlaufen, Verursachen einer illegalen Anweisung, überhaupt nichts und vieles mehr. Das „alte“ klassische Papier Zerschmettere den Stack für Spaß und Profit beschreibt viele Möglichkeiten, wie man mit diesem Zeug “Spaß” haben kann.

Eine andere Sache ist, was zur Kompilierzeit passieren kann. Sowohl in C als auch in C++ ist das Schreiben über ein Array hinaus oder das Überschreiten der Größe des Stapels ein undefiniertes Verhalten, und wenn ein Programm UB enthält irgendwo Der Compiler ist grundsätzlich frei was es will zu irgendein Teil Ihres Programms. Und moderne Compiler werden sehr aggressiv darin, UB für Optimierungszwecke auszunutzen – oft indem sie annehmen, dass UB nie passiert, was dazu führt, dass sie einfach den Code entfernen, der UB enthält, oder dass eine Verzweigung immer oder nie genommen wird, weil die Alternative UB verursachen würde. Manchmal führt der Compiler ein Zeitreise oder Rufen Sie eine Funktion auf, die im Quellcode nie aufgerufen wurde und viele, viele andere Dinge, die ein wirklich verwirrendes Laufzeitverhalten verursachen können.

Siehe auch:

Was jeder C-Programmierer über undefiniertes Verhalten wissen sollte #1/3

Was jeder C-Programmierer über undefiniertes Verhalten wissen sollte #2/3

Was jeder C-Programmierer über undefiniertes Verhalten wissen sollte #3/3

Ein Leitfaden zu undefiniertem Verhalten in C und C++, Teil 1

Ein Leitfaden zu undefiniertem Verhalten in C und C++, Teil 2

Ein Leitfaden zu undefiniertem Verhalten in C und C++, Teil 3

  • Ich habe nicht über das Überschreiten von Array-Grenzen gesprochen, sondern über ein absolut gültiges Programmverhalten, außer dass der für den Stapel zugewiesene Speicherplatz überschritten wird. Lassen Sie mich das in der Frage klarstellen.

    – einpoklum

    6. Juni 2018 um 20:47 Uhr


  • @einpoklum überschreitet die Größe des Stapels nicht gültiges Programmverhalten und würde gleichermaßen zu UB führen.

    – Jesper Juhl

    6. Juni 2018 um 20:50 Uhr


  • Eine der Möglichkeiten (und die häufigste), einen Stack überlaufen zu lassen ist um auf ein Stack-zugewiesenes Array außerhalb seiner Grenzen zuzugreifen.

    – Eugen Sch.

    6. Juni 2018 um 20:52 Uhr

  • @JesperJuhl: Wenn Sie Ihr Programm schreiben, wissen Sie nicht, wie groß der Stapel sein wird.

    – einpoklum

    6. Juni 2018 um 20:56 Uhr

  • @einpoklum Du meinst den für den Stapel zugewiesenen Speicherplatz oder die Nutzung? Ersteres können Sie sicher wissen. Letztere können beschränkt oder geschätzt werden.

    – Eugen Sch.

    6. Juni 2018 um 20:58 Uhr

Andere Antworten haben die PC-Seite ziemlich gut abgedeckt. Ich werde einige der Probleme in der eingebetteten Welt ansprechen.

Eingebetteter Code hat etwas Ähnliches wie ein Segfault. Code wird in einer Art nichtflüchtigem Speicher gespeichert (normalerweise heutzutage Flash, aber in der Vergangenheit eine Art ROM oder PROM). Das Schreiben darauf erfordert spezielle Operationen, um es einzurichten; Normale Speicherzugriffe können daraus lesen, aber nicht darauf schreiben. Darüber hinaus weisen eingebettete Prozessoren in der Regel große Lücken in ihren Speicherabbildern auf. Wenn der Prozessor eine Schreibanforderung für einen schreibgeschützten Speicher oder eine Lese- oder Schreibanforderung für eine Adresse erhält, die physisch nicht existiert, löst der Prozessor normalerweise eine Hardware-Ausnahme aus. Wenn Sie einen Debugger angeschlossen haben, können Sie den Status des Systems überprüfen, um herauszufinden, was schief gelaufen ist, wie bei einem Core-Dump.

Es gibt jedoch keine Garantie dafür, dass dies bei einem Stapelüberlauf geschieht. Der Stapel kann überall im RAM platziert werden, und dies wird normalerweise neben anderen Variablen sein. Das Ergebnis eines Stapelüberlaufs ist normalerweise die Beschädigung dieser Variablen.

Wenn Ihre Anwendung auch Heap (dynamische Zuweisung) verwendet, ist es üblich, einen Speicherabschnitt zuzuweisen, in dem der Stapel am Ende dieses Abschnitts beginnt und nach oben erweitert wird, und der Heap oben in diesem Abschnitt beginnt und nach unten erweitert wird. Dies bedeutet eindeutig, dass dynamisch zugewiesene Daten das erste Opfer sein werden.

Wenn Sie Pech haben, bemerken Sie es möglicherweise nicht einmal, und dann müssen Sie herausfinden, warum sich Ihr Code nicht richtig verhält. Im ironischsten Fall, wenn die zu überschreibenden Daten ein Zeiger sind, erhalten Sie möglicherweise immer noch eine Hardware-Ausnahme, wenn der Zeiger versucht, auf ungültigen Speicher zuzugreifen – aber dies wird einige Zeit nach dem Stapelüberlauf sein, und die natürliche Annahme wird normalerweise sein ein Fehler in Ihrem Code.

Eingebetteter Code hat ein allgemeines Muster, um damit umzugehen, nämlich den Stapel mit einem “Wasserzeichen” zu versehen, indem jedes Byte mit einem bekannten Wert initialisiert wird. Manchmal kann der Compiler dies tun; oder manchmal müssen Sie es vor main() selbst im Startcode implementieren. Sie können vom Ende des Stacks zurückblicken, um herauszufinden, wo dieser Wert nicht mehr festgelegt ist. An diesem Punkt kennen Sie die obere Grenze für die Stack-Nutzung. oder wenn alles falsch ist, dann wissen Sie, dass Sie einen Überlauf haben. Es ist üblich (und bewährte Praxis), dass eingebettete Anwendungen dies kontinuierlich als Hintergrundoperation abfragen und es zu Diagnosezwecken melden können.

Da es möglich ist, die Stack-Nutzung zu verfolgen, legen die meisten Unternehmen eine akzeptable Worst-Case-Marge fest, um Überläufe zu vermeiden. Dies liegt normalerweise zwischen 75 % und 90 %, aber es wird immer etwas übrig bleiben. Dies lässt nicht nur die Möglichkeit zu, dass es einen schlimmeren Worst-Case gibt, den Sie noch nicht gesehen haben, sondern erleichtert auch die zukünftige Entwicklung, wenn neuer Code hinzugefügt werden muss, der mehr Stack verwendet.

Stackoverflow ist einer der vielen Gründe dafür undefiniertes Verhalten eines Programms. In diesem Fall können Sie ein erwartetes Ergebnis oder einen Segmentierungsfehler erhalten oder Ihre Festplatte könnte gelöscht werden usw. Erwarten Sie kein definiertes Verhalten, da es sich um ein undefiniertes Verhalten handelt.

1386870cookie-checkKann ein Stapelüberlauf zu etwas anderem als einem Segmentierungsfehler führen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy