Forum: PC-Programmierung Linux: Groesse und Position von Stack und Heap festlegen (Stack/Heap Overflow provozieren)


von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

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
int main()
8
{
9
    uint8_t a = 0;
10
    uint8_t *b = (uint8_t*) malloc(1);
11
    uint8_t *c = new uint8_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
    delete c;
21
22
    return 0;
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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Danke Jörg. :)

von Jens G. (jensig)


Lesenswert?

>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.

von Noch einer (Gast)


Lesenswert?

Geht nicht?

Hier ein Beispiel, wie sich in allen unixartigen Systemen Stack und Heap 
gegenseitig überschreiben lassen.

https://blog.qualys.com/securitylabs/2017/06/19/the-stack-clash

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Noch einer (Gast)


Lesenswert?

Abwarten... Anscheinend findet alle 5 Jahre jemand einen neuen Trick, 
wie er den Fix für den letzten Exploit umgehen kann.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.