Hallo Leute,
fuer Mikrocontroller gibt es in der Regel Linkerscripte, in denen z.B.
die Startadresse und die Groesse des Stacks, usw. festgelegt werden.
Kann man sowas auch fuer ein eigenes Linuxprogramm machen?
Hintergrund der Frage:
Ich studiere IT-Sicherheit und wir haben gestern einen Versuch zu
Bufferoverflows gemacht. Typisches Problem:
Es werden in einem C-Programm Daten eingelesen, aber es wird nicht
ueberprueft, ob die Daten laenger als das angedachte feste char-Array
sind => Bufferoverflow, Ruecksprungadresse ueberschrieben, alles schick.
Versuch geglueckt, Patient tot.
Das passiert aber alles auf dem Stack.
Nutzt man C++ statt C, und std::string (oder einen anderen dynamischen
Container) anstelle eines festen char-Arrays, so funktioniert das nicht
mehr, da der dynamisch allokierte (oder heisst es 'allozierte'?)
Speicher ja auf dem Heap liegt.
Jetzt muesste es doch aber moeglich sein, den Heap in den Stack wachsen
zu lassen, und so ebenfalls den Stack und Ruecksprungadressen zu
ueberschreiben. An der Stelle kommt aber natuerlich das OS und die
Speicherverwaltung ins Spiel (und ganz viele andere Sachen wie
Stack-Protection vom Compiler, etc.).
Ein kleines C++-Testprogramm:
1
#include<stdlib.h>
2
#include<stdint.h>
3
#include<stdio.h>
4
5
#include<new>
6
7
intmain()
8
{
9
uint8_ta=0;
10
uint8_t*b=(uint8_t*)malloc(1);
11
uint8_t*c=newuint8_t;
12
13
printf("\nAddr. of &a: %p\n",&a);
14
printf("Addr. of &b: %p\n",&b);
15
printf("Addr. of &c: %p\n",&c);
16
printf("\nAddr. b points: %p\n",b);
17
printf("Addr. c points: %p\n",c);
18
19
free(b);
20
deletec;
21
22
return0;
23
}
1
Addr. of &a: 0x7ffea353e617
2
Addr. of &b: 0x7ffea353e618
3
Addr. of &c: 0x7ffea353e620
4
5
Addr. b points: 0x55c7664fae70
6
Addr. c points: 0x55c7664fae90
--------------------------------------
Anmerkung:
Es sind malloc und new drin, da an einigen Stellen auf Stackoverflow
darauf hingewiesen wird, das malloc und new wohl unterschiedliche
Speicherbereiche nutzen: malloc => heap; new => 'free memory'
--------------------------------------
So, wie die Ausgabe zeigt gibt es nun aber eine ziemlich grosse Luecke
zwischen den Variablen auf dem Stack und dem allokierten Speicher.
Jetzt zu meiner eigentlichen Frage:
Kann man dem GCC/Linker/OS nun irgendwie mitteilen, dass das Programm x
Byte an RAM bekommen soll, der Stack auf der einen Seite und der Heap
auf der anderen Seite beginnt, so dass ich den Heap in den Stack wachsen
lassen kann, oder geht das (aus einer viel Zahl von Gruenden) einfach
nicht?
Gruesse
Kaj G. schrieb:> Jetzt muesste es doch aber moeglich sein, den Heap in den Stack wachsen> zu lassen
Bei einem VM-basierten System ist sowas normalerweise nicht möglich.
Stack und Heap haben völlig verschiedene virtuelle Speicherbereiche.
Der Stack wird normalerweise (in festlegbaren Grenzen) vom OS
automatisch (nach unten) vergrößert, wenn beim Beschreiben ein page
fault auftritt, d. h. eine Seite referenziert wird, die bislang keinen
physischen Speicher zugeordnet hatte. Aber das OS wird dir niemals
den gleichen physischen Speicher für den Heap zuweisen, den es schon
im Stack benutzt hat.
Ist eine komplett andere Welt als ein Mikrocontroller oder als die
alten (nicht-VM-)Unixe.
>Bei einem VM-basierten System ist sowas normalerweise nicht möglich.
Warum soll das da nicht möglich sein.
>Stack und Heap haben völlig verschiedene virtuelle Speicherbereiche.
Ist aber im selben Adressraum, den ein Programm "sieht".
>Der Stack wird normalerweise (in festlegbaren Grenzen) vom OS>automatisch (nach unten) vergrößert, wenn beim Beschreiben ein page>fault auftritt, d. h. eine Seite referenziert wird, die bislang keinen>physischen Speicher zugeordnet hatte. Aber das OS wird dir niemals>den gleichen physischen Speicher für den Heap zuweisen, den es schon>im Stack benutzt hat.
Warum soll das OS die Seite nochmal allokieren wollen.
Wenn Heap und Stack sich "irgendwo in der Mitte" des virt. Addressraums
treffen, und die nächste (unterste) Stackpage würde dann auf die oberste
Heappage zeigen, dann würde das OS einfach diese oberste Heappage als
Stack benutzen, also den Heap überschreiben (und auch andersherum). Denn
die Pages sind ja sicherlich nicht irgendwie direkt als Heap oder Stack
gekennzeichnet. Es sei denn, das OS tracked die Assoziation der
Speicherbereiche zu Heap oder Stack irgendwie mit, und kontrolliert das
auch.
Durch ulimits (stack/data) kann man die Ausdehnung beider BEreiche
limitieren, und somit von vornherein eine Kollision vermeiden.
Bei Solaris 32bit war das jedenfalls einfach möglich, wenn die ulimits
dies nicht passend begrenzte (dann gab's Stackkorruptions, bzw. SIGILL
oder SIGSEGV, je nach Umstände)
Bei AIX 32bit war der 32bit Addressraum segmentiert, wobei Heap, Stack,
kernel, Libs ... je ein Segment benutzten. Da konnte es von Grund auf
schon keine Kollision geben.
Bei Linux wird es wohl wie bei Solaris sein. Ob da das OS aktiv die
Assoziation der Pagebereiche zu Stack/Heap unter Kontrolle hat (sofern
die Ulimits dies nicht begrenzten), kann ich jetzt nicht sagen.
Bei 64bit sollte das alles aber nicht wirklich ein Problem sein, denn da
ist der virt. Adressraum ja praktisch "unbegrenzt" ;-). Ehe da was
kollidiert, muß man schon ganz schön den Stack oder Heap füllen können.
Eher ist vorher der phys. Memory+Pagingspace voll (größer kann der virt.
Memory ja ohnehin nicht werden).
Das hat nix mit dem phys. Speicher zu tun. Wenn eine phys. Page virtuell
als Heap benutzt wird, dann könnte das OS diese Page auch zufällig als
Stack missbrauchen (ohne die Page nochmal allokieren zu wollen, was wohl
ohnehin nicht gehen sollte), bzw. damit überschreiben, wenn das OS da
keine eigenen Schutzmechanismen implementiert hat.
Jens G. schrieb:> Bei Solaris 32bit war das jedenfalls einfach möglich, wenn die ulimits> dies nicht passend begrenzte
Ist natürlich richtig, wenn man bei 32 Bit Adressraum keine
sinnvollen Limits hat, dann können sie sich schon gegenseitig
erreichen.
Zumindest bekomme ich die Qualys-Exploits bei mir nicht zum Clash,
ist aber auch ein 64-Bit-System.
Bei 64 Bit Adressraum dürften die Segmente zumindest für die nächsten
10 Jahre weit genug auseinander sein, als dass man keine Kollision
erzeugen kann. ;-) (Adressraum: 18446744 Terabyte)