Ich studiere die ELF-Spezifikation ( http://www.skyfree.org/linux/references/ELF_Format.pdf ) und ein Punkt, der mir über den Programmladeprozess nicht klar ist, ist, wie und was der Stapel initialisiert wird Die anfängliche Seitengröße ist. Hier ist der Test (unter Ubuntu x86-64):
$ cat test.s
.text
.global _start
_start:
mov $0x3c,%eax
mov $0,%edi
syscall
$ as test.s -o test.o && ld test.o
$ gdb a.out -q
Reading symbols from a.out...(no debugging symbols found)...done.
(gdb) b _start
Breakpoint 1 at 0x400078
(gdb) run
Starting program: ~/a.out
Breakpoint 1, 0x0000000000400078 in _start ()
(gdb) print $sp
$1 = (void *) 0x7fffffffdf00
(gdb) info proc map
process 20062
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x401000 0x1000 0x0 ~/a.out
0x7ffff7ffa000 0x7ffff7ffd000 0x3000 0x0 [vvar]
0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x0 [vdso]
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
Die ELF-Spezifikation hat sehr wenig darüber zu sagen, wie oder warum diese Stapelseite überhaupt existiert, aber ich kann Referenzen finden, die besagen, dass der Stapel mit SP initialisiert werden sollte, das auf argc zeigt, mit argv, envp und dem Hilfsvektor direkt darüber das, und ich habe dies bestätigt. Aber wie viel Platz ist unter SP verfügbar? Auf meinem System sind 0x1FF00
Bytes unterhalb von SP zugeordnet, aber vermutlich zählt dies von der Oberseite des Stapels bei herunter 0x7ffffffff000
, und es gibt 0x21000
Bytes in der vollständigen Zuordnung. Was beeinflusst diese Zahl?
Mir ist bewusst, dass die Seite direkt unter dem Stapel eine "Schutzseite" ist, die automatisch beschreibbar wird und "den Stapel verkleinert", wenn ich darauf schreibe (vermutlich, damit die naive Stapelbehandlung "nur funktioniert"), aber wenn ich eine zuweise Ein riesiger Stapelrahmen, dann könnte ich die Schutzseite und den Segfault überschreiten. Daher möchte ich feststellen, wie viel Speicherplatz mir bereits zu Beginn des Prozesses ordnungsgemäß zugewiesen wurde.
EDIT : Einige weitere Daten machen mich noch unsicherer, was los ist. Der Test ist der folgende:
.text
.global _start
_start:
subq $0x7fe000,%rsp
movq $1,(%rsp)
mov $0x3c,%eax
mov $0,%edi
syscall
Ich habe hier mit verschiedenen Werten der Konstante gespielt 0x7fe000
, um zu sehen, was passiert, und für diesen Wert ist es nicht deterministisch, ob ich einen Segfault bekomme oder nicht. Laut GDB wird die subq
Anweisung allein die Größe der mmap erweitern, was für mich mysteriös ist (woher weiß Linux, was sich in meinem Register befindet?), Aber dieses Programm stürzt GDB normalerweise beim Beenden aus irgendeinem Grund ab. Es kann nicht ASLR sein, das den Nichtdeterminismus verursacht, weil ich keinen GOT- oder PLT-Abschnitt verwende. Die ausführbare Datei wird jedes Mal an denselben Stellen im virtuellen Speicher geladen. Ist dies also eine Zufälligkeit der PID oder des physischen Gedächtnisses? Alles in allem bin ich sehr verwirrt darüber, wie viel Stapel tatsächlich legal für den wahlfreien Zugriff verfügbar ist und wie viel beim Ändern des RSP oder beim Schreiben in Bereiche "nur außerhalb des Bereichs" angefordert wird.
readelf
undobjdump
waren zu diesem Zweck sehr nützlich. Dies ist nur ein letztes Stück unterbestimmtes Verhalten, das ich hoffentlich regeln kann. (Anders ausgedrückt, mein ultimatives Ziel ist nicht "Ich frage mich, was unter der Haube vor sich geht", sondern "Ich möchte eine genaue Beschreibung, welche Bytes bei der folgenden Eingabe wohin gehen")Antworten:
Ich glaube nicht, dass diese Frage wirklich mit ELF zu tun hat. Soweit ich weiß, definiert ELF eine Möglichkeit, ein Programmabbild in Dateien " flach zu packen " und es dann für die erste Ausführung wieder zusammenzusetzen. Die Definition des Stacks und seiner Implementierung liegt irgendwo zwischen CPU-spezifisch und OS-spezifisch, wenn das OS-Verhalten nicht auf POSIX erhöht wurde. Zweifellos stellt die ELF-Spezifikation einige Anforderungen an die Anforderungen an den Stapel.
Minimale Stapelzuordnung
Aus Ihrer Frage:
Ich kämpfe darum, eine maßgebliche Referenz dafür zu finden. Ich habe jedoch eine ausreichende Anzahl nicht autorisierender Referenzen gefunden, um darauf hinzuweisen, dass dies falsch ist.
Nach dem, was ich gelesen habe, wird die Schutzseite verwendet, um Zugriff außerhalb der maximalen Stapelzuweisung zu erhalten, und nicht für "normales" Stapelwachstum. Die eigentliche Speicherzuordnung (Zuordnung von Seiten zu Speicheradressen) erfolgt nach Bedarf. Dh: Wenn auf nicht zugeordnete Adressen im Speicher zugegriffen wird, die zwischen Stapelbasis und Stapelbasis liegen - maximale Stapelgröße + 1, wird möglicherweise eine Ausnahme von der CPU ausgelöst, aber der Kernel behandelt die Ausnahme durch Zuordnung einer Seite Speicher, ohne einen Segmentierungsfehler zu kaskadieren.
Der Zugriff auf den Stapel innerhalb der maximalen Zuordnung sollte daher keinen Segmentierungsfehler verursachen. Wie du entdeckt hast
Maximale Stapelzuordnung
Die Untersuchung der Dokumentation sollte den Zeilen der Linux-Dokumentation zur Thread-Erstellung und zum Laden von Images folgen ( Fork (2) , Clone (2) , Execve (2) ). Die Dokumentation von execve erwähnt etwas Interessantes:
Dies bestätigt, dass das Limit erfordert, dass die Architektur es unterstützt, und verweist auch, wo es begrenzt ist ( getrlimit (2) ).
Vergrößern des Stapels durch Ändern des RSP-Registers
Ich kenne keinen x86-Assembler. Ich werde Sie jedoch auf die "Stack Fault Exception" aufmerksam machen, die von x86-CPUs ausgelöst werden kann, wenn das SS-Register geändert wird. Bitte korrigieren Sie mich, wenn ich falsch liege , aber ich glaube auf x86-64 SS: SP ist gerade zu "RSP" geworden. Wenn ich das richtig verstehe, kann eine Stapelfehlerausnahme durch dekrementiertes RSP (
subq $0x7fe000,%rsp
) ausgelöst werden .Siehe Seite 222 hier: https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce.html
quelle
SP
Register ist gerecht gewordenRSP
undSS
praktisch verschwunden. Der x86-64 im Long-Modus, der unter 64-Bit-Linux der "normale" Modus ist, verwendet keine Segmentierung mehr. Nur "dieFS
undGS
-Segmente werden in Restform zur Verwendung als zusätzliche Basiszeiger auf Betriebssystemstrukturen beibehalten". Wikipedia . Das Laden derrsp
mit einer nicht-kanonischen Adresse kann eine Ausnahme verursachen, wobei eine nicht-kanonische Adresse eine Adresse bedeutet, die nicht alle Einsen oder Nullen in (normalerweise) den oberen 16 Bits der virtuellen 64-Bit-Adresse enthält.SP
die Ausnahme vollständig entfernt oder kann sie jetzt durch ausgelöst werdenrsp
?R10 <- RSP, RSP <- 0xbababa, RSP <- R10
wenn ich beispielsweise festlegte, wo der schlechte Wert von RSP niemals verwendet wird, bevor er auf einen vernünftigen Wert zurückgesetzt wurde. Dies ist wahrscheinlich kein sehr guter Test, aber es fällt mir schwer zu glauben, dass dies jemals einen eigenen Fehler verursachen würde, ohne dass ein erheblicher Leistungsaufwand in der Hardware entsteht.rsp
Sie müssen den Speicher mit der ungültigen Adresse referenzieren, um die Ausnahme auszulösen. Ich weiß nicht, warum sie die nicht-kanonischen Adressen separat erwähnen, weil sie sowieso illegal sind.Jeder Prozessspeicherbereich (z. B. Code, statische Daten, Heap, Stapel usw.) hat Grenzen, und ein Speicherzugriff außerhalb eines Bereichs oder ein Schreibzugriff auf einen schreibgeschützten Bereich erzeugt eine CPU-Ausnahme. Der Kernel verwaltet diese Speicherbereiche. Ein Zugriff außerhalb eines Bereichs breitet sich in Form eines Segmentierungsfehlersignals bis zum Benutzerraum aus.
Nicht alle Ausnahmen werden durch Zugriff auf Speicher außerhalb der Regionen generiert. Ein regionaler Zugriff kann auch eine Ausnahme erzeugen. Wenn die Seite beispielsweise nicht dem physischen Speicher zugeordnet ist, behandelt der Seitenfehler-Handler dies transparent für den laufenden Prozess.
Dem Prozesshauptstapelbereich ist anfangs nur eine geringe Anzahl von Seitenrahmen zugeordnet, wächst jedoch automatisch, wenn mehr Daten über den Stapelzeiger auf ihn übertragen werden. Der Ausnahmebehandler überprüft, ob sich der Zugriff noch in dem für den Stapel reservierten Bereich befindet, und weist gegebenenfalls einen neuen Seitenrahmen zu. Dies geschieht automatisch aus Sicht des Codes auf Benutzerebene.
Eine Schutzseite wird direkt nach dem Ende des Stapelbereichs platziert, um einen Überlauf des Stapelbereichs zu erkennen. Kürzlich (im Jahr 2017) haben einige Leute erkannt, dass eine einzelne Schutzseite nicht ausreicht, da ein Programm möglicherweise dazu gebracht werden kann, den Stapelzeiger um einen großen Betrag zu dekrementieren, wodurch der Stapelzeiger möglicherweise auf einen anderen Bereich zeigt, der Schreibvorgänge zulässt. Die "Lösung" für dieses Problem bestand darin, die 4-kB-Schutzseite durch eine 1-MB-Schutzregion zu ersetzen. Siehe diesen LWN-Artikel .
Es sollte beachtet werden, dass diese Sicherheitsanfälligkeit nicht ganz trivial auszunutzen ist. Sie erfordert beispielsweise, dass der Benutzer die Speichermenge steuern kann, die ein Programm über einen Aufruf zuweist
alloca
. Robuste Programme sollten den übergebenen Parameter überprüfenalloca
, insbesondere wenn er aus Benutzereingaben abgeleitet wird.quelle