Autor Thema: Allgemeiner Ressourcen-Allocator  (Gelesen 63145 mal)

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #100 am: 19. November 2010, 17:31 »
Hallo,

Objekte auf den Stack zu legen anstatt dafür den Heap zu benutzen ist eigentlich eine Entscheidung das Programmierers. Sicher kann man versuchen das der Compiler da zur compilezeit etwas optimiert oder man könnte Code einfügen lassen der zur Laufzeit ermittelt wie viel Speicher da eigentlich geholt werden soll und wenn das nicht zu viel ist (<= z.B. 64kBytes) dann wird einfach Stack benutzt. Ich denke schon das man da einiges vom Compiler optimieren lassen könnte aber ich glaube nicht dass das heutige Compiler bereits tun.
Würde mich nicht wundern, wenn sie das tun - das bringt eine Menge Performance, wenn eben dieser Kleinkram nicht über die Heap-Verwaltung geht und wenn der Stack ausreichend groß ist, kann man das auch mit größeren kurzlebigen Objekten tun.

Was die "Magic Cookies" angeht so ist das einfach ein Problem der Stack-Implementierung, das der Stack nach unten wächst aber alle anderen Datenstrukturen nach oben ist einfach eine Fehlkonstruktion (zumindest aus heutiger Sicht wo der Stack einfach ein beliebiger dynamischer Bereich des Speichers ist und nicht das obere Ende eines fixen Real-Mode-Segments). Deswegen wächst mein Stack ja auch nach oben und schon ist dieses Problem nur noch halb so schlimm, trotzdem werde ich noch etwas Denkarbeit in die Realisierung von VLAs usw. investieren müssen.
Naja, du kannst als Userspace-Programm deinen Stack auch per Hand adressieren und wenn du dann in der Lage bist, die Rücksprungadressen zu verändern (unter DOS nur ein "pop" zuviel), dann fliegt dir dein Programm um die Ohren. Viel schlimmer ist es, dass du Argumentübergabe auch über den Stack machst und die Syscalls daraus unter Umständen irgendetwas berechnen. Das ist ein wunderbarer Angriffsvektor, wenn die Syscalls nicht sicher implementiert sind und das ist genau unabhängig von der Richtung, in die der Stack wächst... man denke auch an Return Based Programming.

Echten Zufall müsste eigentlich auch die CPU selber zur Verfügung stellen, etwa so wie VIA das macht, und schon wäre noch ein Angriffspunkt weg. Wenn der Referenzwert für die Cookies irgendwo im Speicher liegt dann ist er dort auch lesbar und vor allem auch manipulierbar. Welches OS markiert schon die Pages in denen die .const-Section liegt wirklich als Read-Only?
Sollte jedes moderne OS eigentlich tun, bei Linux bin ich mir relativ sicher und bei Windows auch (weil dort viel an Sicherheit gedacht wurde). Wenn man das NX-Bit vollständig unterstützt, dann ist das RO-Bit doch nur noch eine Nebensache, weil die Infrastruktur (Page-Permissions) schon da ist... Wahrer Zufall wird auch von den CPUs nur begrenzt (OpenSSL unterstützt den RNG von VIA-CPUs übrigens nicht, weil er bestimmte Anforderungen nicht erfüllt), außerdem kann das nicht jede CPU. Wenn das vorhanden ist, ist das allerdings eine sehr gute (weitere) Quelle für Zufallszahlen.

Irgendwo muss es doch aber gespeichert sein, damit zu einen Wert zum überprüfen hast und diese Adresse dieses Wertes muss auch bekannt sein.
Das ist eines der Probleme dieser Stack-Protectoren. Die schützen zwar gegen "aus versehen" fehlerhaften Code oder simple Buffer-Overflows aber nicht gegen einen gezielten und mit Köpfchen vorbereiteten Angriff.
Wenn dieses Magic Cookie für jeden Stack extra abgespeichert wird, und zwar in nicht-Userspace-lesbaren Kernel-Strukturen (!), dann kann die Applikation das nicht wissen und damit auch nicht dagegen vorgehen. Aber sicher ist nur, dass es keine absolute Sicherheit gibt - aber selbst eine simple Buffer-Overflow-Erkennung ist schon etwas richtig feines.

Als Mindestausstattung an qualitativ hochwertigen Sportgeräten sollte eine Freundin vorhanden sein, da gibt es dann schon viele flexible Möglichkeiten zur körperlichen Bewegung, auch an frischer Luft. Ich meine so Dinge wie eine Fahrradtour usw. ;)
In Ermangelung einer solchen gehe ich jeden Morgen/Abend insgesamt ein paar km zu Fuß zur derzeitigen Arbeitsstelle. Das muss an Bewegung ausreichen.

Zitat von: erik
Man muss ein Array nicht zwangsläufig von Index 0 bis Index X linear durchlaufen, man kann da auch Lücken lassen die dann eben übersprungen werden. Ist das selbe Problem wie mit den Guard-Pages, wenn Du weißt wo die sind und wie groß die sind dann ist es kein Problem da drüber zu kommen.
Wir reden von Buffer-Overflows und das Problem ist ja das du etwas (linear) in den Buffer schreibst, aber davon ausgehst das sich jeder an gewisse Regeln hält was die Größe betrifft und wenn er das nicht macht, wird mehr in den Buffer geschrieben, als der Buffer groß ist. Das geschiet doch aber alles, wie gesagt, linear oder?
Ich kann mir ein Array int ar[1] deklarieren und dann mit Indizierung ( ar[1024*1024*1024] ) durchaus gezielt auf Speicher vor/hinter dem Array zugreifen. Wenn ich weiß, wo mein Array liegt (kann ich über den Pointer rauskriegen, in einem linaren Adressraum) und ich weiß, wie groß meine Daten sind (ich kenne mein Programm), dann weiß ich im Normalfall auch, wo die Rücksprungadressen sind und kann diese gezielt überschreiben. Da der Stack mein eigener ist, kannst du dich davor auch nicht schützen: Stack muss les- & schreibbar sein. Stack ausführen ist auch so ne nette Technik, die für Exploits gerne benutzt wird - denn die Übergabeparameter in die Syscalls liegen auch auf dem Stack und so bekommt man Code in den Kernel, ohne dass dieser es merkt.

Magic Cookies sind mehr als nur eine simple Buffer-Overflow-Erkennung, sie können (wenn die zugrundeliegenden Informationen Position/Wert des Cookies sicher versteckt sind) auch Stack-Angriffe vermeiden. Kostet natürlich Performance.

Gruß,
Svenska

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #101 am: 19. November 2010, 17:40 »
Zitat von: svenska
Würde mich nicht wundern, wenn sie das tun - das bringt eine Menge Performance, wenn eben dieser Kleinkram nicht über die Heap-Verwaltung geht und wenn der Stack ausreichend groß ist, kann man das auch mit größeren kurzlebigen Objekten tun.
Erwischt ;)

Woher weiß malloc() wie lange du den angeforderten Speicher brauchst? Was ich nämlich immernoch so sehe, ist dass du keine langlebigen Objekte auf den Stack packen kannst, weil dieser beim Funktions-Return wieder abgebaut und dann von einer neuen Funktion überschrieben wird.

Zitat von: svenska
Wenn dieses Magic Cookie für jeden Stack extra abgespeichert wird, und zwar in nicht-Userspace-lesbaren Kernel-Strukturen (!), dann kann die Applikation das nicht wissen und damit auch nicht dagegen vorgehen.
Sorry, aber das ist mit Kanonen auf Spatzen schießen. Denn das würde jedes Mal heißen, das ich nen Syscall machen müsste um diese Überprüfung durchzuführen und das ist es nicht Wert bzw. meiner Meinung nach vollkommen übertrieben (geht mit viel Fantasie auch in die Richtung Sozialismus ;) ).

Zitat von: svenska
dann weiß ich im Normalfall auch, wo die Rücksprungadressen sind und kann diese gezielt überschreiben.
Wir sind immernoch bei nem Buffer-Overflow und das jemand von Außen versucht durch einen solchen seinen eigenen Code auszuführen.
Wenn der Stack nach Oben anstatt nach Unten zu wachsen, dann ist der Buffer-Overflow kein Problem mehr, weil er nicht mehr die Rücksprungadresse oder irgendetwas vor dem Buffer überschreibt.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #102 am: 19. November 2010, 20:58 »
Hallo,


.... Das geschiet doch aber alles, wie gesagt, linear oder?
Meistens sicher schon. Ein Overflow der z.B. in einem sprintf (ohne 'n') ensteht dürfte wohl keine Lücken haben. Aber kannst Du das für alle Situationen garantieren? Wenn Du von linear ausgehst machst Du wieder so eine Annahme die zwar oft aber eben nicht immer zutrifft, böse Hacker werden sich da sicher nicht dran halten.

Zitat von: erik
Das der Stack auf x86 nach unten wächst ist unumstößlich fest eingebaut!
Ich behaupte das dem nicht so ist.
Dann beweise mal auch!

in welche Richtung das geschiet, wird meines Wissens nach, durch den Segment Diskriptor des Stack Segments festgelegt und da kann man auch sagen das der Stack nach oben wachsen kann.
Das Bit musst Du mir erst mal zeigen, das kenne ich noch nicht.

@OffTopic

aber leider sind immer irgendwelche Faktoren vorhanden die etwas wie eine Fahrradtour schwer bis gar nicht möglich machen vorhanden.
Was kann es denn da für Ausreden geben, deswegen empfehle ich ja extra ne Freundin weil die viele (männliche) Ausreden schon ganz gut wegdiskutieren können und eben auch für eine erhöhte Motivation sorgen können. ;)

(macht aber mehr Spaß)
Das ist hier völlig irrelevant, entscheidend ist das Dein Körper in guter Verfassung ist. Du kennst doch sicher "in einem gesunden Körper wohnt ein gesunder Geist"?


Würde mich nicht wundern, wenn sie das tun - das bringt eine Menge Performance, wenn eben dieser Kleinkram nicht über die Heap-Verwaltung geht ...
Das glaube ich nicht, denn da gibt es ne Menge Einschränkungen zu berücksichtigen. Wenn für das Objekt ein Pointer gebildet wird und dieser Pointer in eine statische Variable gelegt wird dann ist das schon wieder alles erledigt, auch muss geprüft werden ob das Objekt wirklich in jedem Fall vor dem Ende der Funktion wieder gelöscht wird (es also keine Wege gibt aus der Funktion raus zu kommen ohne das Objekt zu löschen, Exceptions wären da z.B. eine Möglichkeit). Aber prinzipiell ist das eine gute Möglichkeit etwas Performance zu gewinnen, es wäre auf jeden Fall sehr schön wenn die Compiler das können.

Naja, du kannst als Userspace-Programm deinen Stack auch per Hand adressieren und wenn du dann in der Lage bist, die Rücksprungadressen zu verändern (unter DOS nur ein "pop" zuviel), dann fliegt dir dein Programm um die Ohren.
Klar muss der Compiler aufpassen das er keinen Mist baut aber das ist denke ich heute kein ernstes Problem mehr. Der böse Hacker kann das natürlich trotzdem machen wenn er denn einen Weg findet eigenen Code einzuschleusen.

Viel schlimmer ist es, dass du Argumentübergabe auch über den Stack machst und die Syscalls daraus unter Umständen irgendetwas berechnen. Das ist ein wunderbarer Angriffsvektor, wenn die Syscalls nicht sicher implementiert sind und das ist genau unabhängig von der Richtung, in die der Stack wächst
Auf meiner Plattform wird es keine Syscalls mit Parametern/Rückgabewerten auf dem Stack geben, ich hab ja auch genügend Register, außerdem wird der Stack beim Modus-Wechsel automatisch mit gewechselt und sowas wie das Kopieren von den ersten x Stack-Bytes beim Ring-Wechsel auf x86 gibt es bei mir auch nicht. (hat das auf x86 überhaupt schon mal jemand benutzt?) Ein ordentlich programmierter Syscall prüft alle Parameter auf Plausibilität bevor er sie benutzt.

man denke auch an Return Based Programming.
Da lese ich gleich mal genauer nach. Ich dachte immer das vor allem x86, wegen seinen Befehlen mit flexibler Länge und dem Umstand das man jede beliebige Byte-Kette als Befehle interpretieren kann, da ein Problem ist aber es gibt da wohl einen Angriff auf SPARC und das ist dann doch schon etwas ernster. Trotzdem muss der Angreifer mindestens ein mal den Stack-Pointer nach seinen Wünschen manipulieren können bevor so ein Angriff funktioniert und da bieten einfach Stacks die nach oben wachsen eine deutlich kleinere Angriffsfläche.

(OpenSSL unterstützt den RNG von VIA-CPUs übrigens nicht, weil er bestimmte Anforderungen nicht erfüllt)
Das der RNG in den VIA-CPUs von nur bescheidener Qualität ist hab ich auch schon mal gelesen, aber das würde ich eher auf schlechte Implementierung bei VIA schieben, das kann man ganz sicher besser machen. Ein erstes Indiz für die schlechte RNG-Qualität bei VIA ist ja schon das dieser RNG konfigurierbar ist. Was soll man bei einem richtigen TRNG konfigurieren können? Aber prinzipiell gehört sowas direkt in den Befehlssatz einer jeden anständigen CPU.

Wenn dieses Magic Cookie für jeden Stack extra abgespeichert wird, und zwar in nicht-Userspace-lesbaren Kernel-Strukturen (!)
Da schließe ich mich FlashBurns Meinung an und sage dass das deutlich viel zu viel Aufwand ist. Vor allem in Relation dazu das damit trotzdem keine 100%-Sicherheit (nicht mal 99%) gegen die Manipulation der Rücksprungadressen gewährleistet werden kann. Was hinderten den eingeschleusten Code daran diesen Syscall zu benutzen um dieses Geheimnis auszuspähen?

aber selbst eine simple Buffer-Overflow-Erkennung ist schon etwas richtig feines.
Wenn man sprintf und all die anderen Kollegen ohne 'n' aus dem C-Standard verbannen tät und jedem Programmierer erklärt warum man sowas nicht macht wären die wichtigsten Einfallstore für "einfache" Buffer-Overflows schon mal gebannt. Was dann noch übrig bleibt sind die komplexeren Angriffsszenarien und gegen die hilft eine simple Stack-Protection auch nicht wirklich. Ist eben eine Frage der Verhältnismäßigkeit.

In Ermangelung einer solchen gehe ich jeden Morgen/Abend insgesamt ein paar km zu Fuß zur derzeitigen Arbeitsstelle. Das muss an Bewegung ausreichen.
Das ist sehr löblich fordert aber leider nicht den gesamten Körper, ich finde da muss noch mehr getan werden. ;)

Ich kann mir ein Array int ar[1] deklarieren und dann mit Indizierung ( ar[1024*1024*1024] ) durchaus gezielt auf Speicher vor/hinter dem Array zugreifen.
Man beachte das "vor", ist es nicht eigentlich idiotisch das man bei der Speicheradressierung bewusst mit Überläufen arbeiten kann? Ich finde es deutlich besser wenn man im OpCode des Befehls zwischen + und - wählen kann und die CPU alle Überläufe mit ner Exception quittiert, da sind dann gleich wieder eine ganze Reihe von potentiellen Einfallstoren zugegangen.

denn die Übergabeparameter in die Syscalls liegen auch auf dem Stack und so bekommt man Code in den Kernel, ohne dass dieser es merkt.
Sind heutige Kernels für sowas wirklich anfällig? Das hätte ich ehrlich gesagt nicht vermutet, da müsste man sich doch als Kernel-Programmierer IMHO schon Mühe geben damit sowas auch wirklich klappt.

Magic Cookies sind mehr als nur eine simple Buffer-Overflow-Erkennung, sie können (wenn die zugrundeliegenden Informationen Position/Wert des Cookies sicher versteckt sind) auch Stack-Angriffe vermeiden. Kostet natürlich Performance.
Aber gerade das Versteck ist das Problem, wenn der vom Compiler generierte Code da ran kommt dann kann das auch der eingeschleuste Code.


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #103 am: 19. November 2010, 21:16 »
Zitat von: erik
Aber kannst Du das für alle Situationen garantieren? Wenn Du von linear ausgehst machst Du wieder so eine Annahme die zwar oft aber eben nicht immer zutrifft, böse Hacker werden sich da sicher nicht dran halten.
Ok, wenn dem so ist, dann nutzt einem aber auch so ein Magic Cookie nichts. Denn der Hacker kann das ja einfach "ignorieren". Wo ich dann wieder da wäre, das die ganzen Schutzmaßnahmen eh nichts bringen, da jemand der wirklich will auch kann ;)

Zitat von: erik
Das Bit musst Du mir erst mal zeigen, das kenne ich noch nicht.
In meiner Revision der Intel Manuals ist das "3.4.5 Segment Discriptors". Da kann man sagen, ob ein Datensegment nach oben oder nach unten wächst.

Zitat von: erik
Was kann es denn da für Ausreden geben, deswegen empfehle ich ja extra ne Freundin weil die viele (männliche) Ausreden schon ganz gut wegdiskutieren können und eben auch für eine erhöhte Motivation sorgen können. Wink
Das liegt einmal am Wetter (hier regnet es wirklich oft, das sind wir einfach nicht gewöhnt) und sich mit mir zu bewegen ist nicht so einfach ;)

Zitat von: erik
Das ist hier völlig irrelevant, entscheidend ist das Dein Körper in guter Verfassung ist. Du kennst doch sicher "in einem gesunden Körper wohnt ein gesunder Geist"?
Ich hätte wahrscheinlich dazu sagen sollen, dass ich meiner Meinung nach nicht Fit bin, aber wenn ich mir so den durchschnitts Deutschen angucke, sieht es gar nicht so schlecht aus ;)

Zitat von: erik
auch muss geprüft werden ob das Objekt wirklich in jedem Fall vor dem Ende der Funktion wieder gelöscht wird
Aber wie willst du das als Compiler 100%ig wissen und zur Laufzeit möchte ich das nicht rausbekommen müssen (das wäre ja schon fast nen Garbage Collector)?

Zitat von: erik
Sind heutige Kernels für sowas wirklich anfällig? Das hätte ich ehrlich gesagt nicht vermutet, da müsste man sich doch als Kernel-Programmierer IMHO schon Mühe geben damit sowas auch wirklich klappt.
Mich würde jetzt eher mal interessieren, wie man es dann schafft das der Code auch ausgeführt wird?

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #104 am: 19. November 2010, 21:36 »
Hallo,


Ok, wenn dem so ist, dann nutzt einem aber auch so ein Magic Cookie nichts. Denn der Hacker kann das ja einfach "ignorieren". Wo ich dann wieder da wäre, das die ganzen Schutzmaßnahmen eh nichts bringen, da jemand der wirklich will auch kann ;)
Diese Magic Cookies sind ganz gut wenn man Buffer-Overflows bei sprintf usw. vermeiden will aber gegen echte Angriffe eines Profis sind die IMHO nicht viel wert außer das sie eben die Latte ein klein wenig höher legen.

In meiner Revision der Intel Manuals ist das "3.4.5 Segment Discriptors". Da kann man sagen, ob ein Datensegment nach oben oder nach unten wächst.
Steht da auch das man damit die Wirkungsweise der Stack-Befehle beeinflussen kann?

hier regnet es wirklich oft, das sind wir einfach nicht gewöhnt
Muss ich jetzt noch den Spruch mit der unpassenden Kleidung ablassen? Alles nur ausreden!

aber wenn ich mir so den durchschnitts Deutschen angucke, sieht es gar nicht so schlecht aus ;)
Du sollst Dich doch nicht an irgendwelchen Leuten orientieren, ich dachte Du bist Profi also suche Dir passende Vergleiche. ;)

Zitat von: erik
auch muss geprüft werden ob das Objekt wirklich in jedem Fall vor dem Ende der Funktion wieder gelöscht wird
Aber wie willst du das als Compiler 100%ig wissen
Einfach eine Control-Flow-Graph-Analyse (oder wie das noch mal genau heißt), die Kunst ist einfach nur das man keinen möglichen Weg vergisst/übersieht. Sowas gibt es auch schon, in Java kann man z.B. eine Variable als const (heißt dort final) deklarieren ohne sie gleich zu initialisieren und der Compiler prüft ob die in jedem Fall vor dem ersten Lesezugriff genau ein mal beschrieben wird.

Mich würde jetzt eher mal interessieren, wie man es dann schafft das der Code auch ausgeführt wird?
Eben das meinte ich mit "schon Mühe geben".


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #105 am: 19. November 2010, 21:55 »
Zitat von: erik
Steht da auch das man damit die Wirkungsweise der Stack-Befehle beeinflussen kann?
Das ist das interessante, dort steht halt das man damit die Richtung festlegen kann in welche ein Datensegment wächst.
Liest man sich aber die Erklärung zu POP durch dann liest man dort, dass es egal ist (also es steht nicht so da, sondern das bei einem POP immer inkrementiert wird).
Was soll man daraus also jetzt schließen? Wenn ich morgen mal die Zeit finde, kann ich es ja mal auf realer Hardware testen (oder ich gucke nachher noch mal schnell in Bochs nach, dem Source Code).

Zitat von: erik
Einfach eine Control-Flow-Graph-Analyse (oder wie das noch mal genau heißt), die Kunst ist einfach nur das man keinen möglichen Weg vergisst/übersieht. Sowas gibt es auch schon, in Java kann man z.B. eine Variable als const (heißt dort final) deklarieren ohne sie gleich zu initialisieren und der Compiler prüft ob die in jedem Fall vor dem ersten Lesezugriff genau ein mal beschrieben wird.
Bei Java möge das ja gehen (zumal du von einer lokalen Variable sprichst), aber wie willst du sicherstellen, das wenn du einen Pointer auf diesen Speicher an eine Funktion übergibst, die in einem anderen Source-File liegt (und der Compiler keinen Zugriff drauf hat) oder meinetwegen in einer Library, das diese nicht den Pointer irgendwo speichert um später damit zu arbeiten?
Oder anders, man muss den Speicher ja nicht in der erstellenden Funktion wieder freigeben, sondern könnte das auch in einer anderen machen (selbes Problem).

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #106 am: 19. November 2010, 23:16 »
Hallo,

Woher weiß malloc() wie lange du den angeforderten Speicher brauchst? Was ich nämlich immernoch so sehe, ist dass du keine langlebigen Objekte auf den Stack packen kannst, weil dieser beim Funktions-Return wieder abgebaut und dann von einer neuen Funktion überschrieben wird.
Die kannst du nicht auf den Stack legen und das sicherzustellen ist Aufgabe des Compilers. Außerdem geht es nicht mit großen Strukturen, da der Stackspace begrenzt ist. Das sind Randbedingungen, die im Voraus bekannt sind. Allerdings sind kleine Strukturen meist nur kurzlebig oder lokal, wobei lokal immer zu einer Unterfunktion relativ ist. Im Zweifelsfall ist es relativ zu main(), wie z.B. ein pollfds-Array für poll(). Da ein realloc() nicht garantiert, dass der Zeiger gültig bleibt, kann man so dynamische Arrays auf dem Stack bauen, die für seltsame Anwendungsfälle in den Heap verschoben werden.

Das sehe ich nicht als gefangen an. :-P

Zitat von: svenska
Wenn dieses Magic Cookie für jeden Stack extra abgespeichert wird, und zwar in nicht-Userspace-lesbaren Kernel-Strukturen (!), dann kann die Applikation das nicht wissen und damit auch nicht dagegen vorgehen.
Sorry, aber das ist mit Kanonen auf Spatzen schießen. Denn das würde jedes Mal heißen, das ich nen Syscall machen müsste um diese Überprüfung durchzuführen und das ist es nicht Wert bzw. meiner Meinung nach vollkommen übertrieben (geht mit viel Fantasie auch in die Richtung Sozialismus ;) ).
Man muss es ja nicht in der Form implementieren. Ein simpler Mechanismus (statisches Magic Cookie direkt zwischen Rücksprung und Daten) erkennt Stack-Overflows und ist zumindest ein sinnvoller Debug-Helfer.

Zitat von: svenska
dann weiß ich im Normalfall auch, wo die Rücksprungadressen sind und kann diese gezielt überschreiben.
Wir sind immernoch bei nem Buffer-Overflow und das jemand von Außen versucht durch einen solchen seinen eigenen Code auszuführen.
Wenn der Stack nach Oben anstatt nach Unten zu wachsen, dann ist der Buffer-Overflow kein Problem mehr, weil er nicht mehr die Rücksprungadresse oder irgendetwas vor dem Buffer überschreibt.
Korrekt. Wenn es um Code ausführen geht, dann kannst du den Stack trotzdem adressieren, egal ob er nach oben oder nach unten wächst. Ein ungewollter Bufferoverflow wird nicht gezielt Code ausführen (er wird in der Regel das Programm zum Absturz bringen), daher sind das zwei Paar Schuhe.

Aber prinzipiell ist das eine gute Möglichkeit etwas Performance zu gewinnen, es wäre auf jeden Fall sehr schön wenn die Compiler das können.
Ich behaupte grundlos, dass Compiler genau diese Überprüfungen ohnehin im Rahmen ihrer Optimierungen schon tun und daher kostenlos (höchstens dauert das Kompilieren länger) die richtige Entscheidung treffen können.

Ein ordentlich programmierter Syscall prüft alle Parameter auf Plausibilität bevor er sie benutzt.
Ich behaupte, dass das nicht überall möglich (oder performant) ist. Eine grundlegende Optimierung ist doch, überflüssige Prüfungen einzusparen... darum sind Datenbanken auf virtuellen Maschinen auch schneller auf realen - die Wartezeit für "wurde auf Platte geschrieben?" ist in der VM null, in der realen Maschinen von der Festplatte abhängig. ;-)

man denke auch an Return Based Programming.
Da lese ich gleich mal genauer nach. Ich dachte immer das vor allem x86, wegen seinen Befehlen mit flexibler Länge und dem Umstand das man jede beliebige Byte-Kette als Befehle interpretieren kann, da ein Problem ist aber es gibt da wohl einen Angriff auf SPARC und das ist dann doch schon etwas ernster. Trotzdem muss der Angreifer mindestens ein mal den Stack-Pointer nach seinen Wünschen manipulieren können bevor so ein Angriff funktioniert und da bieten einfach Stacks die nach oben wachsen eine deutlich kleinere Angriffsfläche.
Damit wurde eine Wahlmaschine manipuliert. Die Technik dahinter hätte ich schon als sicher betrachtet, das war ein Z80 mit einem ROM als Codebereich und einem RAM als Datenbereich. Wenn eine Execute-Adresse auf dem Bus in das RAM zeigte, schlug ein Komparator an und hat RESET aktiviert - also kein Code im RAM möglich. Darum hat man sich auf Return Based Programming zurückgezogen und das kann man, den Original-Code vorausgesetzt, auf jeder Architektur machen.

(OpenSSL unterstützt den RNG von VIA-CPUs übrigens nicht, weil er bestimmte Anforderungen nicht erfüllt)
Das der RNG in den VIA-CPUs von nur bescheidener Qualität ist hab ich auch schon mal gelesen, aber das würde ich eher auf schlechte Implementierung bei VIA schieben, das kann man ganz sicher besser machen. Ein erstes Indiz für die schlechte RNG-Qualität bei VIA ist ja schon das dieser RNG konfigurierbar ist. Was soll man bei einem richtigen TRNG konfigurieren können? Aber prinzipiell gehört sowas direkt in den Befehlssatz einer jeden anständigen CPU.
Davon kannst du aber nicht ausgehen. Sicherheit ist noch nicht so lange wichtig, Microsoft hat damit in Gründlichkeit erst mit Vista begonnen. Andererseits bin ich ja der Meinung, dass du, sofern du einigermaßen zufällige Ereignisse verschiedener Quellen benutzt, gar keine Garantie über den absoluten Zufall mehr brauchst. Eine Anwendung kann ja nicht jede Hardware gleichermaßen unter Kontrolle haben, um sowas vorherzusagen.

Wenn dieses Magic Cookie für jeden Stack extra abgespeichert wird, und zwar in nicht-Userspace-lesbaren Kernel-Strukturen (!)
Da schließe ich mich FlashBurns Meinung an und sage dass das deutlich viel zu viel Aufwand ist. Vor allem in Relation dazu das damit trotzdem keine 100%-Sicherheit (nicht mal 99%) gegen die Manipulation der Rücksprungadressen gewährleistet werden kann. Was hinderten den eingeschleusten Code daran diesen Syscall zu benutzen um dieses Geheimnis auszuspähen?
Der geht an mir vorbei. Jeder Syscall, der vom Programm aufgerufen wird, prüft im Vorbeigehen die Konsistenz des Stacks. Da jedes sinnvolle Programm irgendwann einmal Syscalls aufrufen muss, ist das für mich ein Weg. Mal abgesehen davon, welchen Syscall meinst du denn?

aber selbst eine simple Buffer-Overflow-Erkennung ist schon etwas richtig feines.
Wenn man sprintf und all die anderen Kollegen ohne 'n' aus dem C-Standard verbannen tät und jedem Programmierer erklärt warum man sowas nicht macht wären die wichtigsten Einfallstore für "einfache" Buffer-Overflows schon mal gebannt. Was dann noch übrig bleibt sind die komplexeren Angriffsszenarien und gegen die hilft eine simple Stack-Protection auch nicht wirklich. Ist eben eine Frage der Verhältnismäßigkeit.
Soweit ich informiert bin, wirft das MSVC einen Fehler aus, wenn man diese Funktionen benutzt und dem GCC kann man das auch beibringen. Microsoft verbietet inhärent unsichere Funktionen (bzw. sie werden als obsolet markiert und kompilieren später nicht) und in den Developer Guidelines von Apple wird auch explizit auf Buffer-Overflows eingegangen. Das Umdenken ist nicht mehr das Problem; mal abgesehen davon können Buffer-Overflows auch außerhalb des Stacks geschehen, dann bringt das sowieso nichts weil andere Baustelle.

Man beachte das "vor", ist es nicht eigentlich idiotisch das man bei der Speicheradressierung bewusst mit Überläufen arbeiten kann? Ich finde es deutlich besser wenn man im OpCode des Befehls zwischen + und - wählen kann und die CPU alle Überläufe mit ner Exception quittiert, da sind dann gleich wieder eine ganze Reihe von potentiellen Einfallstoren zugegangen.
Guck dir im x86 mal den Opcode-Space an. Der ist voll. Da nochmal jeden potentiellen Befehl in zwei Varianten haben wird eher nichts. Zumal ich die Möglichkeit an sich nicht so schlecht finde - wobei ein Vergleich mit dem i860 doch "etwas" hinkt.

Wenn du schnelle, stromsparende CPUs möchtest, musst du entweder zwischen Performance und Anzahl der Transistoren einen Kompromiss finden. Die Möglichkeiten gehen von einfacheren Architekturen (Atom/VIA) und Emulatoren (Transmeta) zu neuen Architekturen (ARM, MIPS). Für Dinge, die man im Compiler beheben könnte noch Unmengen an Opcodes zu reservieren, ist beiden Varianten nicht förderlich... Das ist auch eine Frage der Verhältnismäßigkeit (und Kompatiblität).

Sind heutige Kernels für sowas wirklich anfällig? Das hätte ich ehrlich gesagt nicht vermutet, da müsste man sich doch als Kernel-Programmierer IMHO schon Mühe geben damit sowas auch wirklich klappt.
Naja, wird dein Kernel den Sicherheitsanforderungen von "heutigen Kernels" genügen? Ich traue mir sichere Programmierung jedenfalls nicht zu, geringfügig abgesicherte Designs schon.

Magic Cookies sind mehr als nur eine simple Buffer-Overflow-Erkennung, sie können (wenn die zugrundeliegenden Informationen Position/Wert des Cookies sicher versteckt sind) auch Stack-Angriffe vermeiden. Kostet natürlich Performance.
Aber gerade das Versteck ist das Problem, wenn der vom Compiler generierte Code da ran kommt dann kann das auch der eingeschleuste Code.
Eben das muss man verhindern... der Compiler generierte Userspace-Code darf keine Möglichkeit haben, der vom Compiler generierte und davon unabhängige Kernel-Code muss diese Möglichkeit haben und nutzen. Ist für mich eher ne Designsache, wobei ich es wahrscheinlich auch nicht konkret implementieren kann. ;-)

Ok, wenn dem so ist, dann nutzt einem aber auch so ein Magic Cookie nichts. Denn der Hacker kann das ja einfach "ignorieren". Wo ich dann wieder da wäre, das die ganzen Schutzmaßnahmen eh nichts bringen, da jemand der wirklich will auch kann ;)
Mein Vorschlag steht und fällt mit dem Versteck. Du kannst nichts ignorieren, was du nicht kennst. Und dass jemand, der will, auch kann, sieht man eindeutig wirklich gut an der Differential Power Analysis (ich errechne aus dem Stromverbrauch eines Mikrocontrollers den privaten Schlüssel, wenn ich den Algorithmus kenne). Sowas ist krank.

Zitat von: erik
auch muss geprüft werden ob das Objekt wirklich in jedem Fall vor dem Ende der Funktion wieder gelöscht wird
Aber wie willst du das als Compiler 100%ig wissen und zur Laufzeit möchte ich das nicht rausbekommen müssen (das wäre ja schon fast nen Garbage Collector)?
Ich behaupte, das fällt bei den ganzen Optimierungsschritten für den Compiler hinten mit raus.

Mich würde jetzt eher mal interessieren, wie man es dann schafft das der Code auch ausgeführt wird?
Standardprozedere: Ich überschreibe irgendeinen Kernelspeicher und weiß, zu welcher Funktion der gehört und an welcher Stelle in der Funktion ich deren Code gerade überschreibe. Danach brauche ich die Funktion nur noch so aufrufen, dass sie an diese Stelle springt. Beide Phasen des Exploits sind vollkommen unabhängig voneinander und können auch verschiedene Anwendungen betreffen (PHP überschreibt Kernelspeicher, Apache löst überschriebene Funktion aus). Klingt einfach, ist inzwischen aber sehr schwierig (u.a. durch so nette Dinger wie Heap-Randomization oder Magic Cookies im Stack).

Diese Magic Cookies sind ganz gut wenn man Buffer-Overflows bei sprintf usw. vermeiden will aber gegen echte Angriffe eines Profis sind die IMHO nicht viel wert außer das sie eben die Latte ein klein wenig höher legen.
Hundertprozentige Sicherheit gibt es nicht. Ziel ist es immer, die Latte so hoch zu legen, dass der Angriff unattraktiv oder unzuverlässig wird, dann werden nämlich andere Angriffsvektoren gesucht. Heutzutage greift man nur noch selten einen PC an, sondern eher den Anwender; OS/2 oder Linux sind nahezu virenfrei, weil sie auf so gut wie keinem PC installiert sind. Und selbst Windows wird nicht mehr angegriffen, sondern Dinge wie Acrobat Reader oder Adobe Flash oder Internet Explorer oder Firefox.

Hängst du die Messlatte hinreichend hoch, bleibst du von Breitbandangriffen verschont. Gegen gezielte Angriffe wie Styxnet kannst du dich nicht schützen.

Oder anders, man muss den Speicher ja nicht in der erstellenden Funktion wieder freigeben, sondern könnte das auch in einer anderen machen (selbes Problem).
Das Problem fällt auf die Frage zurück, ob derselbe Stack für sowohl malloc()ende und free()ende Funktion gültig ist. Handelt es sich um eine direkt oder indirekt von der Überfunktion (malloc) aufgerufene Funktion (free), so gibt es kein Problem.

Handelt es sich um etwas Globales, musst du zwangsweise den Heap benutzen.

So, genug geschrieben, ich geh schlafen.

Gruß,
Svenska

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #107 am: 19. November 2010, 23:29 »
Hallo,


Das ist das interessante, dort steht halt das man damit die Richtung festlegen kann in welche ein Datensegment wächst.
Das ist eine eher verwaltungstechnische Information, damit kann man nicht die Richtung des Stacks beeinflussen. Als ich dieses Flag vor vielen Jahren entdeckt habe dachte ich auch erst das man damit den Stack beeinflussen könnte aber dem ist nicht so. Ich sag es nur ungern (besser gesagt es gibt hier Leute die das nur ungern lesen) aber x86 ist einfach total vermurkst! Es ist ein klein wenig schade das der Itanium nichts geworden ist, da waren eine Reihe interessante Ideen drin aber auch wieder neuer Murks.

Liest man sich aber die Erklärung zu POP durch dann liest man dort, dass es egal ist (also es steht nicht so da, sondern das bei einem POP immer inkrementiert wird).
Wenn es nicht da steht dann gibt es das auch nicht, die Intel-Manuals sind in dieser Hinsicht sehr gründlich.

Bei Java möge das ja gehen (zumal du von einer lokalen Variable sprichst) ....
Das hat nichts mit Java zu tun, das sollte mit jeder ordentlich strukturierten Programmiersprache gehen. Das Ergebnis eines malloc landet ja auch in einer localen Variable wenn der so allozierte Speicher nur innerhalb einer Funktion benutzt werden soll. Sobald dieser Pointer in eine statische Variable kommt geht der Trick eh nicht mehr. Auch wenn man diesen Pointer an eine andere Funktion weiter gibt ist dieser Trick nicht mehr möglich (es sei den der Compiler hat die Möglichkeit die aufgerufene Funktion vollständig zu analysieren, z.B. bei statischen Funktionen in der selben .c-Datei). Dieser Trick wäre ein nettes Feature das aber nur in eng begrenzten Situationen funktioniert, aber davon gibt es sicher einige und wenn man sich da das malloc sparen könnte wäre es eine wünschenswerte Beschleunigung. Das Problem das bleibt ist das der Programmierer das eben auch tun kann und die meisten Programmierer überlegen sicher ob sie den Speicher den sie mal kurz brauchen vom Stack oder von malloc holen, diese Entscheidung dem Compiler zu überlassen lohnt sich nur dann wenn man davon aus gehen kann das der Compiler da den besseren Überblick (Analyse der aufgerufenen Funktionen mit beliebiger Tiefe) hat und das ist gerade erst am werden, der LLVM mit seiner Fähigkeit das er nach(während) dem Linken noch mal gründlich optimiert könnte dieses Feature eventuell in ein paar Jahren tatsächlich erfolgreich umsetzen.


@svenska:
Ich hab mir das mit dem Return Oriented Programming auf SPARC mal genauer durchgelesen und muss ehrlich sagen das ich das noch nicht ganz kapiert habe. Das Prinzip hab ich, glaube ich zumindest, verstanden aber das es wirklich so viele brauchbare Code-Schnippsel gibt (vor allem bei einer CPU-Architektur wie SPARC, die Bezeichnung als anti-x86 ist schon ziemlich zutreffend) erscheint mir irgendwie unlogisch. Trotzdem ist auch dieser Angriff eben davon anhängig das dieser Angriffs-String irgendwie auf den Stack kommt und dort den Stack-Pointer überschreibt, gerade das ließe sich bei nach oben wachsenden Stack nicht mit einer einzelnen Aktion erledigen. Nebst dessen das gutes ALR und auch das Randomisieren des Initial-Stack-Pointers (wenn das OS die Threads erstellt, beides soll mein OS können, gerade das mit dem Stack ist ja simpel zu implementieren) diesen Angriff nahezu unmöglich machen würden (selbst wenn der Stack nach unten wächst).


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #108 am: 19. November 2010, 23:51 »
Ich hab mir das mit dem Return Oriented Programming auf SPARC mal genauer durchgelesen und muss ehrlich sagen das ich das noch nicht ganz kapiert habe. Das Prinzip hab ich, glaube ich zumindest, verstanden aber das es wirklich so viele brauchbare Code-Schnippsel gibt (vor allem bei einer CPU-Architektur wie SPARC, die Bezeichnung als anti-x86 ist schon ziemlich zutreffend) erscheint mir irgendwie unlogisch.
Du brauchst nur den vorhandenen Code an jeder Bytestelle disassemblieren, bis du auf ein RETURN triffst. Dann analysierst du, ob der Code, den du gefunden hast, irgendwas sinnvolles tut. Wenn du genug Stellen gefunden hast, um dir eine turingvollständige Umgebung zu bauen, brauchst du nur noch die Daten auf den Stack schieben und einen Stackpointer überschreiben (oder in einen vorhandenen Stack injizieren).

Zu SPARC kann ich nichts sagen, aber es funktioniert garantiert mit Architekturen, deren Opcode-Space hinreichend ausgefüllt ist, besonders gut mit dem Z80 (jedes Byte ist ein gültiger Opcode) oder mit ARM (durch die Condition Codes im Opcode), aber auch x86 ist sehr gut geeignet.

Trotzdem ist auch dieser Angriff eben davon anhängig das dieser Angriffs-String irgendwie auf den Stack kommt und dort den Stack-Pointer überschreibt, gerade das ließe sich bei nach oben wachsenden Stack nicht mit einer einzelnen Aktion erledigen.
Korrekt. Der Vorteil dieser Technik ist aber (a) du legst keine unspezifizierten Daten auf den Stack, sondern valide Rücksprungadressen und (b) du führst niemals Daten aus, sondern immer vorhandenen validen Code.

Damit führst du das NX-Bit sehr elegant ad absurdum.

Nebst dessen das gutes ALR und auch das Randomisieren des Initial-Stack-Pointers (wenn das OS die Threads erstellt, beides soll mein OS können, gerade das mit dem Stack ist ja simpel zu implementieren) diesen Angriff nahezu unmöglich machen würden (selbst wenn der Stack nach unten wächst).
Bei dir funktioniert das Überschreiben des Stackpointers durch Dereferenzierung im Code sowieso nicht, da dein Stack in einem eigenen Adressraum (Segment) lebt. Außerdem brauchst du dir den Ärger mit "kurzlebige Daten auf den Stack" auch nicht machen, da bei dir Stack und Heap zwar nicht identisch, aber mit identischen Techniken gebaut sind (je ein eigenes Segment).

Du hängst die Latte für Angriffe schon allein dadurch sehr hoch, dass du "komisches Zeug" machst. Wenn ich NetBSD auf ner VAX im Keller installiere, wird die allein dadurch keiner angreifen, weil normale binäre Exploits einfach nicht funktionieren und eh sich der Angreifer heutzutage dafür eine Toolchain baut... gut, ne VAX wär cool aber nicht praktisch.

Gruß,
Svenska

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #109 am: 20. November 2010, 11:24 »
Hallo,


Ich hab mir das mit dem Return Oriented Programming auf SPARC mal genauer durchgelesen und muss ehrlich sagen das ich das noch nicht ganz kapiert habe. Das Prinzip hab ich, glaube ich zumindest, verstanden aber das es wirklich so viele brauchbare Code-Schnippsel gibt (vor allem bei einer CPU-Architektur wie SPARC, die Bezeichnung als anti-x86 ist schon ziemlich zutreffend) erscheint mir irgendwie unlogisch.
Du brauchst nur den vorhandenen Code an jeder Bytestelle disassemblieren, bis du auf ein RETURN triffst....
Das ist mir alles klar, und es funktioniert auf x86 auch wunderbar weil es eben in den Bytes ne Menge Befehle gibt die der Compiler gar nicht bewusst erzeugt hat aber auf den typischen RISC-Architekturen mit fester Befehlsgröße und immer ausgerichteten Befehlen gibt es eigentlich keine Befehle die der Compiler nicht absichtlich generiert hat. Was ich mich eben frage ist: warum findet man dann trotzdem genug dieser Code-Schnippsel um damit alle wichtigen Operationen für eine Turing-Maschine zusammen zu bekommen? Könnte man da nicht versuchen dafür zu sorgen das der Compiler da etwas vorsichtiger ist? Außerdem benutzen die für ihren Angriff auf SPARC ja immer nur das Ende der Funktionen aber sollte in diesem Ende nicht irgendeine Form von "Stack-Aufräum-Code" sein der eben ein Chaos hinterlässt wenn nicht auch der zugehörige "Stack-Einrichtungs-Code" am Funktionsanfang durchlaufen wurde? Klar der Stack wird in diesen Angriffs-Daten passend vorbereitet aber dass das immer funktioniert wundert mich sehr.

Als Gegenmaßname sind mir noch 2 Dinge eingefallen: zum einen könnte man einfach ein Byte der Magic-Cookies auf 0x00 prüfen weil die in ihren Angriffs-Daten ja keine 0x00-Bytes einbauen können und zum anderen müsste die CPU da etwas mehr in die Pflicht genommen werden den Stack (oder zumindest die Rücksprungadresse) auf Gültigkeit zu prüfen.

In meiner CPU hab ich auch ENTER und LEAVE die jeweils die callee-save Register sichern/wiederherstellen, das Link-Register und das Flag-Register ebenfalls optional sichern/wiederherstellen und eben den Platz für die lokalen Variablen auf den Stack schaffen/freigeben. Das sind eh recht komplexe Befehle die viele Dinge tun und da wäre es IMHO doch gut möglich das diese Befehle noch zusätzlich/optional ein Magic-Cookie zu den Daten auf dem Stack ablegen bzw. es auch wieder prüfen. Dieses Magic-Cookie könnte von den Daten selber, der Adresse auf dem Stack (Stackpointer) und von einem Geheimnis das nur die CPU selber lesen kann abhängen. All diese 3 Dinge sind für den Angreifer absolut zufällig und unvorhersehbar (das Linkregister ist wegen der ALR zufällig, der Stackpointer ebenfalls weil das OS einen zufälligen Startwert benutzt und das Geheimnis erst recht weil es für jeden Thread neu aus einem ordentlichen TRNG kommt), damit wäre die Manipulation des Stack quasi aussichtslos. Ich habe in den OpCodes dieser beiden Befehle noch 2 oder 3 Bits frei und ich denke das ich die für so einen Trick benutze, das Geheimnis würde ich vor dem Anfang des Stack ablegen, die CPU hat ja den Stack-Segment-Descriptor eingelesen und weiß daher wo sie nachschauen muss und für den User-Code ist das absolut unerreichbar weil dieses Geheimnis ja außerhalb des gültigen Offset-Bereichs im Stack-Segment liegt.

Trotzdem ist auch dieser Angriff eben davon anhängig das dieser Angriffs-String irgendwie auf den Stack kommt und dort den Stack-Pointer überschreibt, gerade das ließe sich bei nach oben wachsenden Stack nicht mit einer einzelnen Aktion erledigen.
Korrekt. Der Vorteil dieser Technik ist aber (a) du legst keine unspezifizierten Daten auf den Stack, sondern valide Rücksprungadressen und (b) du führst niemals Daten aus, sondern immer vorhandenen validen Code.
Punkt (a) verstehe ich nicht, um so einen Angriffs-Daten-String zu bauen musst Du doch genau wissen wo auf dem Stack er dann liegen wird und Du musst auch wissen wo genau der Code liegt, also mit ein klein wenig Zufall in beiden Dingen (ein paar wenige Bits würden schon reichen) ist dieser Angriff doch nur noch ein reiner Zufallstreffer, selbst ganz ohne irgendeine Art von Stack-Protector (damit ist meine Idee oben eigentlich fast wieder überflüssig).

Damit führst du das NX-Bit sehr elegant ad absurdum.
So haben sich AMD und Intel das sicher nicht vorgestellt. ;)

Bei dir funktioniert das Überschreiben des Stackpointers durch Dereferenzierung im Code sowieso nicht, da dein Stack in einem eigenen Adressraum (Segment) lebt.
Ich verstehe nicht was Du da meinst, ein Stack-Overflow mit Manipulation der Rücksprungadresse ist auch bei nach oben wachsenden Stack möglich. Nur eben überschreibt die aufgerufene Funktion ihre eigene Rücksprungadresse indem sie in ein Array, das auf dem Stack der aufrufenden Funktion (oder noch davor) liegt, etwas schreibt. Das ist etwas mehr tricky und sicher nicht ganz so häufig anzutreffen aber prinzipiell genau so gut möglich.

Außerdem brauchst du dir den Ärger mit "kurzlebige Daten auf den Stack" auch nicht machen, da bei dir Stack und Heap zwar nicht identisch, aber mit identischen Techniken gebaut sind (je ein eigenes Segment).
Ein malloc ist auch bei mir deutlich aufwendiger als das Stack-Frame zu vergrößern, auch wenn beides in O(1) läuft. Okay, das malloc geht nicht immer aber oft in O(1), ab und an wird auch mal neuer Speicher vom OS benötigt also das Segment vergrößert aber das trifft auf dem Stack ja auch zu da dort eben mal ne Exception wegen einem Stack-Segment-Overflow auftreten kann und das OS dann eben mal wieder das Stack-Segment vergrößern muss, so lange beides nur recht selten ist geht die Betrachtung mit O(1) als grobe Näherung gerade so durch.

Du hängst die Latte für Angriffe schon allein dadurch sehr hoch, dass du "komisches Zeug" machst.
Mit Ausnahme des Wortes "komisch" gebe ich Dir absolut Recht. Ich mache das alles eben weil ich mir davon einen enormen Gewinn an Sicherheit (und Performance) verspreche.

...und eh sich der Angreifer heutzutage dafür eine Toolchain baut...
Also ich möchte das mein System sicher ist auch wenn der Angreifer sich ne Tool-Chain baut. Es soll auch mit extrem hohem Aufwand nahezu unmöglich sein mein System zu knacken!


Ein simpler Mechanismus (statisches Magic Cookie direkt zwischen Rücksprung und Daten) erkennt Stack-Overflows und ist zumindest ein sinnvoller Debug-Helfer.
Als simples (Zweit-)Cookie würde ich ein 0-Wort empfehlen, einfach weil das nicht über die üblichen String-Befehle kommen kann. Das schließt schon mal die simplen Lücken (was wohl auch die Mehrheit der Lücken sind) aus.

Ein ordentlich programmierter Syscall prüft alle Parameter auf Plausibilität bevor er sie benutzt.
Ich behaupte, dass das nicht überall möglich (oder performant) ist. Eine grundlegende Optimierung ist doch, überflüssige Prüfungen einzusparen...
Dann ist das eine Form der Optimierung die für einen OS-Kernel definitiv nicht akzeptabel ist.

Damit wurde eine Wahlmaschine manipuliert....
Ja ja, erstens kommt es anders und zweitens als man denkt. ;)
Ich frage mich ob mir das auch passiert, heute prahle ich noch rum wie super sicher mein System doch werden soll aber wenn es übermorgen mit einem simplen Trick überlistet wird stehe ich da wie der Depp vom Dienst. :(

Sicherheit ist noch nicht so lange wichtig
Es ist gut das da jetzt endlich mal ein umdenken stattfindet. Ich muss mir mal genau durchlesen was die OpenSSL-Leute eigentlich am RNG von VIA auszusetzen haben damit ich nicht den selben Fehler mache.

Man beachte das "vor", ist es nicht eigentlich idiotisch das man bei der Speicheradressierung bewusst mit Überläufen arbeiten kann? Ich finde es deutlich besser wenn man im OpCode des Befehls zwischen + und - wählen kann und die CPU alle Überläufe mit ner Exception quittiert, da sind dann gleich wieder eine ganze Reihe von potentiellen Einfallstoren zugegangen.
Guck dir im x86 mal den Opcode-Space an. Der ist voll. Da nochmal jeden potentiellen Befehl in zwei Varianten haben wird eher nichts. Zumal ich die Möglichkeit an sich nicht so schlecht finde - wobei ein Vergleich mit dem i860 doch "etwas" hinkt.
Das war auch kein Vorschlag für x86 (da ist eh alles zu spät) sondern einfach nur so eine Idee (die ich in meiner CPU auch konsequent umsetze).

Wenn du schnelle, stromsparende CPUs möchtest, musst du entweder zwischen Performance und Anzahl der Transistoren einen Kompromiss finden. Die Möglichkeiten gehen von einfacheren Architekturen (Atom/VIA) und Emulatoren (Transmeta) zu neuen Architekturen (ARM, MIPS). Für Dinge, die man im Compiler beheben könnte noch Unmengen an Opcodes zu reservieren, ist beiden Varianten nicht förderlich... Das ist auch eine Frage der Verhältnismäßigkeit (und Kompatiblität).
Bei ARM haben die meisten Speicherzugriffe so ein Bit im OpCode (ist bei einer reinen Load-Modify-Store-Architektur ja auch nicht schwer) nur der Overflow wird nicht abgefangen, es gibt also oft 2 Möglichkeiten ein bestimmtes Byte zu adressieren.

Naja, wird dein Kernel den Sicherheitsanforderungen von "heutigen Kernels" genügen?
Ich hoffe, ich werde mir auf jeden Fall große Mühe geben alle (mir) bekannten Angriffsszenarien auszuschließen. Ob das reicht werde ich dann sehen.

Magic Cookies sind mehr als nur eine simple Buffer-Overflow-Erkennung, sie können (wenn die zugrundeliegenden Informationen Position/Wert des Cookies sicher versteckt sind) auch Stack-Angriffe vermeiden. Kostet natürlich Performance.
Aber gerade das Versteck ist das Problem, wenn der vom Compiler generierte Code da ran kommt dann kann das auch der eingeschleuste Code.
Eben das muss man verhindern...
Das muss man dann aber in Hardware machen ansonsten wird das einfach nur ein absoluter Performance-Killer.

Mich würde jetzt eher mal interessieren, wie man es dann schafft das der Code auch ausgeführt wird?
Standardprozedere: Ich überschreibe irgendeinen Kernelspeicher und weiß, zu welcher Funktion der gehört und an welcher Stelle in der Funktion ich deren Code gerade überschreibe. Danach brauche ich die Funktion nur noch so aufrufen, dass sie an diese Stelle springt. Beide Phasen des Exploits sind vollkommen unabhängig voneinander und können auch verschiedene Anwendungen betreffen (PHP überschreibt Kernelspeicher, Apache löst überschriebene Funktion aus). Klingt einfach, ist inzwischen aber sehr schwierig (u.a. durch so nette Dinger wie Heap-Randomization oder Magic Cookies im Stack).
Wie kann man den eigentlich Code im Kernel überschreiben? Gibt es da das NX-Bit nicht mehr? Oder wie wäre es mit ALR für den Kernel-Code?

Gegen gezielte Angriffe wie Styxnet kannst du dich nicht schützen.
Das klingt nach aufgeben, soweit bin ich noch nicht. ;)


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #110 am: 20. November 2010, 14:01 »
Hallo,

Was ich mich eben frage ist: warum findet man dann trotzdem genug dieser Code-Schnippsel um damit alle wichtigen Operationen für eine Turing-Maschine zusammen zu bekommen? Könnte man da nicht versuchen dafür zu sorgen das der Compiler da etwas vorsichtiger ist?
Halte ich nicht für machbar. Der Code, den Compiler da hinten rauswerfen, ist in der Regel hochgradig optimiert und hat mit dem ursprünglich im Source vorhandenem Code nicht unbedingt viel zu tun. Die Anforderungen für Turingvollständigkeit sind außerdem relativ gering, alles, was man zusätzlich findet, ist Bonus.

Außerdem benutzen die für ihren Angriff auf SPARC ja immer nur das Ende der Funktionen aber sollte in diesem Ende nicht irgendeine Form von "Stack-Aufräum-Code" sein der eben ein Chaos hinterlässt wenn nicht auch der zugehörige "Stack-Einrichtungs-Code" am Funktionsanfang durchlaufen wurde?
Kann man tun, riskiert dann aber totalen Stackverlust. Zumal die Programmiertechnik an sich noch ziemlich neu ist, da sind Gegenmaßnahmen noch nicht in die Programme eingezogen.

Bedenke, dass jede Gegenmaßnahme irgendwo Performance kostet. Die optimale Gegenmaßnahme wäre, alle Befehle in einem Simulator auszuführen und diesen Simulator in einen Sandkasten zu stopfen - das Prinzip von Java oder .NET. Verbindet man das mit exakt notwendig vielen Berechtigungen und verbietet alles andere, landet man bei Dingen wie SELinux.

Allerdings ist SELinux nicht allgemein realisierbar, weil viel zu komplex - feingeschnittene Berechtigungsschnipsel sind wesentlich komplizierter zu konfigurieren - und Java zeichnet sich vor allem durch Langsamkeit aus. Wenn man "sicher" möchte, kann man auch in Ada programmieren, wie z.B. Autopiloten im Flugzeug. Aber Endkundensoftware wird dann doch in C geschrieben... und warum, kann man hier am Beispiel von Scriptsprachen sehen. Ist kein guter Vergleich, aber als Illustration taugt er.

Als Gegenmaßname sind mir noch 2 Dinge eingefallen: zum einen könnte man einfach ein Byte der Magic-Cookies auf 0x00 prüfen weil die in ihren Angriffs-Daten ja keine 0x00-Bytes einbauen können und zum anderen müsste die CPU da etwas mehr in die Pflicht genommen werden den Stack (oder zumindest die Rücksprungadresse) auf Gültigkeit zu prüfen.
Die Rücksprungadresse auf Gültigkeit zu prüfen ist nicht möglich, das ist ja doch Sinn vons Janze: Du springst immer an korrekte, gültige Adressen. Was du meinst, ist eine Whitelist über gültige Sprungadressen für Userspace - und die zu durchsuchen ist nicht O(1), schlecht in Hardware lösbar und auch nur ein Workaround für ein anderes Problem. Mit dem Nullbyte würdest du aus Angriffen NULL-Pointer-Angriffe machen, aber auch die hat man bisher erfolgreich ausgenutzt. (Darum werden im virtuellen Adressraum die ersten 4K (ARM) / 64K (x86) von Linux nicht genutzt, die BSDs machen das ähnlich.)

Dieses Magic-Cookie könnte von den Daten selber [...] abhängen.
Von den Daten selbst geht nicht, weil sich diese ja zwischenzeitlich ändern können. Auch in der Größe: realloc() darf in den Heap verschieben, schon existiert das Objekt im Stack nicht mehr oder ist veraltet. Der Rest ja.

Korrekt. Der Vorteil dieser Technik ist aber (a) du legst keine unspezifizierten Daten auf den Stack, sondern valide Rücksprungadressen
Punkt (a) verstehe ich nicht, um so einen Angriffs-Daten-String zu bauen musst Du doch genau wissen wo auf dem Stack er dann liegen wird und Du musst auch wissen wo genau der Code liegt, also mit ein klein wenig Zufall in beiden Dingen (ein paar wenige Bits würden schon reichen) ist dieser Angriff doch nur noch ein reiner Zufallstreffer, selbst ganz ohne irgendeine Art von Stack-Protector (damit ist meine Idee oben eigentlich fast wieder überflüssig).
Voraussetzung für solche Angriffe ist, dass du den Systemcode ganz genau kennst. Bei Windows-Kerneln (wenig Variation) oder Wahlmaschinen mit ROMs (keine Variation) ist das in jedem Fall gegeben. Diese Information brauchst du auch schon, bevor du den Angriff überhaupt machen kannst, schließlich brauchst du einen Disassembler dafür.
Wo genau im Stack deine manipulierten Daten landen, ist wiederum relativ egal, solange der Stackpointer da hinzeigt; für den ersten Aufruf reicht es. Danach kannst du es ja vom System ermitteln lassen. Da gibt es sicherlich genug Fantasie bei den richtigen Leuten. ;-)

Nur eben überschreibt die aufgerufene Funktion ihre eigene Rücksprungadresse indem sie in ein Array, das auf dem Stack der aufrufenden Funktion (oder noch davor) liegt, etwas schreibt. Das ist etwas mehr tricky und sicher nicht ganz so häufig anzutreffen aber prinzipiell genau so gut möglich.
Wenn du für den Stack ein eigenes Segment aufmachst und für den Heap ein eigenes Segment aufmachst (was grundsätzlich nahe liegt), dann brauchst du Allokation auf dem Stack eigentlich nicht, weil Stack und Heap dann gleich kompliziert aufgebaut sind (im Endeffekt hängt die Geschwindigkeit davon ab, ob du die Segmente vergrößern musst oder nicht). Einen Performance-Vorteil bekommst du nur, wenn die Heap-Implementation wesentlich langsamer ist, als die Stack-Implementation.

...und eh sich der Angreifer heutzutage dafür eine Toolchain baut...
Also ich möchte das mein System sicher ist auch wenn der Angreifer sich ne Tool-Chain baut. Es soll auch mit extrem hohem Aufwand nahezu unmöglich sein mein System zu knacken!
Richtig. Aber je seltsamer das System aufgebaut ist, umso mehr Aufwand (v.a. Wissen) verlangst du vom Angreifer. Und bei steigendem Aufwand sinkt die Wahrscheinlichkeit für einen Angriff.

Wie gesagt, gegen gezielte Angriffe kann man sich nicht schützen. Es geht in erster Linie um Breitbandangriffe.

Ein simpler Mechanismus (statisches Magic Cookie direkt zwischen Rücksprung und Daten) erkennt Stack-Overflows und ist zumindest ein sinnvoller Debug-Helfer.
Als simples (Zweit-)Cookie würde ich ein 0-Wort empfehlen, einfach weil das nicht über die üblichen String-Befehle kommen kann. Das schließt schon mal die simplen Lücken (was wohl auch die Mehrheit der Lücken sind) aus.
Genau das würde ich nicht machen, wenn es um das Debugging geht. Damit werden Fehler im Programm durch das Betriebssystem entschärft und das Programm hat dann auf anderen Betriebssystemen gravierende Sicherheitslücken.

Damit wurde eine Wahlmaschine manipuliert....
Ja ja, erstens kommt es anders und zweitens als man denkt. ;)
Ich frage mich ob mir das auch passiert, heute prahle ich noch rum wie super sicher mein System doch werden soll aber wenn es übermorgen mit einem simplen Trick überlistet wird stehe ich da wie der Depp vom Dienst. :(
Eben. Die Idee, dass ein ausführender Zugriff auf den RAM hardwareseitig mit einem RESET quittiert wird, fand ich schon genial... aber die Fantasie mancher Menschen ist meiner eben weit überlegen.

Bei ARM haben die meisten Speicherzugriffe so ein Bit im OpCode (ist bei einer reinen Load-Modify-Store-Architektur ja auch nicht schwer) nur der Overflow wird nicht abgefangen, es gibt also oft 2 Möglichkeiten ein bestimmtes Byte zu adressieren.
Bei x86 hast du ganz viele Möglichkeiten. ;-)

Naja, wird dein Kernel den Sicherheitsanforderungen von "heutigen Kernels" genügen?
Ich hoffe, ich werde mir auf jeden Fall große Mühe geben alle (mir) bekannten Angriffsszenarien auszuschließen. Ob das reicht werde ich dann sehen.
Oder auch nicht, es sei denn, du engagierst einen guten Hacker. Ich bin keiner. :-P

Wie kann man den eigentlich Code im Kernel überschreiben? Gibt es da das NX-Bit nicht mehr? Oder wie wäre es mit ALR für den Kernel-Code?
NX verbietet das Ausführen, nicht das Überschreiben. Code im Kernel zu überschreiben ist auf verschiedenen Wegen möglich. Man kann als "root" /dev/mem oder /dev/kmem überschreiben, deswegen führt man Anwendungen nur im Notfall so aus. Du kannst einen Hardwaretreiber so manipulieren, dass er DMA-Zugriffe von Speicher zu Speicher erledigt, da gibt es keine Prüfung. Du kannst eine Hardware so mit gezielt kaputten Daten von außen bewerfen, dass der Treiber DMA an von dir gewünschte Adressen macht. Auf diese Weise wurde ja die PS3 gehackt (manipulierte USB-Sticks). Oder dein Kernel prüft die Zieladresse an einer Stelle nicht genau und überschreibt Teile von sich selbst.

Gegen gezielte Angriffe wie Styxnet kannst du dich nicht schützen.
Das klingt nach aufgeben, soweit bin ich noch nicht. ;)
Noch nicht. ;)

Gruß,
Svenska

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #111 am: 20. November 2010, 14:12 »
Also ich kann euch beiden gar nicht mehr folgen  :?

Also wie man Kernel-Code überschreiben kann würde mich auch mal interessieren. Denn so wie ich es verstanden willst du irgendeinen Kernel-Code überschreiben und dann ausführen.

Gut, jetzt wurde DMA genannt, aber dagegen kann man sich ja nur schützen in dem die Treiber gut programmiert sind.
(Gibt es sowas wie DMA auch auf anderen Architekturen?)

Jetzt mal von nem MikroKernel ausgehend, wie willst du da nen Buffer-Overflow durch Paramter die über den Stack übergeben werden, im Kernel erreichen?

Noch was zu dem Direction-Flag in den Diskriptoren. Wenn das den Stack nicht beeinflusst, wozu ist das dann gut und da?

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #112 am: 20. November 2010, 14:51 »
Also wie man Kernel-Code überschreiben kann würde mich auch mal interessieren. Denn so wie ich es verstanden willst du irgendeinen Kernel-Code überschreiben und dann ausführen.
Das ist der Weg für Götter, ja.

Gut, jetzt wurde DMA genannt, aber dagegen kann man sich ja nur schützen in dem die Treiber gut programmiert sind.
Sind sie in der Regel nicht.

(Gibt es sowas wie DMA auch auf anderen Architekturen?)
Ja.

Jetzt mal von nem MikroKernel ausgehend, wie willst du da nen Buffer-Overflow durch Paramter die über den Stack übergeben werden, im Kernel erreichen?
Hä? Angriffe durch Buffer-Overflow sind implementierungsspezifisch. Ich kenne deine Implementation nicht, also kann ich darauf nicht antworten. Im Zweifelsfall kann man das IPC angreifen.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #113 am: 20. November 2010, 15:00 »
Zitat von: svenska
Hä? Angriffe durch Buffer-Overflow sind implementierungsspezifisch. Ich kenne deine Implementation nicht, also kann ich darauf nicht antworten. Im Zweifelsfall kann man das IPC angreifen.
Wie greift man ein fixed-size IPC an? Also ich bin mir relativ sicher (so lange bis mir jemand das Gegenteil beweist), das mein Kernel-Code Buffer-Overflow sicher ist.

Zitat von: svenska
Sind sie in der Regel nicht.
Leider. Problem ist halt immer wieder, das man sich nicht gegen DMA schützen kann bzw. das was es anrichten kann.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #114 am: 20. November 2010, 15:03 »
Zitat von: svenska
Hä? Angriffe durch Buffer-Overflow sind implementierungsspezifisch. Ich kenne deine Implementation nicht, also kann ich darauf nicht antworten. Im Zweifelsfall kann man das IPC angreifen.
Wie greift man ein fixed-size IPC an? Also ich bin mir relativ sicher (so lange bis mir jemand das Gegenteil beweist), das mein Kernel-Code Buffer-Overflow sicher ist.
Ich würde davon ausgehen, dass es inhärent unsicher ist. Ist sicherer.

Man kann ein Fixed-Size-IPC angreifen, indem man mehr schickt als erlaubt oder indem man den Inhalt so strickt, dass der Message-Parser amok läuft oder Dinge tut, die man als Designer so nicht vorgesehen hat.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #115 am: 20. November 2010, 15:23 »
Zitat von: svenska
Man kann ein Fixed-Size-IPC angreifen, indem man mehr schickt als erlaubt
Du meinst, dass man 2 Msgs schickt obwohl laut Protokoll nur eine geschickt werden soll?

Zitat von: svenska
indem man den Inhalt so strickt, dass der Message-Parser amok läuft oder Dinge tut, die man als Designer so nicht vorgesehen hat.
Das wären dann wieder Programmierfehler, aber wie stellst du dir das vor das der Parser amok lufen könnte?

Ich meine, ich würde nen Message-Parser als switch-Verzweigung bauen und die anderen Paramter sind dann Parameter für die aufzurufenden Funktionen (mal sehr vereinfacht gesagt), da kann dem Parser erstmal nichts passieren.

Allerdings dachte ich auch mit IPC angeifen meinst du das man das IPC nutzt um den Kernel zu infiltrieren.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #116 am: 20. November 2010, 18:59 »
Das meine ich.
Sicherheitsprobleme teilen sich meist in zwei Kategorien auf: (a) systematische Probleme bzw. schlechtes/kaputtes Design oder (b) zufällige Probleme bzw. Bugs.

Das Design sollte von Anfang an mit Sicherheit im Hinterkopf gestrickt sein, dein fixed-length-message-IPC sollte dazu gehören. Das ist gefühlt der leichtere Teil. Gegen Bugs kannst du nichts tun außer sauber programmieren.

Und ja, ich bezweifle, dass wir alle fehlerfrei programmieren können. ;-)

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #117 am: 21. November 2010, 10:09 »
Hallo,


Was ich mich eben frage ist: warum findet man dann trotzdem genug dieser Code-Schnippsel um damit alle wichtigen Operationen für eine Turing-Maschine zusammen zu bekommen? Könnte man da nicht versuchen dafür zu sorgen das der Compiler da etwas vorsichtiger ist?
Halte ich nicht für machbar. Der Code, den Compiler da hinten rauswerfen, ist in der Regel hochgradig optimiert und hat mit dem ursprünglich im Source vorhandenem Code nicht unbedingt viel zu tun. Die Anforderungen für Turingvollständigkeit sind außerdem relativ gering, alles, was man zusätzlich findet, ist Bonus.
Okay, der Compiler hat natürlich in erster Linie die Ausführungsgeschwindigkeit im Auge, deswegen wäre z.B. ein Compiler-Parameter nicht schlecht das ihn zwingt den Stack sauberer zu halten (in meinem Fall z.B. ENTER und LEAVE mit Magic-Cookies zu benutzen (falls die Funktion das überhaupt erfordert, was z.B. bei einer Leaf-Funktion ja nicht unbedingt der Fall ist)).

Bedenke, dass jede Gegenmaßnahme irgendwo Performance kostet....
Deswegen ja gleich in Hardware damit der Performanceverlust so klein als möglich bleibt.

und zum anderen müsste die CPU da etwas mehr in die Pflicht genommen werden den Stack (oder zumindest die Rücksprungadresse) auf Gültigkeit zu prüfen.
Die Rücksprungadresse auf Gültigkeit zu prüfen ist nicht möglich, das ist ja doch Sinn vons Janze: Du springst immer an korrekte, gültige Adressen. Was du meinst, ist eine Whitelist über gültige Sprungadressen für Userspace - und die zu durchsuchen ist nicht O(1), schlecht in Hardware lösbar und auch nur ein Workaround für ein anderes Problem.
Da hast Du mich falsch verstanden. Ich meinte das wenn die CPU ein Magic-Cookie zu den gesicherten Registern (zu denen auch das Link-Register mit der Rücksprungadresse zählt) packt das sie dann den numerischen Wert des Magic-Cookie aus eben diesen gesicherten Daten plus dem Geheimnis errechnen soll (quasi ne Art Prüfsumme), beim Laden könnte dann die selbe Rechnung durchgeführt und das Magic-Cookie geprüft werden und es würde jede Manipulation auffallen. Das funktioniert gerade deshalb weil die gesicherten Register (eben die callee-saves) und die Rücksprungadresse ja nicht auf dem Stack manipuliert werden dürfen und der Caller sich drauf verlässt das die alle noch stimmen. Andere Daten die auch noch auf dem Stack liegen dürfen natürlich nicht mit in diese Berechnung einfließen, es geht nur um das was der ENTER-Befehl selber auf dem Stack ablegt bzw. was der LEAVE-Befehl vom Stack wieder einließt. Klar ist es theoretisch möglich das die Manipulation nicht auffällt wenn der Angreifer das Geheimnis kennt oder errät. Gegen das Kennen hilft es wenn die CPU dieses Geheimnis effektiv gegen jedweden User-Mode-Zugriff schützt (das kann ich bei meiner CPU problemlos gewährleisten) und das erraten sollte bei z.B. 64Bit als Option ausscheiden. Bleibt nur noch sicherzustellen das der Kernel für jeden neuen Thread ein neues Geheimnis aus einen guten TRNG holt. Ich denke wenn man das ordentlich umsetzt dann dürfte ein Buffer-Overflow keine Gefahr mehr sein und der Performanceverlust sollte sich bei guter HW-Implementierung auch in Grenzen halten (auch wenn er nicht 0 ist, aber für irgendetwas müssen die Computer ja immer schneller werden).

Mit dem Nullbyte würdest du aus Angriffen NULL-Pointer-Angriffe machen, aber auch die hat man bisher erfolgreich ausgenutzt. (Darum werden im virtuellen Adressraum die ersten 4K (ARM) / 64K (x86) von Linux nicht genutzt, die BSDs machen das ähnlich.)
Ich fürchte auch da hast Du mich missverstanden, mit dem 0-Word als (zusätzliches) Magic-Cookie meinte ich das wenn man erwartet das ein 0-Wort auf dem Stack liegt das dann der Angreifer ein Problem hat weil er eben bei dem meisten Buffer-Overflows, die ja auf String-Funktionen beruhen, keine Chance hat weil die String-Funktionen ja eben ein 0-Byte als Abbruchkriterium nehmen. Ließ Dir mal dort http://cseweb.ucsd.edu/~hovav/papers/brss08.html das full paper durch, da drin wird auch erklärt warum eben keine 0-Bytes in den Angriffs-Daten drin sein dürfen.

Wo genau im Stack deine manipulierten Daten landen, ist wiederum relativ egal, solange der Stackpointer da hinzeigt; für den ersten Aufruf reicht es. Danach kannst du es ja vom System ermitteln lassen. Da gibt es sicherlich genug Fantasie bei den richtigen Leuten. ;-)
Das kann ich mir einfach nicht vorstellen, wenn Du nicht genau weißt wo Deine Daten im Stack liegen dürfte es schwer fallen diese richtig zu adressieren oder Du musst die "nützlichen" Code-Schnipsel noch weiter einschränken indem nur zum Stack-Pointer relative Adressierung nutzbar ist. Aber spätestens bei ALR für den Code ist dann Schluss weil Du ja korrekte absolute Rücksprungadressen brauchst.

Wenn du für den Stack ein eigenes Segment aufmachst und für den Heap ein eigenes Segment aufmachst (was grundsätzlich nahe liegt), dann brauchst du Allokation auf dem Stack eigentlich nicht, weil Stack und Heap dann gleich kompliziert aufgebaut sind (im Endeffekt hängt die Geschwindigkeit davon ab, ob du die Segmente vergrößern musst oder nicht). Einen Performance-Vorteil bekommst du nur, wenn die Heap-Implementation wesentlich langsamer ist, als die Stack-Implementation.
Die Heap-Implementierung ist zwangsläufig langsamer als die Stack-Implementierung, der Heap muss erst mal ermitteln welches Segment er für die gewünschte Objekt-Größe nehmen muss und dann noch ein paar kleine Verwaltungsaufgaben erledigen (z.B. das neu benutze Objekt als belegt markieren), beim (nach oben wachsenden) Stack läuft es darauf hinaus das der aktuelle Stack-Pointer gesichert werden muss (das ist dann der Pointer auf das neue Objekt) und dann die Größe des gewünschten Objekts einfach dazu addiert werden muss (also genau 2 Assemblerbefehle). Das der Stack die Objekte auch nur in LIFO frei gibt macht es ihm auch sehr viel einfacher bzw. direkt freigeben muss er eigentlich nicht das wird ja spätestens am Ende der Funktion eh automagisch erledigt.

Wie gesagt, gegen gezielte Angriffe kann man sich nicht schützen. Es geht in erster Linie um Breitbandangriffe.
Wie gesagt, ich möchte das mein System auch bei gezielten Angriffen noch gut stand halten kann. Das 100%-tige Sicherheit unmöglich ist ist mir auch bewusst aber ich will mich zumindest bemühen (dazu hat mir diese Diskussion in den letzten Tagen auch einige Denkanstöße vermittelt).

Ein simpler Mechanismus (statisches Magic Cookie direkt zwischen Rücksprung und Daten) erkennt Stack-Overflows und ist zumindest ein sinnvoller Debug-Helfer.
Als simples (Zweit-)Cookie würde ich ein 0-Wort empfehlen, einfach weil das nicht über die üblichen String-Befehle kommen kann. Das schließt schon mal die simplen Lücken (was wohl auch die Mehrheit der Lücken sind) aus.
Genau das würde ich nicht machen, wenn es um das Debugging geht. Damit werden Fehler im Programm durch das Betriebssystem entschärft und das Programm hat dann auf anderen Betriebssystemen gravierende Sicherheitslücken.
Hä, wovon schreibst Du da eigentlich? Was hat das OS damit zu tun? Das Erzeugen und Prüfen von diesen Magic-Cookies macht doch üblicherweise der vom Compiler generierte User-Code im Funktions-Prolog/Epilog. Dadurch kann ein Programm doch keine zusätzlichen Sicherheitslücken bekommen bzw. vorhandene überdeckt werden.

Bei ARM haben die meisten Speicherzugriffe so ein Bit im OpCode (ist bei einer reinen Load-Modify-Store-Architektur ja auch nicht schwer) nur der Overflow wird nicht abgefangen, es gibt also oft 2 Möglichkeiten ein bestimmtes Byte zu adressieren.
Bei x86 hast du ganz viele Möglichkeiten. ;-)
Nein, bei x86 (im PM mit Flat-Memory, im RM ist das wieder anders) gibt es immer nur eine Möglichkeit ein bestimmtes Byte zu adressieren (weil die Offsets und Displacements ja immer dazuaddiert werden), es gibt aber extrem viele Befehle die dieses Byte erreichen können.

Code im Kernel zu überschreiben ist auf verschiedenen Wegen möglich. Man kann als "root" /dev/mem oder /dev/kmem überschreiben, deswegen führt man Anwendungen nur im Notfall so aus.
Also wenn der Admin sein Passwort eingibt um ein Programm laufen zu lassen dann helfen eh nicht mehr viele Sicherheitsmaßnahmen, egal ob die in Software oder in Hardware implementiert sind.

Du kannst einen Hardwaretreiber so manipulieren, dass er DMA-Zugriffe von Speicher zu Speicher erledigt, da gibt es keine Prüfung. Du kannst eine Hardware so mit gezielt kaputten Daten von außen bewerfen, dass der Treiber DMA an von dir gewünschte Adressen macht.
Das sind dann Bugs im Treiber oder in der zugehörigen Hardware, die müssen auf jeden Fall gefixt werden. Ich denke das auch die Hardware-Hersteller sich so langsam mit dem Thema Sicherheit beschäftigen. Das ist einer der Gründe warum ich prinzipiell kein FireWire mag, da ist es möglich das ein externes Gerät beliebig auf den physischen Speicheradressraum zugreifen kann (lesen und schreiben, im RAM aber auch in den MMIO-Bereichen aller HW-Komponenten). Aus diesem Grund hab ich auf meiner Plattform im System-Controller (der Bridge zwischen den CPUs mit dem RAM und der Peripherie) eine Möglichkeit geplant das die Peripherie nur auf bestimmte RAM-Bereiche zugreifen kann. Alle die Adressen die ein Treiber seiner HW mitteilt müssen ja in festen Pages liegen und das muss der Treiber immer explizit anfordern (und auch wieder freigeben) und genau darauf soll der Kernel dann die Peripherie-Zugriff beschränken, ob das überhaupt praktikabel ist weiß ich noch nicht aber wenn nicht dann soll es damit zumindest möglich sein das der Kernel seine eigenen Speicherbereiche für die Peripherie verbietet.

Das klingt nach aufgeben, soweit bin ich noch nicht. ;)
Noch nicht. ;)
Bis ich aufgebe dauert es für gewöhnlich eine kleine Weile. ;)


Gut, jetzt wurde DMA genannt, aber dagegen kann man sich ja nur schützen in dem die Treiber gut programmiert sind.
Selbst wenn die Treiber perfekt sind könnte immer noch ein Bug in der Hardware stecken und das kannst du als User oder Treiber-Programmierer kaum prüfen. Da müssen einfach die Hardware-Hersteller mitdenken. Noch schlimmer ist es wenn externer Zugriff in der Spezifikation klar erlaubt und beschrieben ist, wie bei FireWire.

Gibt es sowas wie DMA auch auf anderen Architekturen?
Ja und wenn nicht dann ist die Hardware busmasterfähig, kann also selbstständig (ohne einen plattform-spezifischen dedizierten DMA-Controller) auf den RAM (und oft auch auf die andere Hardware) zugreifen.

Noch was zu dem Direction-Flag in den Diskriptoren. Wenn das den Stack nicht beeinflusst, wozu ist das dann gut und da?
Das ist eben eine Verwaltungsinformation damit der Kernel weiß das neuer Speicher unten dran kommt und nicht oben. Ob dieses Flag auch die CPU in irgendeiner Weise beeinflusst weiß ich nicht (mehr) aber das sollten die Intel-Manuals ausführlich erklären.


Man kann ein Fixed-Size-IPC angreifen, indem man mehr schickt als erlaubt
Das sollte der Kernel mit geeigneten Plausibilitätschecks abfangen. Gerade deswegen sollte man bei den Syscalls an dieser Stelle nicht sparsam sein, was nützt ein schnelles System wenn es mit ner simplen Kleinigkeit zum Absturz zu bringen ist.

Wegen dem Fixed-Size-IPC: ich will Flexible-Size-IPC und sehe da auch keine zusätzlichen Probleme. Warum denkt ihr das IPC überhaupt ein guter Angriffspunkt ist?

oder indem man den Inhalt so strickt, dass der Message-Parser amok läuft oder Dinge tut, die man als Designer so nicht vorgesehen hat.
Dagegen sollte ein sauber programmierter Service einigermaßen immun sein.


Und ja, ich bezweifle, dass wir alle fehlerfrei programmieren können. ;-)
Ich möchte Dich bitten keine solchen unhaltbaren Verallgemeinerungen mehr zu machen! ;)


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #118 am: 21. November 2010, 10:30 »
Zitat von: erik
Das ist eben eine Verwaltungsinformation damit der Kernel weiß das neuer Speicher unten dran kommt und nicht oben.
Auch dadurch erschließt sich mir der Sinn dieses Flags noch nicht (das ist doch etwas dass das OS selbst speichern kann, dafür brauch man keine Hardware Unterstützung). So ein Flag würde für mich nur Sinn ergeben wenn es Einfluss auf die Hardware hat.

Zitat von: erik
Ob dieses Flag auch die CPU in irgendeiner Weise beeinflusst weiß ich nicht (mehr) aber das sollten die Intel-Manuals ausführlich erklären.
Inzwischen habe ich sogar die Stelle gefunden, wo steht das der Stack auf x86 immer nach unten wächst. Das Flag macht irgendetwas im Zusammenhang mit den Segment-Limits, aber das sind Sachen die nur Verwendung finden, wenn man kein FlatMemory benutzt.

Zitat von: erik
Das sollte der Kernel mit geeigneten Plausibilitätschecks abfangen. Gerade deswegen sollte man bei den Syscalls an dieser Stelle nicht sparsam sein, was nützt ein schnelles System wenn es mit ner simplen Kleinigkeit zum Absturz zu bringen ist.
Wie soll der Kernel überprüfen ob die Daten in der Nachricht Sinn ergeben? Der Kernel braucht bei IPC so gut wie gar nichts zu überprüfen.

Zitat von: erik
Warum denkt ihr das IPC überhaupt ein guter Angriffspunkt ist?
Das würde mich auch interessieren!

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #119 am: 21. November 2010, 11:02 »
Hallo,


Inzwischen habe ich sogar die Stelle gefunden, wo steht das der Stack auf x86 immer nach unten wächst.
Na also.

Das Flag macht irgendetwas im Zusammenhang mit den Segment-Limits, aber das sind Sachen die nur Verwendung finden, wenn man kein FlatMemory benutzt.
Also benutzt das heute keiner mehr. Die Segmentierung des 386 kann so viel mehr, da ist es richtig schade das die konsequent ignoriert wird und AMD sollte sich was schämen das die sowas tolles im Long-Mode abgeschafft haben.

Wie soll der Kernel überprüfen ob die Daten in der Nachricht Sinn ergeben? Der Kernel braucht bei IPC so gut wie gar nichts zu überprüfen.
Der Kernel kann die Länge der Daten prüfen und auch ob die Pointer gültig sind. Da ist einiges was der Kernel bei jedem Syscall auf Plausibilität prüfen kann/muss.
Die bei IPC übergebenen Kommandos/Antworten und Nutzdaten müssen die beteiligten Prozesse schon selber prüfen, da kann der Kernel wirklich nicht viel machen.


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

 

Einloggen