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