Ich führe meine a.out-Datei aus. Nach der Ausführung wird das Programm einige Zeit ausgeführt und dann mit der folgenden Meldung beendet:
**** stack smashing detected ***: ./a.out terminated*
*======= Backtrace: =========*
*/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)Aborted*
Was könnten die möglichen Gründe dafür sein und wie kann ich das beheben?
Antworten:
Stack Smashing wird hier tatsächlich durch einen Schutzmechanismus verursacht, der von gcc verwendet wird, um Pufferüberlauffehler zu erkennen. Zum Beispiel im folgenden Snippet:
Der Compiler (in diesem Fall gcc) fügt Schutzvariablen (Canaries genannt) mit bekannten Werten hinzu. Eine Eingabezeichenfolge mit einer Größe größer als 10 führt zu einer Beschädigung dieser Variablen, was dazu führt, dass SIGABRT das Programm beendet.
Um einen Einblick zu erhalten, können Sie versuchen, diesen Schutz von gcc mithilfe der Option
-fno-stack-protector
beim Kompilieren zu deaktivieren . In diesem Fall wird ein anderer Fehler angezeigt, höchstwahrscheinlich ein Segmentierungsfehler, wenn Sie versuchen, auf einen unzulässigen Speicherort zuzugreifen. Beachten Sie, dass-fstack-protector
dies für Release-Builds immer aktiviert sein sollte, da es sich um eine Sicherheitsfunktion handelt.Sie können einige Informationen über den Überlaufpunkt erhalten, indem Sie das Programm mit einem Debugger ausführen. Valgrind funktioniert nicht gut mit stapelbezogenen Fehlern, aber wie ein Debugger kann es Ihnen helfen, den Ort und den Grund für den Absturz genau zu bestimmen.
quelle
Beispiel für minimale Reproduktion mit Demontageanalyse
Haupt c
GitHub stromaufwärts .
Kompilieren und ausführen:
schlägt wie gewünscht fehl:
Getestet unter Ubuntu 16.04, GCC 6.4.0.
Demontage
Nun schauen wir uns die Demontage an:
was beinhaltet:
Beachten Sie die handlichen Kommentare automatisch hinzugefügt von
objdump
‚s Modul künstlicher Intelligenz .Wenn Sie dieses Programm mehrmals über GDB ausführen, sehen Sie Folgendes:
myfunc
ist genau das, was die Adresse des Kanarienvogels ändertDer Kanarienvogel wird randomisiert, indem er mit gesetzt
%fs:0x28
wird. Dieser enthält einen zufälligen Wert, wie unter:Debug-Versuche
Von nun an ändern wir den Code:
stattdessen sein:
interessanter sein.
Wir werden dann versuchen zu sehen, ob wir den Täteraufruf
+ 1
mit einer Methode lokalisieren können, die automatisierter ist als nur das Lesen und Verstehen des gesamten Quellcodes.gcc -fsanitize=address
um den Address Sanitizer (ASan) von Google zu aktivierenWenn Sie mit diesem Flag neu kompilieren und das Programm ausführen, wird Folgendes ausgegeben:
gefolgt von etwas mehr farbiger Ausgabe.
Dies zeigt deutlich die problematische Linie 12.
Der Quellcode hierfür lautet: https://github.com/google/sanitizers, aber wie wir aus dem Beispiel gesehen haben, wird er bereits in GCC übertragen.
ASan kann auch andere Speicherprobleme wie Speicherverluste erkennen: Wie finde ich einen Speicherverlust in einem C ++ - Code / Projekt?
Valgrind SGCheck
Wie von anderen erwähnt , ist Valgrind nicht gut darin, diese Art von Problem zu lösen.
Es gibt ein experimentelles Tool namens SGCheck :
Ich war also nicht sehr überrascht, als der Fehler nicht gefunden wurde:
Die Fehlermeldung sollte anscheinend so aussehen: Valgrind fehlender Fehler
GDB
Eine wichtige Beobachtung ist, dass, wenn Sie das Programm über GDB ausführen oder die
core
Datei nachträglich untersuchen :Dann sollte GDB, wie wir auf der Baugruppe gesehen haben, Sie auf das Ende der Funktion hinweisen, die die Kanarienvogelprüfung durchgeführt hat:
Und deshalb ist das Problem wahrscheinlich bei einem der Aufrufe dieser Funktion.
Als nächstes versuchen wir, den genauen fehlgeschlagenen Anruf zu lokalisieren, indem wir den ersten einzelnen Schritt unmittelbar nach dem Setzen des Kanarienvogels ausführen:
und die Adresse beobachten:
Dies lässt uns nun bei der richtigen beleidigenden Anweisung zurück:
len = 5
undi = 4
hat uns in diesem speziellen Fall auf die Täterlinie 12 hingewiesen.Die Rückverfolgung ist jedoch beschädigt und enthält Müll. Eine korrekte Rückverfolgung würde folgendermaßen aussehen:
Vielleicht könnte dies den Stapel beschädigen und Sie daran hindern, die Ablaufverfolgung zu sehen.
Bei dieser Methode muss auch bekannt sein, was der letzte Aufruf der Kanarienvogelprüffunktion ist. Andernfalls treten falsch positive Ergebnisse auf, die nur dann möglich sind, wenn Sie das Reverse-Debugging verwenden .
quelle
Bitte schauen Sie sich die folgende Situation an:
Als ich den Stack Smashing Protector deaktivierte, wurden keine Fehler festgestellt. Dies hätte passieren müssen, wenn ich "./a.out wepassssssssssssssssss" verwendet habe.
Um Ihre Frage oben zu beantworten, wurde die Meldung "** Stapelzerstörung erkannt: xxx" angezeigt, da Ihr Stapelzerstörungsschutz aktiv war und festgestellt wurde, dass in Ihrem Programm ein Stapelüberlauf vorliegt.
Finden Sie einfach heraus, wo dies auftritt, und beheben Sie es.
quelle
Sie könnten versuchen, das Problem mit valgrind zu debuggen :
quelle
Dies bedeutet, dass Sie auf illegale Weise auf einige Variablen auf dem Stapel geschrieben haben, höchstwahrscheinlich als Ergebnis eines Pufferüberlaufs .
quelle
Ein Szenario wäre das folgende Beispiel:
In diesem Programm können Sie einen String oder einen Teil des Strings umkehren, wenn Sie beispielsweise Folgendes aufrufen
reverse()
:Wenn Sie die Länge des Arrays wie folgt übergeben möchten:
Funktioniert auch gut.
Aber wenn Sie dies tun:
Sie erhalten:
Und dies geschieht, weil im ersten Code die Länge von
arr
überprüftrevSTR()
wird, was in Ordnung ist, aber im zweiten Code, in dem Sie die Länge übergeben:Die Länge ist jetzt länger als die tatsächliche Länge, die Sie überschreiten, wenn Sie sagen
arr + 2
.Länge von
strlen ( arr + 2 )
! =strlen ( arr )
.quelle
gets
und basiertscrcpy
. Ich frage mich, ob wir weiter minimieren könnten. Ich würde zumindest loswerdenstring.h
mitsize_t len = sizeof( arr );
. Getestet auf gcc 6.4, Ubuntu 16.04. Ich würde auch das fehlerhafte Beispiel mit dem geben,arr + 2
um das Einfügen von Kopien zu minimieren.Stapelverfälschungen, die normalerweise durch Pufferüberläufe verursacht werden. Sie können sich gegen sie verteidigen, indem Sie defensiv programmieren.
Wenn Sie auf ein Array zugreifen, stellen Sie ihm eine Zusicherung vor, um sicherzustellen, dass der Zugriff nicht außerhalb der Grenzen liegt. Beispielsweise:
Dies lässt Sie über Array-Grenzen nachdenken und über das Hinzufügen von Tests, um diese nach Möglichkeit auszulösen. Wenn einige dieser Behauptungen während des normalen Gebrauchs fehlschlagen können, verwandeln Sie sie in eine reguläre
if
.quelle
Ich habe diesen Fehler erhalten, als ich malloc () verwendet habe, um einer Struktur * Speicher zuzuweisen. Nachdem ich einige Zeit damit verbracht hatte, den Code zu debuggen, habe ich schließlich die Funktion free () verwendet, um den zugewiesenen Speicher freizugeben, und anschließend ist die Fehlermeldung verschwunden :)
quelle
Eine andere Quelle für das Zerschlagen von Stapeln ist die (falsche) Verwendung von
vfork()
anstelle vonfork()
.Ich habe gerade einen Fall debuggt, in dem der untergeordnete Prozess
execve()
die ausführbare Zieldatei nicht erreichen konnte und einen Fehlercode zurückgegeben hat, anstatt aufzurufen_exit()
.Da
vfork()
dieses Kind erzeugt wurde, kehrte es zurück, während es tatsächlich noch im Prozessbereich des Elternteils ausgeführt wurde, wodurch nicht nur der Stapel des Elternteils beschädigt wurde, sondern auch zwei unterschiedliche Diagnosesätze durch "nachgeschalteten" Code gedruckt wurden.Ändern
vfork()
auffork()
behebt beide Probleme, ebenso wie das Ändern derreturn
Aussage des Kindes in_exit()
stattdessen.Da
execve()
der untergeordnete Code dem Aufruf jedoch Aufrufe anderer Routinen vorausgeht (in diesem speziellen Fall zum Festlegen der UID / GID), erfüllt er technisch nicht die Anforderungen fürvfork()
, sodass eine Änderung der Verwendungfork()
hier richtig ist.(Beachten Sie, dass das problematisch ist
return
Anweisung war nicht wirklich als solche codiert - stattdessen ein Makro aufgerufen wurde, und das Makro entschieden , ob auf_exit()
oderreturn
basierend auf einer globalen Variablen , damit es nicht sofort klar war , dass das Kind Code für nicht - konforme wurde.vfork()
Nutzung. )Weitere Informationen finden Sie unter:
Der Unterschied zwischen fork (), vfork (), exec () und clone ()
quelle