 Wobei, bei Blöschen von Speicher hat der Titelfunknisten-Vortrag ist Memset, warum ist es schwierig, Speicher zu löschen? Ilja von Schumel ist ein Sicherheits-Researcher, der gerne neue Sachen findet. Und er hat gefunden, dass es ganz schön schwer ist, sensible Inhalte im Speicher zu löschen. Also wird er uns heute eine Überblickspräsentation geben. Dazu gibt ihm eine große Runde von Applaus. Okay, perfekt. Ja, so, als Herrold erklärt hat, sprich ich über Memset. Wer bin ich? Vor langer Zeit war das ich. Viel mehr Haare, viel weniger Fett. Dieses ist mein 17. Kongress. Top von Kessel. Dieses ist mein 17. Kongress in Sequenz. Ich habe hier schon einige Male gesprochen. Nicht fünf Sequenz, aber gut. Ich arbeite für einen Betrieb namens IO Active und bin der Direktor von Pentesting. Das heißt, es gibt hier keine Doppelbedeutung. Wir haben einfach einen Tief von Pentestern. Wir stellen andere Kontrolle, ich spreche mit mir. Ich mag gerne Low-Level-Stuff und lese gerne Code. Was ist es, über mich? Von wem glaube ich, dass Leute das mögen könnten? Sicherheitsleute, Kryptoleute, wenn ihr Kompilatzeug mögt? Oder im allgemeinen über Technologie-Neugierig seid? Was ihr so wissen solltet, das erste auf den Vorlehen ist relativ simpel. Also grundlegendes Technologieverständnis sollte reichen. Wenn ihr ein bisschen C-Hintergrund habt, ist das gut. Während ich in die zweite Hälfte gehe, werden Sachen etwas schwieriger. Aber wenn ihr auch nur die erste Hälfte versteht, ist das immer noch höchstlich. Also worüber möchte ich sprechen? Im Grunde über ein sehr einfaches Problem davon, Kryptografie zu implementieren. Das Problem ist wirklich sehr einfach, die Lösung halt nicht. Es gibt sehr, sehr viel, was sich bewegt. Es gibt viele Subtilitäten zu verstehen. So, warum? Was zum Henke, es ist spät des 2018, wissen wir doch, warum trägst du darüber vor? Naja, es ist erstens immer noch überall zu finden. Ich werde das auf den Folien zeigen, mit Daten, mit Bugs. Aber der eigentliche Antrieb für diesen Talk und diese Präsentation ist, dass ich dieses Jahr mit drei verschiedenen Kunden und drei verschiedenen Softwareprojekten darüber geredet habe, die alle ungefähr diesen Bug hatten, also den exakt gleichen Bug hatten. Ja, toll, verstehen wir, wie fixen wir das jetzt portabel. Und das ist irgendwie nicht einfach. Und das andere ist, konzeptuell, das ist gut verstanden, sicher? Also, Leute sind irgendwie plassiert darüber. Aber praktisch verstehen nicht viele Menschen, wie ubiquitär und allgemein dieses Problem vorhanden ist. Also, ja, nee, der Compiler macht das. Und der Tod ist überall. Also, kommen diese Probleme überall? Es ist schwer, aus dem Code zu sehen, dass es da ist. Aber wenn man sich dann die Compiler-Logs ansieht, sieht man, dass es da ist. Und das Dritte ist, es sind halt grundlegende Sachen, die auf den Kongress passen. Und wir wollen insbesondere auf dem Kongress auch über grundlegende Sachen in die nächste Generation tragen. Und das passt doch hervorragend zur Grundlage. Bevor ich hier also rein tauche, es gibt reichlich Leute, die mir hier geholfen haben. Das Problem ist wohl bekannt, wie gesagt, schon seit 20 oder 30 Jahren. Also, haben viele Leute darüber publiziert und Paper und Präsentationen. Ich kenne nicht alle davon persönlich. Und ich wünsche, ich könnte sie alle auflisten, aber die Leute, die hier aufgelistet sind, sind nur einen oder zwei entfernt von mir, die irgendwie Einfluss auf diese Folien hatten. Und manche von euch sind hier sicher im Publikum. Eure Hilfe war sehr wertgeschätzt. Und jetzt lass uns endlich wirklich einfach. Also sagen wir, du schreibst gerade ein Stück Code und es wird sensibler Daten behandeln. Also Schlüsse, Klartext, Session Tokens, Passwörter. Passwort Hashes. Irgendwas, was irgendwie sensibel sein könnte. Wenn man also eine Sicherheitsbewusste Person ist, dann möchte man diese sensiblen Daten natürlich auch schnell wieder loswerden. Und zwar richtig gründlich löschen aus dem Speichel. Warum sollte man das wollen? Naja, falls nicht. Und wenn es dann irgendwie ein Informationsleck gibt, das später gefunden wird, dann könnte vielleicht dieser Speichel benutzt werden, um diese Tokens und Kies zu finden. Das fühlt sich vielleicht wie ein großer Sprung an, aber Hardbleed ist ein gutes praktische Baldbleed dafür, dass es das wirklich gibt. Wenn ihr also diesen Schritt macht und denkt, ich will solche sensiblen Daten wirklich loswerden, dann ist das schon ziemlich cool. Viel Software, die das tun sollte und die solche sensiblen Daten hält, kümmern sich nicht herum. Also seid ihr schon wirklich vor den meisten, wenn ihr daran denkt. In der Praxis würde es also aussehen, dass man eine solche Funktion wie dieses hier schreibt. So Beispielcode, die einen Schlüssel generiert. Das deklariert eine lokale Variable. Sie liest zufällige Bits, kopiert sie, und jetzt sagt Memset und tut da die Schlüsseldaten rein, dann wird es geklärt und Ruhe ist perfekt, oder? Dann lässt man das laufen, man kompelliert es, man fügt einen Main hinzu und tut es genau das, was es soll. Man guckt sich den Assemblycode an und das ist auch immer noch gut. Das Problem ist, was ihr jetzt tut, das ist nicht releasebasierter Code. Dann sagt ihr ja aber dem Optimierer beim Kompellieren, das optimieren soll. Typischerweise irgendwo zwischen O2 oder OS, und wenn man sich dann Assembly wieder ansieht, kriegt man ein völlig anderes Bild davon, was tatsächlich passiert. Es gibt eine Webseite der Compiler Erkunde, Compiler Explorer. Auf der linken Seite zeigt es C, auf der rechten Seite zeigt es Assembly. Es ist farbbasiert und zeigt euch gut die Verbindungen an. Lass uns also unser kleines Beispiel hier ansehen und dann sehen wir auf der linken Seite meinen C Code General Key und reizt den dazu passenden Assemblycode mit den passenden Farben. Dann seht ihr, welcher C Code zu welchem Assembly gehört, seht das verheert, dieses Memset sind diese 5-Zahlen Assembler. Insbesondere seht ihr das Memset einfach direkt zu Memset übersetzt wird. Wenn du jetzt also fertig bist und möchtest das in die Welt hinaus tragen, dann kommt plötzlich das Optimieren bei minus O1 und plötzlich sieht es anders aus. Es ist kürzer. Plötzlich ist der Call Memset, hat sich verändert und ist jetzt aber weiß und nicht mehr zu finden. Es passiert einfach nicht mehr, es ist nicht mehr da. Und das ist ein Problem. Was ist passiert? Ja, ich habe es gestohlen. Was passiert ist? Detstore Optimization oder Detstore Elimination. Es ist ein Problem. Es ist ein Problem. Es ist ein Problem. Es ist ein Problem. Es ist ein Problem. Es ist ein Problem. Es ist eine Elimination. Dieē was du kein Er Ugandadestory ermöglicht hast, hat sie nicht killed. Ich geh Emitter H distances, da sie ihren Dawn und Zeit evo er Ram Morocco, nicht weiß, was der Code tut, ist er total, ist er der Meinung, dass ein Code angemessen ausführt. Das ist ein Semihagemein des Problems. Ich wollte mir also die üblichen Compiler ansehen und gucken, welche das so ausoptimieren können und das tun. Mit manchen musste ich mehr spielen, mit manchen weniger, oder hin und her springen. Diese Liste von 10 Compilern habe ich getestet, aber den IBM Compiler konnte ich nicht testen so viele tausend Dollar, habe ich nicht. Und das sind die, die ich getestet habe. Die ersten 5 oder die ersten 4 kennt ihr, Schissesee, Klang oder Microsoft Compiler. Auch der Oracle Compiler ist auf dem Compiler Explorer, das war also auch einfach zu testen. Und es war einfach zu dazukriegen, dass die Memsets wegoptimieren. Und dann habe ich auch andere versucht, den Imparcaderos, Prismfess und Farbeleih. Und von den 10 habe ich 8 dazu gekriegt, dass sie es wegoptimieren. 80 Prozent der gewöhnlichsten Compiler machen das in praktischen Fällen. Es ist also nicht nur ein theoretisches Problem. Die letzte Sache, ich habe eine ganze Weile versucht, PGI Compiler dazu zu bringen, das zu machen. Und der hat sogar eine Option, in Toten Speicher zu schreiben, zu eliminieren. Aber selbst damit hat das verdammte Ding sich nicht bewegt. Ich weiß nicht, was es da tut. Aber die meisten Compilers, wenn du ihnen sagst, sie sollen optimieren, dann optimieren sie halt auch so was wie ein Death Star weg. Okay, wie oft passiert das denn jetzt eigentlich? Und das kommt so ein bisschen aus einer Diskussion, die ich früher im Jahr hatte mit ein paar Kollegen. Und einige Leute haben gesagt, ja, okay, ich sehe jetzt O1 oder O2 oder OS jetzt natürlich so oft. Und dann habe ich mich auch umgeguckt und dann habe ich mir überlegt, wo kriegt die Daten hier. Ja gut, ich gucke mal bei opensource.effort.com und habe mir mal die Makefiles geguckt. Und habe mir mal die O2 oder OS Switches angeguckt. Da habe ich gesehen, die benutzen ja keine Makefiles, sie haben irgendwie ein komisches Spielsystem. Und das benutzt aber OS by default. Also irgendwie so 200 von 200. And then I don't hold list of programs I want to test in like FreeBSD and you've been to a bunch of Linux distros. But that's pretty boring and I ran out of time. So I kind of stopped there. But these numbers should be good enough. In addition, if you look at the common ... Wenn man sich darüber hinaus noch die üblichen Entwicklungsumgebungen anguckt und guckt was so, wenn man ihnen sagt so, was passiert, wenn man ein Projekt im Release Mode baut, dann macht xCode by default OS und Visual Studio by default O2. Also die machen von Haus aus, machen die Optimierung. Ja und daraus kann man eigentlich dessen, ja, das ist überall. Nicht überall, aber fast überall. Okay, jetzt wissen wir, was das Problem ist. Und wir wissen, dass es tatsächlich auch ein praktisch relevantes Problem ist. Das sehr, sehr oft auftritt und mit den meisten Compilern auftritt. Also es ist ein echtes Problem. Also wie lösen wir das jetzt tatsächlich? Und das ist der Punkt, wo es wirklich schwierig wird. Es gibt viele Lösungen, aber kein davon ist wirklich jetzt übertragbar von einem zum anderen. Das eine funktioniert mit dem einen Compiler, das andere funktioniert mit dem anderen OS. Das hier funktioniert mit einer Version von der Sprache. Und der andere funktioniert nur, wenn du einem bestimmtes Executable-Format hattest. Und das offensichtlich ist, nein, nicht selber schreiben. Ich habe Leute gehört, die gesagt haben, ich kenne mit dem Compiler und ich weiß, was ich tun muss und die versuchen da so Leroy Jenkins mäßig dran zu gehen. Und da machen das natürlich komplett falsch. Ich habe mal irgendwo gehört, ja, gut, mach mal einfach IO mit dem Buffer und dann, ja, gut. Und gut, dann machst du halt IO, ne, für ohne guten Grund. Also nicht selber machen, vermutlich kommt man irgendwie mit einer Lösung, die dumm aussieht, dann sieht man auch selber dumm aus. Und vor allem, ja, Kleid, deine Lösung macht jetzt für die eine Version von dem Compiler funktionieren, aber vermutlich wird das mit der nächsten Version von einem Compiler, der wieder ein bisschen schlauer ist, nicht mehr funktionieren. Also nicht selber oder wenn du selber machen willst, dann hör dir zumindest an, was ich so jetzt auf den nächsten 10 Folien sage. Halt dich dran. Okay, hier ist eine Lösung. Die erste Lösung ist dieses Lib C Funktion Expressive B0. Das ist nicht im Standard drinnen. Das wurde so im Mai 2015 von den OpenBSD Leuten entwickelt. Das könnte irgendwie damit zusammen mit Hartlid. Und was es halt tut, ist B0 aber es garantiert, dass es nicht wegoptimiert wird. Und jetzt ist es nicht mehr dein Problem, sondern jetzt ist es Lib C's Problem. Und weil die die Garantie gemacht haben und die müssen jetzt nicht drum kümmern, dass es auch so umgesetzt wird. Und ja, Lib C hat sich jetzt auch gedacht so, das ist eine primäre Idee, das machen wir auch. Das heißt, da ist es irgendwie anders. Dialypsy unterstützt das auch, aber aus X wird das halt noch nicht unterschätzt. Okay, wenn man sich nur um diese Plattformen kümmern will, dann ist das eine prima Lösung. In dem selben Rahmen, wenn man nur für Windows entwickelt, dann gibt es Security Zero Memory. Das ist quasi die Funktion, wo bei der Microsoft sagt, wir garantieren, dass das nicht wegoptimiert wird. Also einfach das zu ändern. Also das steht dann halt echt mit dem Standesrande, dass sie sicherstellen, dass diese Daten sofort überschrieben werden. Und da war Microsoft wirklich früh dran, das wird seit XP und Windows 2003 unterstützt. Und die API wird halt weiterhin unterstützt. Dann gibt es noch eine Funktion, die nennt sich Memset an das hohe Unterstrich S. Und auch die garantiert, dass sie nicht wegoptimiert wird. Das ist jetzt ne Standardfunktion seit C11, aber es ist halt leider eben optionalen NXK. Ja, wenn man sich hier anguckt, was da drin steht, dann steht da halt ja, Optionalerweiterung. Das heißt, man kann C11-Complaint sein, ohne Memset S tatsächlich anzuwenden. Ja, mit nem Fals und nein, mit nem Aber. Das hätte echt das Potenzial, eine prima portable Lösung zu sein, aber es ist dann halt leider doch nicht. Und dann gibt es natürlich noch irgendwie die offensichtlichen Lösungen. Wenn man irgendwas mit sensiblen Daten macht, dann macht man vermutlich irgendwo Krypto. Und wenn man eh schon den Krypto-Bibliothek benutzt, dann hat vermutlich deine Krypto-Bibliothek eh schon irgendwie Funktionen, um das zu tun. In OpenSSL gibt es OpenSSL Cleanse und garantiert halt, dass es nicht wegoptimiert wird. GNU TLS hat GNU TLS Memset mit denselben Garantien. LibSodium hat auch Sodium Mem Zero. Selbe Garantien. Bisher habe ich euch quasi Lösungen gegeben, hier habe ich euch quasi API Funktionen gegeben, die ihr verwenden könnt, dass ihr benutzt. Hier ist jetzt die Lösung, wenn ihr euch nicht auf die API verlassen könnt, aus irgendeinem Grund. Und das ist jetzt nicht direkt portable, aber die meisten Compiler haben irgendwie was in der, was equivalent ist. Und was diese Option macht, ist halt, sagt dem Compiler, dass das nicht die interne Implementierung von Memset benutzt soll, sondern die von LibC. Und ab dem Zeitpunkt weiß halt der Compiler nicht mehr, was die Funktion tatsächlich tut und kann da nicht mehr wegoptimieren. Ja, also dieser Option selber ist für GCC. Bis Klang 37 denkt sich Klang so, ja, okay, wir unterstützen das und dann lasst es aber einfach weg und ignorieren das. Und Memset wird immer noch wegoptimiert. Das ruiniert so ein bisschen dieses, benutzt das, weil es funktioniert, weil, ja, außerhalb, wenn man halt alte Versionen von Klang benutzt. Andere Funktionen können immer noch wegoptimiert werden, also wenn man irgendwie einen Follow-Part, der das Clearing macht, dann wird es immer noch wegoptimiert. Eine andere Lösung ist halt, einfach nicht optimieren. Das funktioniert. Klar, natürlich kriegt man die Garantie, dass Code nicht wegoptimiert wird, wenn man überhaupt nichts optimiert. Offensichtlich ist das... Also wenn man vor Defi-Source verwenden will, dann funktioniert das halt nicht, weil das O1 braucht, aber... Klar, und man muss irgendwie seine Bildumgebung ein bisschen ändern. Und auch das ist jetzt nicht wieder komplett portable, aber die meisten Compiler haben irgendwie eine Option, um dem Compiler zu sagen, dass er nicht optimieren soll. Aber das Problem ist, du willst halt, genau, es wird halt dann doch nicht optimiert und das will man nicht unbedingt in Production. Dazu gehöre ich es halt, einige Compiler unterstützen halt, dass man irgendwie lokal, dass man lokal auf bestimmten Scopes oder bestimmten Funktionen nur irgendwie sagen kann, dass man nicht optimieren soll und ihm sagen soll, hier mach mal nur 0 an der Stelle. Das sieht nicht so aus, als ob das eine übliche Funktion ist und das könnte so ein paar Seiteneffekte haben. Also über diese O-Switches hinaus, die der Compiler normalerweise macht. Also ich hab da mal ein bisschen rumgespielt und es scheint zu funktionieren, aber es scheint irgendwie nicht ein üblicher Weg zu sein, das zu tun. Ja, das ist halt Pragmas, die hängen ich vom Compiler ab. Eine andere Lösung sind, ist das schwache Symbole, sogenannte schwache Symbole zu benutzen. Wenn ihr Schwache Symbole kennt, kennt ihr Schwache Symbole? Also ein paar. Ich versuche euch kurz zu erzählen, was es ist. Das EL-Format spezifiziert sozusagen wie ein Ausfieberer Datei aussehen kann, wie solche Formaten ihn unterstützen sollen. Ein Teil des Formates ist Symbole für Funktionen, Variable und so fort definieren zu können. Generell sind Symbole starke Symbole, man kann aber Symbole auch als schwach markieren. Das bedeutet, dass so ein Symbole sich bei der Laufzeit verändern kann. Wenn man also etwa eine Funktion oder das Symbole einer Funktion als schwach definiert, dann kann das heißen, dass zur Compile Zeit Schwierigkeiten haben wird, sich zu überlegen, was das heißt. Denn es ist als schwach definiert worden. Das ist die genaue Lösung, die OpenBSD benutzt für Explicit B0. Was ich daran mag, hier ist die Commit Message von den OpenBSD-Leuten, sie sind da sehr pragmatisch. Die Lösung ist clever, aber irgendwie ist sie nicht nach und sicher. Es gibt immer noch Arten und Weisen, das zu stören, kaputt zu machen, insbesondere könnte der Compiler bei Laufzeit prüfen, was das Ding ist bei Laufzeit und das Dannwerk optimieren. Aber in der vorhersehbaren Zukunft glauben wir nicht, dass das passiert. Aber es ist möglich, dass das irgendwo mal passieren mag in der Zukunft. Ich mag die Beschreibung, sie ist halt nicht nach und sicher, könnte brechen, funktioniert aber erstmal. Die nächste Lösung sind Barrieren in Speicher. Wie viele Leute wissen, was eine Barriere im Speicher tut? Auch ungefähr so viele. Lass mich versuchen, das kurz zu erzählen. Und seid freundlich zu mir, ich werde es extrem übersimplifizieren. Es ist kein besonders einfaches Konzept. Sagen wir, wir haben einen Teil Code mit Variablen A und B und sagen A ist gleich etwas und B ist irgendwas, ohne dass sie was mehr zu tun haben. Das bedeutet, dass Compiler und Hardware kriegen die Erlaubnis, sie umzusortieren, weil es total okay ist, auch B zuerst zu setzen und dann erst A. Ist okay. Und der zweite Thread, der zweite Thread ist während B noch nicht null, ist Lauf. Und dann, wenn A gesetzt wird und B null ist, benutzt halt A. Das klingt sich natürlich an, das sollte funktionieren. Aber der Compiler auf der einen Seite, die Compiler Hardware auf der einen Seite, weiß nicht, dass es eine Relation zwischen A und B gibt. Und wenn es dann B setzt, bevor A gesetzt wird, passieren wirklich schlechte Dinge. Es gibt viele Sicherheitsbugs, die darauf basieren auf der Art Problem. Okay, Tiesan im Linux Kernel, die kommen aus dieser Art Box. Die Art und Weise, das zu reparieren, funktioniert mit Memory und Speicherbarrieren. Also wenn man sagt A ist gleich etwas, sagt er in der Mitte Memory Barrier und dann B gleich etwas. Das bedeutet, der Hardware und auch dem Compiler, was auch immer hier passiert, tauscht nicht über diese Barriere hinweg. Es gibt hier eine Korrelation, von der ich weiß, die du nicht sehen kannst. Ich hoffe, ihr habt es gut erklärt. Normalerweise muss man hierfür viel mehr erzählen. Aber ich hoffe, ich habe die Message irgendwie gut darüber gekriegt, um euch zu erzählen, was eine Barriere im Speicher ist. Das Coole an solchen Memory Barrieren ist, es sagt dem Compiler, ich weiß hier etwas, was du nicht weißt, fass es nicht an. Das funktioniert für Umsortieren, das funktioniert aber auch sehr gut, um etwas nicht wegoptimieren zu lassen. Die Idee ist also, Memory Barrier zu machen, dann sagen wir Memory Barrier und das sagt dem Compiler, schmeißt es nicht weg. Ich weiß, es hört sich kompliziert an. Aber es ist ziemlich clever. Aber das funktioniert wirklich gut. In und wird benutzt, N-Dite-Lip-C, G-Lip-C. N-Gen X hat gerade einen Fix eingebracht, wo sie explicit B-Zero benutzen hatten und haben auch den Infix eine Memory Barrier eingebracht. Also ein interessantes Konzept und es funktioniert. Das sind Lösungen, die bekannt sind, die funktionieren, getestet und von vielen bekannten Stöcken Software. Wenn ihr auskündigt in einer Umgebung seid, wo ihr all das nicht benutzen könnt, wo es nicht portabel genug ist und ihr braucht eine Lösung, die überall funktioniert, dann ist das Beste auf bekannte Konstruktionen in C zurückzufallen. In diesem Fall etwa auf das Volatile Keyword. Leute sagen in diesem Fall, ach, benutzt einfach Volatile und alles ist gut. Das Compiler sind aber sehr clever und irgendwie schwierig zu verstehen. Und wenn sie clever genug sind, können sie es im Prinzip immer noch wegoptimieren. Also ist die Volatile Solution ein guter Fallback, eine beste Bemühung, aber nicht vollständig. Eine Lösung davon ist der Volatile Pointerwrite, also schreiben auf den Pointer der Fallback von Lipsodium. Und hier sehen wir den Lipsodium Fallback. Man sagt dem Compiler, ich weiß hier was, was du nicht weißt. Es fühlt sich hier sehr sprachanwaltig an. Das sieht so als objekt something und noch was. Wenn der Compiler sich das hier ansieht und wenn er beweisen kann, dass wo immer PNT herkommt und es war tatsächlich nicht Volatile, dann bedeutet es Volatile nicht viel und dann optimiert es immer noch weg. Das hört sich sehr theoretisch an. Ich weiß nicht, ob es tatsächlich passiert. Aber ein paar Leute haben mir gesagt, dass das durchaus was ist, was passieren kann und dass dem Compiler erlaubt wäre, das zu tun. Die Tatsache, dass es ein Fallback ist, lässt mich auch glauben, dass es wahrscheinlich nicht passiert, aber könnte. Das ist die Lösung aus OpenSSL, die benutzt auch Volatile. Es erzeugt einen Volatile Funktionen-Pointer auf MAPSID mit Schwachmischwand-Symbolen. Um dem Compiler zu sagen, das könnte sich bei jeder Zeit verändern, ohne dass der Compiler davon weiß, das ist eine ziemlich gute Lösung. Aber ein Weg, um da theoretisch drumherum zu kommen, ist, wenn der Compiler Love-Zeit-Code sich ansieht, der bevor die Funktionen aufgerufen wird und sich dann ansieht, ist das MAPSID oder was anderes, was MAPSID ist, geben wir einfach das zurück und nehmen die optimierte Version davon und sparen ein paar Seikel. In Theorie könnte das passieren, ich weiß nicht, ob es passiert. Denkt über diese Lösungen also nach, als Fallback-Lösungen werden in Theorie vielleicht nicht funktionieren, in Realität wahrscheinlich schon. Das ist also die erste Hälfte der Präsentation und ich habe noch relativ viel Zeit, das ist cool. Es gibt also nicht diese eine portable Lösung, darum ist Speicherlöschen schwer. Wenn ihr also sucht nach einer Allrounder-Lösung, die überall funktioniert, dann ist es ziemlich schwer so eine gute Lösung zu finden. Das ist genau das, wo dann ein Kund innen auch mitzukommen und sagen, ich brauche das aber portabel und es muss das und das können. Die beste Lösung wäre also, benutzt alles, was ich gerade genannt habe, so gut ihr könnt. Aber wenn sich Lipsodium ansieht, ihr werdet auf der Folie einen Link finden und dann seht ihr, das hier ist die Tat, Lipsodium Memesyrio ist hervorragend geschrieben, irgendwie schön. Aber es hat dieses sehr elegante If-Deafness und If-Deafness was anderes und wenn das definiert ist, dann macht das. Und in diesem Setup macht diese Lösung und das hat das für sechs oder sieben Fälle, von denen ich gesprochen habe. Es ist hübsch, es ist elegant. Wenn ihr Inspirationen braucht, Leute dahin zeigen, ist sozusagen eine portable Lösung. Und nun haben wir über das Problem geredet, über ein paar Lösungen und jetzt möchte ich aber auch Erkennung. Ich möchte auch sehen, dass es passiert. Und ich möchte kommen und Palle beibringen, dass sie mir das sagen. Wie sie sagt mit GCC nicht, dass es das wegoptimiert hat. Wenn es Sicherheitskonsequenzen hat, dann sollte es mir das sagen, warum tut es das nicht? Also habe ich mich daran gesetzt und GCC modifiziert und kam mit diesem Patch. Und wenn es das Delete Call machen will, dann füge ich hin zu, bevor du Delete Call machst, wirft diese Message, sagt mir, wo es passiert und die Zeilen nummer. Und dann optimierst du immer noch weg. Was das bedeutet, ist, dass jedes Mal dann, wenn ein Memset auch so optimiert wird, dann sagt es mit GCC. Das ist interessant. Ich kriege also nicht nur Erkennung für meine eigenen Code, denn so kommt man auch sehr schnell, sehr einfach an Zero Day Exploits. Also habe ich reichlich bekannte Open Source Projekte heruntergeladen und durch meinen modifizierten Compiler gejagt von GCC und fand eine Liste von Dingen, also ich kenne diese spezielle Probleme, dass praktisch Open SSL, MRT-Curve, Heimdorck-Curve, Metric-SSL, NJXPHP, DHCP-Bind, SquidCash betrifft. Und die Liste ist länger. Ich habe auch Async und noch mehr. Wir wissen also, das Problem ist weit verbreitet. Und wenn Code hier drauf aufgebaut ist, hat das natürlich auch diese Probleme. Und natürlich ziti hier einfach nur Namen. Aber schaut hier mal einfach so ein Zero Day an. Das ist ein MRT-Curve. Dieses Memset wird wegoptimiert. Das ist ein PLP, das wird auch wegoptimiert. Das glaube ich ist MRT-Curve-SSL. Und dieser entschlossete Clartext wird wegoptimiert. Also das 0C-Setzen von diesem entschlosseten Clartext. Diese Crypto Extended Data wird wegoptimiert. Das hier ist das NJX. Dieses Passwort wird nicht genullt. Das ist das N-Bind DHCP. Auch Privatschlüsseldaten, die nicht weggenullt werden. Das hier ist auch Squid. Es versucht die Credentials zu klären, aber das wird wegoptimiert. Ich muss da ein bisschen mit Powerpoint spielen. Das hier ist ein Schlüssel, wird auch wegoptimieren. Hier in ourSync auch Credentials, die in der Datei geshtweigert sind. Und auch das 0-Setzen wird wieder rausoptimiert. Und alles, was ich dazu tun musste, war 15 Code in GCC hinzufügen. Und GCC hat mir all diese Bugs geworfen. Was auch schön war, dass man diese Daten aus GCC wiederkriegt haben war. Außer, dass es einem Bugs gegeben hat. Alles mal die Sachen gezeigt, die es rausoptimiert hat. Wo ich gedacht hätte, dass es die nicht rausoptimieren würde. Also klar, eine Variable, die kurz danach aus dem Song geht. Das ist eine Variante. Aber was sie auch rausgefunden hatte, war, es gibt dieses Muster, dass man was melloggt und dann erst mal mem-setted und dann weitermacht. Und in vielen Fällen wird das auch wegoptimiert. Die Idee ist, dass, wenn es nur auf dem Song geht, dann wird es auch wegoptimiert. Und dann wird es auch wegoptimiert. Und dann wird es auch wegoptimiert. Die Idee ist, dass es nur wegoptimiert wird, wenn der Compiler beweisen kann, dass alle Elemente in dem Struct oder in dem Feld direkt e-gefüllt werden. Ich bin mir sicher, ob das wirklich hundertprozentig wahr ist. Wir reden jetzt auch über so Sachen wie Structure Paddingen und INAMS und so was. Unions meine ich. Da bin ich mir nicht ganz sicher, wie genau das funktioniert. Ich habe das jetzt noch nicht wirklich weitergegraben. Ich habe das einfach gestern. Gestern kam dieser Patch. Also, ich bin mir sicher, wie viel Potenzial da wirklich ist. Also, da könnten noch ein paar Bugs rauskommen. Also, da muss man noch ein bisschen weiter forschen. Wenn man da Interesse hat, hat viel Spaß. Eine andere. Und ich habe halt oft gesehen, dass nichtsensative Daten dann gemem-settet und dann gefreet werden. Und klar, dieser Mem-set wird dann rausoptimiert. Da ist dieses Coding-Muster, wo man ersten Meloks dann Mem-set 0 und dann vor dem Free Mem-set 0. Nicht, weil sie irgendwie sensitiver Daten rauswerfen wollen, sondern weil sie die Garantie haben wollen, dass sie immer auf einem sauberen Zustand wieder aufsetzen. Und ja, wenn das jetzt irgendwie raus aufnimmt, dann halten diese Garantie natürlich nicht mehr. Und ja, Code, der hat sich darauf verlässt, dass man immer irgendwie mit einem sauberen Zustand anfängt, der funktioniert dann einfach nicht mehr. Dieses Muster funktioniert, glaube ich, einfach nicht gut mit Compile-Optimierung. Also, ich habe noch nicht alle Informationen darüber, aber es sah jetzt zumindest mal interessant aus. Die andere Sache, die mir aufgefallen war, war, in der Nähe von diesem Mem-set sind mir auch andere Bugs aufgefallen. Und dann habe ich halt vielleicht gedacht, so schlechter Codes, sieht anderen schlechten Code an. Das ist eine Sache, wie, das ist eine Sache, wie man mit dem Mem-Set, also erst den Free und dann Mem-Set oder so. This is the case of the Null-Diref, where basically a malloc happens, and then the Mem-Set... Hier ist Null-Diref, also erst guck mal, ob das Null ist, und dann... Obviously, that Mem-Set, if the value is Null, that Mem-Set would cause a Null-Diref. Also, wenn dieser Parameter tatsächlich schon Null ist, dann wäre das Mem-Set auch Null-Diref, also... Also, dieses Konstrukt ist einfach kaputt. So, now, I mostly spoke about Mem-Set, aber ich meine, da gibt es tausend verschiedene Variationen, von... Man kann einen Vorloop machen, man kann andere Abis benutzen, man kann versuchen, wie das irgendwie selber irgendwie zu machen, und dann gibt es ein C++-Nummer zu viele andere Möglichkeiten. Man könnte komische Klassen haben mit Konstruktoren und Vererbungen und virtuellen, ja, was auch immer. Also, es wird auf jeden Fall verrückt. Es sieht alles anders aus, aber... es hat alles irgendwie... das tut alles dasselbe, es hat alles dieselben Probleme. Und wenn man nur von der Code-Perspektive darauf guckt, dann ist halt manchmal der Optimierer schlau genug, und manchmal ist das halt nicht. Also, wenn man sich jetzt Code anguckt und dann macht irgendwie so eine Einschätzung, was die Sicherheit angeht, sollte man irgendwie den Buckets weiterleiten oder nicht, also im Zerhaltsfall denke ich mal, sollte man tun. Selbst wenn es nicht jetzt optimiert wird, dann könnte es halt in der Zukunft wegoptimiert werden. Okay. Jetzt habe ich bis jetzt eigentlich immer über C geredet. Was ist, wenn man nicht über C schreibt? Was ist, wenn man andere Sprachen nutzt? Vielleicht keine nativen Sprachen. Ja, was ist was mit Go Rust, Objective C, C-Sharp, Java, whatever. Ich wollte jetzt da irgendwie mehr darüber reden, aber jetzt habe ich nur eine einzige Folie darüber. Also in C-Sharp gibt es so etwas wie Secure String, was so ein String halt in einer sicheren Art und Weise aufbewahren soll. Und das Problem ist nicht die Implementierung, sondern die Frage ist, wie kriegt man eigentlich etwas sicherein und wie heraus und so ein Secure String? Und in Java gibt es irgendwie den, die Empfehlung nicht String zu benutzen, sondern schon dessen Bidereise zu verwenden. Ich weiß nicht mehr, was es war, es gab irgendwie Grund dafür. Die Idee ist, die meisten, die meisten gemanagten Sprachen bieten einem bis keine Möglichkeit an, das tatsächlich zu tun. Also man kann da irgendwie nicht im Speicher mit sensitiven Sensibilitäten agieren, ohne dass es irgendwie liegt. Es ist besonders, wenn man irgendwie mit Garbage Collection arbeitet und irgendwann wird man irgendwas eingesammelt und man weiß nicht genau, wann und wo und auf einmal hat man fünf Kopien von seinem Private Key, die irgendwo im Speicher rumliegen. Das sind einfach Sprachen, die nicht jeder Struktur haben, um insensiblen Daten umzugehen. In einigen Orten versucht dann irgendwie, die Leute versuchen irgendwie ihre eigenen Sachen daran zu schmieden, mit unterschiedlichen Graden von Erfolg. Und es gab irgendwie, ich habe mal etwas gesehen, mit vier oder fünf Versionen, weil jedes Mal etwas kaputt damit war. Ich würde sagen, es ist schon eher traurig, was da abgeht. Die meisten Leute haben es nicht versucht und diejenigen, die es versucht haben, haben es nicht genug versucht. Also jetzt, wo ich durch diese Verwandtenprobleme durchgegangen bin, wie man Speicher reinigt, möchte ich über ein paar Verwandte Probleme reden. Zuerst mal, was ich zuerst gesagt habe, ist, es ist nicht so, als ob Leute hingehen und sagen, okay, ich sollte jetzt diesen Speicher reinigen, weil da sind sensible Daten drin. Das Problem ist halt, manche Leute versuchen es nicht mal. Das heißt, sie setzen den Key, es ist nicht mehr im Scope und wird nie wieder angesehen. Es wird also häufig schnell überschrieben, aber es kann auch schiefgehen und dann sehr lange im Speicher bleiben. Das Problem ist, es ist schwer zu finden. Das heißt, man sucht irgendwie nach dem Fehlen von etwas. Das Einzige, also wie man diese Fehler finden kann, ist, oh, hier ist was sensibles. Oh, und hier hat sich keiner darum gekümmert, es wieder zu löschen. Und das zweite Problem ist irgendwie süß. Also, wenn man sich Memset so anseht, wie es hier gemacht wird, das ist falsch. Die Länge, und womit man es setzen will, sollten hier transponiert sein. Das Null sollte an zweiter Stelle stehen, und das Lenkt auf der dritten. Also, in diesem Fall, würde Zero die Null im dritten einfach sagen, okay, das wird benutzt als Pattern, also wird nichts in Memory geschrieben. GCC hat eine Warnung dafür. Und was ich also schon wollte, ist mir den Code, den ich mir vorher angesehen hatte, nochmal ansehen mit dieser Warnung. Und diese Liste von Bugs finden, aber ich hatte nicht genug Zeit dafür. Aber die Chance ist, wenn ihr diese Warnung anschaltet und durch die Open Source Code geht, werdet ihr viel davon wenden. So, eine andere Liste von Verwandten Bugs ist, Verwandt im Sinne, dass Optimierung mit Drenn war. Mit dem Fall, dass ohne Optimierung der Sicherheitsbug entweder gar nicht passiert wäre oder weniger schlimm gewesen wäre. Da gibt es so drei relevante Fälle, meiner Meinung nach. Der erste, über den ich sprechen möchte, ist Pointer Overflow. Sagen wir, wir haben diesen Code. Wir haben ein Pointer und eine Länge, von der wir nicht wissen, ob wir das vertrauen können. Wir müssen also eigentlich sicherstellen, dass Pointer plus Länge ist. Wenn also etwa in diesem Fall Pointer plus Länge kleiner als Pointer wäre, ist eine Overflow passiert und wir wollen raus. Aber Pointer Overflow sind immer undefiniertes Verhalten. Also seht ihr, ob die Optimierung dieses undefiniertes Verhältnissen ist? Ja. Die Optimierung dieses undefiniertes Verhältnissen und der Kompaler wirft das einfach durch die Optimierung raus, weil es ja undefiniert ist. Und wenn man jetzt, also sobald man weiß, dass es so ist, sieht man es überall, sonst ist es eher unauffällig. Man muss hier also einen Typ nehmen, den man auf der anderen Seite sieht. Man sieht, dass die Optimierung dieses undefiniertes Verhältnissen ist. Man muss hier also einen Typ nehmen, der zu groß ist. Und dann kann der Kompaler das nicht mehr rausoptimieren. Das Zweite ist ein bisschen subtiler. Switchcase Optimierung. Wenn man einen Switch in C hat und den nach Assembly übersetzt, macht man einfach eine 1 zu 1 Übersetzung. Aber wenn man den Optimierer benutzt, werden, wird eines von verschiedenen Dingen passieren, etwa ein binärer Baum, der benutzt wird im Microsoft-Kompiler. In GCC oder Clang wird eine Jump-Tabelle geschrieben. Das bedeutet, sie sehen sich den Wert an, also das Wertepaar. Es wird verglichen mit einer Zahl. Und die wird als Offset in diesem Jump-Tabel eingetragen. Das ist normalerweise kein Problem. Es ist eine Abstraktion. Und meistens muss man sich nicht darum kümmern. Aber wenn es so eine geteilte Speichervertrauensschwelle gibt, kann das ein Problem sein, nämlich weil der C-Code dann ... Also dann haben wir hier hypervisor Trustboundaries, sehr, sehr starke solche Vertrauensschwellen. Wenn man sich jetzt ... diesen Blog-Post hinter diesem Link ansieht, dann kann man eine Privilegieneskalation in einer Virtualbox-Gust-Post Situation machen. Der Bug ist, wenn man das das erste Mal fetcht, das erste Mal holt, ist alles gut. Wenn man das das zweite Mal macht, passiert was im Jump-Tabel. Und der eine und die andere Person kann was verändern im Jump-Tabel und plötzlich springen wir vom Jump weg und haben einen willkürlichen Jump erzeugt. Das ist offensichtlich schlecht. Ja, aufs Dritte werde ich mich nicht beziehen. Ich muss es jetzt zusammenfassen. Ich habe noch drei Folien, dann können wir uns auf die Fragen stürzen. Das hier ist wichtig. Wir kennen jetzt das Problem, wir kennen eine Verlösung. Wir wissen, dass es ein Real-Welt-Problem ist, dass es das gibt. Aber wir haben es irgendwie verstanden. Aber ich habe euch irgendwie hier was vorgelogen. Es ist nicht das ganze Problem. Wenn es das ganze Problem wäre, dann wäre es ja schön. Aber wir wissen, dass ihr ja zu fixen, sozusagen. Der Compiler-Optimierung ist einfach wirklich, wirklich clever. Und man hat wirklich viele cleverere Sachen. Viele kleine subtile Sachen, sehr architekturspezifisch. Und hier ist so eine Liste von Sachen, die passieren können. Der Optimierer wird zu Sachen tun wie, oh, du benutzt diesen String auf diese Art und Weise. Weißt du, was ich damit tun werde, ich tue es im Register und dann wird es viel schneller. Dann tue ich genug Register und dann tue ich das ab in Stack. Und dann machen wir von da weiter. Und was dann passiert ist, es wurde gelegt in die Register und dann gelegt in den Stack. Und das passiert häufig und viel. Solche Secrets werden also gelegt. Solche Geheimnisse werden gelegt wegen Optimierung. Selbst wenn man sich wirklich hart bemüht. Und damit, dass wir wirklich heftig versuchen, das Richtige zu tun, wirft einem der Compiler wieder starker zwischen die Beine. The Security Guy, FreeBSD, now has, I think, a cloud storage company. Very, very smart Security Guy. FreeBSD Einschreiber davon hat jetzt so eine Komponente, der hat ein Blog-Post darüber geschrieben. Und es wird als Notiz in der Linux-Man-Page von Expecibisero wiedergegeben. Und die sagt, ja, das ist wirklich ein fundamentales Problem. Wir empfehlen immer noch Expecibisero zu benutzen. Aber unsere Hoffnungen und Gedanken sind, dass in der Zukunft wir Compilers sagen können, bitte macht das nicht und macht einfach das Nächste. Aber momentan gibt es keinen guten Fixes dafür. Das ist das erste Statement, was ich für die Gegenwart darüber sage. Also das Schließungsstatement ist, Optimierung und Kryptografie passieren nicht zusammen. Sie schließen sich gegenseitig aus. Bevor ich zu meiner Schlussfrage komme, möchte ich noch ein bisschen schimpfen über Optimierung. Als ob ich es nicht schon getan hätte. Optimierung ist super. Es gibt einem alle möglichen Sachen. Aber ich habe ein großes Problem mit Optimierung. Wenn man eine Entwicklerin ist und über seinen Code nachdenken will, dann weiß man, was seinen Code tut. Und dann kompliziert man es und dann wird es optimiert. Und dann kann man darüber nicht mehr Beweise führen, nicht mehr drüber nachdenken, weil man nicht weiß, was der Optimierer tut. Was ich also möchte, ist, dass wenn, bevor Leute dieses Stich O benutzen, dass sie darüber nachdenken. Viele Expertinnen tun das, aber seid nicht blasiert darüber. Bevor ihr Stich O benutzt, denkt drüber nach, was das bedeutet. Es wird alle möglichen Dinge, über die ihr nicht so richtig sicher war, einführen. Es wird plötzlich Bedeutung von Sachen verändern. Was ich eigentlich wirklich will, von den Compilerleuten und den Sprachleuten, ist Kontrolle und Hauptbarkeit für diese Optimizer. Bevor ihr irgendwas tut, gibt mir eine detaillierte Liste von all dem, was wir jetzt als nächstes tun würden, um zu optimieren. Was ich jetzt wieder ansehen kann, meinen Code ansehen kann. Und jetzt wieder sagen kann, okay, was macht denn jetzt eigentlich die binäre Interpretation von meinem Code? Weil über den Benärcode nachzudenken halt nicht einfach ist. Was ich also möchte, ist eine viel feingranulare Kontrolle über die Optimierung. Oder etwa so lokalisierte Optimierungskontrollen, wie Microsoft Visual C. Oder etwa in diesem Scope optimiere nicht. Oder in diesem Scope optimiere nicht auf diese Art und Weise. So etwas wäre sehr schön. Das hier überspringen wir einfach. Und hier sind da mal die Schlussvergaue. Wir kennen das originäre Problem. Ich habe euch ein paar Lösungen präsentiert. Im Rückblick sind es halt nur parzielle Lösungen, und ich habe auch einen Aufruf dazu, was ihr tun sollt, was wir tun sollen. Das Problem, wie ich gezeigt habe, ist überall in Open Source. Also, hätte ich gerne, dass er einerseits diesen Patch, den ich in GCC gezeigt habe, benutzt, um Bugs zu finden und noch besser, um sie zu fixen. Und Kopeile sollten auch mehr tun. Sich um die Haftbarkeit von dieser Optimierung und die Kontrolle kümmern. Momentan tun sie das, haben wir so ein paar Flags, aber nicht besonders viel Kontrolle und nicht genug klare Kontrolle. Keine Transparenz. Wir wissen einfach nicht, was passiert. Und bei so einer Dampfunktion, es ist wie eine Nadel im Heuhauffen. Wir wollen eigentlich was, was einfach zu lesen ist, einfach zu pausen ist, um dann einfach durch alle solche Programmsschritte durchzugehen. Und ich hätte gerne, dass die Sprachleute auch involviert sind, um es durchzustandetisieren, wie das Portabel geht. Und zuletzt, aus meiner Nordsee, nicht C, Rubrik. Was ist mit Ruby, was ist mit Python, Perl, PHP, JavaScript, Go. Es riecht schlecht, es sieht schlecht aus. Insbesondere zur Laufzeit. Das ist sicher für mehrere Präsentationen wertes Material. Ich wünschte, ich hätte euch hier mehr zeigen können. Das ist alles. Ich hoffe, es hat euch gefallen.