In Bezug auf den Heartbleed-Bug schrieb Bruce Schneier in seinem Crypto-Gram vom 15. April: "Catastrophic" ist das richtige Wort. Auf der Skala von 1 bis 10 ist dies eine 11. ' Ich habe vor einigen Jahren gelesen, dass ein Kernel eines bestimmten Betriebssystems mit einem modernen Programmüberprüfungssystem rigoros überprüft wurde. Könnten daher durch die Anwendung von Programmverifikationstechniken heute verhindert werden, dass Fehler des Genres Heartbleed auftreten, oder ist dies noch unrealistisch oder sogar grundsätzlich unmöglich?
cryptography
correctness-proof
security
software-verification
program-correctness
Mok-Kong Shen
quelle
quelle
Antworten:
Um Ihre Frage so präzise wie möglich zu beantworten: Ja, dieser Fehler wurde möglicherweise von formalen Überprüfungstools behoben. In der Tat ist die Eigenschaft "niemals einen Block senden, der größer als die Größe des gesendeten Hearbeats ist" in den meisten Spezifikationssprachen (z. B. LTL) relativ einfach zu formalisieren.
Das Problem (was eine häufige Kritik an formalen Methoden ist) ist, dass die von Ihnen verwendeten Spezifikationen von Menschen geschrieben wurden. In der Tat verlagern formale Methoden die Herausforderung bei der Fehlersuche nur von der Suche nach Fehlern zur Definition der Fehler. Dies ist eine schwierige Aufgabe.
Außerdem ist die formelle Überprüfung von Software aufgrund des Problems der Staatsexplosion notorisch schwierig. In diesem Fall ist es besonders relevant, da wir oft Grenzen abstrahieren, um die Staatsexplosion zu vermeiden. Wenn wir beispielsweise sagen möchten, dass auf jede Anfrage innerhalb von 100000 Schritten eine Bewilligung folgt, benötigen wir eine sehr lange Formel. Daher abstrahieren wir sie in die Formel "Auf jede Anfrage folgt schließlich eine Bewilligung".
Im Fall von Heartbleed hätte die fragliche Grenze sogar beim Versuch, die Anforderungen zu formalisieren, abstrahiert werden können, was zu demselben Verhalten geführt hätte.
Zusammenfassend lässt sich sagen, dass dieser Fehler möglicherweise mit formalen Methoden vermieden werden könnte, aber es hätte einen Menschen geben müssen, der diese Eigenschaft im Voraus spezifiziert.
quelle
Kommerzielle Programmprüfer wie Klocwork oder Coverity konnten Heartbleed möglicherweise finden, da es sich um einen relativ einfachen Fehler handelt, bei dem vergessen wurde, einen Grenzprüfungsfehler durchzuführen. Dies ist eines der Hauptprobleme, auf die sie prüfen sollen. Es gibt jedoch einen viel einfacheren Weg: Verwenden Sie undurchsichtige abstrakte Datentypen, die gut getestet wurden, um frei von Pufferüberläufen zu sein.
Für die C-Programmierung stehen eine Reihe von abstrakten Datentypen mit "sicheren Zeichenfolgen" zur Verfügung. Das, mit dem ich am besten vertraut bin, ist Vstr . Der Autor James Antill hat eine großartige Diskussion darüber, warum Sie einen abstrakten String-Datentyp mit eigenen Konstruktoren / Factory-Methoden sowie eine Liste anderer abstrakter String-Datentypen für C benötigen .
quelle
Wenn Sie als " Programmüberprüfungstechnik " die Kombination aus Laufzeitüberprüfung und Fuzzing zählen, könnte dieser spezielle Fehler aufgetreten sein .
Richtiges Fuzzing führt dazu, dass der jetzt berüchtigte
memcpy(bp, pl, payload);
über die Grenze des Speicherblocks liest, zu dem erpl
gehört. Die gebundene Laufzeitprüfung kann im Prinzip jeden solchen Zugriff abfangen, und in der Praxis hätte in diesem speziellen Fall sogar eine Debug-Versionmalloc
, die die Parameter überprüftmemcpy
, die Aufgabe erledigt (hätte hier keine Notwendigkeit, sich mit der MMU herumzuschlagen). . Das Problem ist, dass die Durchführung von Fuzzing-Tests für jede Art von Netzwerkpaket mühsam ist.quelle
memcpy
, die wahre Grenze der ursprünglich vom System angeforderten (großen) Region zu erreichenmalloc
.memcpy(bp, pl, payload)
die Grenzen des OpenSSL-malloc
Ersatzes überprüfen müssen , nicht das Systemmalloc
. Dies schließt eine automatisierte Überprüfung der Grenzen auf binärer Ebene aus (zumindest ohne tiefes Wissen über denmalloc
Ersatz). Es muss eine Neukompilierung mit Assistenten auf Quellenebene erfolgen, wobei z. B. C-Makros verwendet werden, die das Token ersetzen,malloc
oder welche OpenSSL auch immer verwendet wird. und es scheint, dass wir dasselbe brauchen,memcpy
außer mit sehr cleveren MMU-Tricks.Die Verwendung einer strafferen Sprache verschiebt nicht nur die Zielpfosten von der korrekten Implementierung zur richtigen Spezifikation. Es ist schwer, etwas zu machen, das sehr falsch und doch logisch konsistent ist. Deshalb fangen Compiler so viele Fehler ab.
Zeigerarithmetik, wie sie normalerweise formuliert wird, ist nicht stichhaltig, da das Typsystem nicht wirklich bedeutet, was es bedeuten soll. Sie können dieses Problem vollständig vermeiden, indem Sie in einer Sprache arbeiten, in der Müll gesammelt wird (der normale Ansatz, bei dem Sie auch für die Abstraktion bezahlen). Oder Sie können viel genauer festlegen, welche Arten von Zeigern Sie verwenden, sodass der Compiler alles ablehnen kann, was inkonsistent ist oder einfach nicht als richtig erwiesen werden kann. Dies ist der Ansatz einiger Sprachen wie Rust.
Konstruierte Typen sind gleichbedeutend mit Beweisen. Wenn Sie also ein Typensystem schreiben, das dies vergisst, gehen alle möglichen Dinge schief. Nehmen wir für eine Weile an, wenn wir einen Typ deklarieren, meinen wir tatsächlich, dass wir die Wahrheit über das, was in der Variablen enthalten ist, behaupten.
In dieser Welt können Zeiger nicht null sein. NullPointer-Dereferenzen existieren nicht und Zeiger müssen nirgendwo auf Null geprüft werden. Stattdessen ist ein "nullable int *" ein anderer Typ, dessen Wert entweder auf null oder auf einen Zeiger extrahiert werden kann. Dies bedeutet, dass Sie an dem Punkt, an dem die Nicht-Null- Annahme beginnt, entweder Ihre Ausnahme protokollieren oder einen Null-Zweig durchlaufen.
In dieser Welt gibt es auch keine Array-Out-of-Bound-Fehler. Wenn der Compiler nicht beweisen kann, dass er in Grenzen liegt, versuchen Sie, ihn neu zu schreiben, damit der Compiler dies beweisen kann. Wenn dies nicht möglich ist, müssen Sie die Annahme an dieser Stelle manuell eingeben. Der Compiler kann später einen Widerspruch dazu finden.
Wenn Sie keinen Zeiger haben können, der nicht initialisiert ist, haben Sie auch keine Zeiger auf den nicht initialisierten Speicher. Wenn Sie einen Zeiger auf freigegebenen Speicher haben, sollte dieser vom Compiler abgelehnt werden. In Rust gibt es verschiedene Zeigertypen, um diese Art von Beweisen vernünftig zu erwarten. Es gibt ausschließlich eigene Zeiger (dh keine Aliase), Zeiger auf tief unveränderliche Strukturen. Der Standardspeichertyp ist unveränderlich usw.
Es gibt auch das Problem, eine tatsächlich genau definierte Grammatik für Protokolle (einschließlich Schnittstellenelementen) durchzusetzen, um die Eingabeoberfläche auf genau das zu beschränken, was erwartet wird. Die Sache mit "Korrektheit" ist: 1) Alle undefinierten Zustände loswerden 2) Logische Konsistenz sicherstellen . Die Schwierigkeit, dorthin zu gelangen, hat viel mit der Verwendung extrem schlechter Werkzeuge zu tun (unter dem Gesichtspunkt der Korrektheit).
Dies ist genau der Grund, warum die beiden schlechtesten Praktiken globale Variablen und Gotos sind. Diese Dinge verhindern, dass vor / nach / invariante Bedingungen um irgendetwas gelegt werden. Es ist auch der Grund, warum Typen so effektiv sind. Wenn Typen stärker werden (wobei letztendlich abhängige Typen verwendet werden, um den tatsächlichen Wert zu berücksichtigen), nähern sie sich als konstruktive Korrektheitsbeweise an sich. Inkonsistente Programme können nicht kompiliert werden.
Denken Sie daran, dass es nicht nur um dumme Fehler geht. Es geht auch darum, die Codebasis vor cleveren Infiltratoren zu schützen. Es wird Fälle geben, in denen Sie eine Einreichung ohne einen überzeugenden maschinengenerierten Nachweis wichtiger Eigenschaften wie "folgt dem formal festgelegten Protokoll" ablehnen müssen.
quelle
Statische Analysewerkzeuge wie Coverity können tatsächlich HeartBleed und ähnliche Fehler finden. Vollständige Offenlegung: Ich war Mitbegründer von Coverity.
Lesen Sie meinen Blog-Beitrag über unsere Untersuchung von HeartBleed und wie unsere Analyse verbessert wurde, um dies zu erkennen:
http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html
quelle
Eine automatisierte / formale Softwareüberprüfung ist nützlich und kann in einigen Fällen hilfreich sein, aber wie andere bereits betont haben, handelt es sich nicht um eine Silberkugel. Man könnte darauf hinweisen, dass OpenSSL insofern anfällig ist, als es Open Source ist und dennoch kommerziell und branchenweit verwendet wird, weit verbreitet ist und vor der Veröffentlichung nicht unbedingt von Fachleuten begutachtet wird (man fragt sich, ob es überhaupt bezahlte Entwickler im Projekt gibt). Der Fehler wurde im Wesentlichen durch die Codeüberprüfung nach der Veröffentlichung entdeckt, und der Code wurde anscheinend vor der Veröffentlichung überprüft (beachten Sie, dass es wahrscheinlich keine Möglichkeit gibt, nachzuverfolgen, wer die interne Codeüberprüfung durchgeführt hat). Der "lehrbare Moment" mit Herzblut (unter anderem) ist im Grunde genommen eine bessere Codeüberprüfung, idealerweise vor der Veröffentlichung von hochsensiblem Code, möglicherweise besser nachverfolgt. Vielleicht wird OpenSSL jetzt einer genaueren Prüfung unterzogen.
mehr bkg von medien, die ihre herkunft beschreiben:
Heartbleed war ein Unfall: Entwickler gibt zu, Codierungsfehler verursacht zu haben und gibt zu, dass seine Wirkung "eindeutig schwerwiegend" ist. "Der deutsche Entwickler Dr. Robin Seggelmann gab zu, dass er den Code geschrieben hat. Er wurde dann von anderen Mitgliedern überprüft und zur OpenSSL-Software hinzugefügt."
Wie Codenomicon den Heartbleed Bug gefunden hat, der jetzt das Internet plagt "Der Chef der Sicherheitsfirma erklärt, wie der Fehler gefunden wurde, wie tief das Risiko geht und was die Leute jetzt dagegen tun können."
3 wichtige Lektionen, die Sie von Heartbleed lernen können "Die verheerende OpenSSL-Sicherheitsanfälligkeit zeigt, wie wichtig die Orchestrierung von Rechenzentren ist, wie wichtig es ist, ältere Versionen auszuführen, und wie wichtig es ist, dem OpenSSL-Projekt etwas zurückzugeben."
quelle