Ich habe einen Passwort-Manager in Ocaml geschrieben. Um es so sicher wie möglich zu machen, möchte ich eine Zeichenfolge (einen Verschlüsselungsschlüssel) so im Speicher speichern, dass sie überschrieben werden kann. Da Ocaml nach Wert besteht und es einen Müllsammler gibt, hat sich dies als schwierig erwiesen. Ich verschlüssele alle Puffer und Variablen, die ich kann, aber ich brauche noch einen "Sitzungsschlüssel", um dies zu tun. Um zu verhindern, dass dies von automatisierten Schlüsselsuchprogrammen erkannt oder in einen Swap-Vorgang eingefügt wird, wird es aus einer Menge zufälliger Daten in einem Puffer mit einem zufälligen Inkrement zusammengesetzt. Also wirklich, alles was ich brauche, ist eine einzelne Variable, die für den zusammengesetzten Schlüssel für ein paar Sekunden überschrieben werden kann, bevor sie in die Nocrypto-Bibliothek übertragen wird ... Würde ein Verweis dafür funktionieren?
Nach dieser cornell-Seite "Refs and Arrays" , refs sind veränderbar und funktionieren ähnlich wie Zeiger in C. Allerdings habe ich auch eine Stack-Overflow-Antwort gefunden, in der diskutiert wird, dass Ocaml refs , in der die Antwort erwähnt "sie wirken wie Zeiger auf neuen zugewiesenen Speicher". Bedeutet dies jedes Mal, dass es nur eine neue Sache im Speicher zuweist, anstatt das Zeug im Speicher zu mutieren? Wenn ja, könnte man eine Referenz nicht wirklich "überschreiben".
Andere mögliche Lösungen, auf die ich gestoßen bin, sind Bigarrays und "benutzerdefinierte Blöcke". Ich bin mir nicht ganz sicher, ob "benutzerdefinierte Blöcke" tatsächlich außerhalb des Bereichs der Garbage Collection zugewiesen werden oder nicht. Sie scheinen für den Zugriff auf externen C-Code verwendet zu werden. Werden sie vom Müllsammler kopiert? Könnten sie "überschrieben" werden? Es gibt auch diese Idee von "undurchsichtigen Bytes" und undurchsichtigen Objekten im Speicher. Es fällt mir ziemlich schwer, meinen Kopf darum zu wickeln, wie das alles zusammenpasst. Eine nützliche, aber verwirrende (für mich) Diskussion von benutzerdefinierten Blöcken im Speicher auf Stack-Überlauf ist hier: Werden benutzerdefinierte Blöcke jemals in den Speicher kopiert? Die Antwort besagt, dass sie verschoben werden können. Aber könnten sie überschrieben werden?
Die letzte mögliche Lösung ist, sie mit einem Cstruct zu speichern, wie es die Nocrypto-Bibliothek zu tun scheint. Sie diskutieren es in dieser GitHub-Ausgabe: Geheime Materiallöschung Der Fragesteller sagt:
"Zugegeben, das meiste Schlüsselmaterial ist Cstruct.t, ein Bigarray.Array1.t, das außerhalb des GC-Heaps"
zugewiesen ist
Ist das überhaupt richtig? Wenn dem so ist, kann ich keine Quelldatei finden, die das tatsächlich tut. Ich bin ziemlich neu in Ocaml und funktionale Programmierung im Allgemeinen. Wenn Sie neugierig sind, ist mein Programm hier auf GitHub: ocaml-pass
Sie dürfen keine geheimen Informationen im OCaml-Heap speichern. Daher dürfen Sie Ihr Geheimnis niemals in einen OCaml-Heap-zugewiesenen Wert kopieren, folglich können weder Byte noch Strings oder Arrays verwendet werden, auch nicht temporär.
OCaml-Werte werden einheitlich als markierte Maschinenwörter dargestellt. Das niedrigstwertige Bit eines Wortes wird als ein Tag verwendet, das zwischen Zeigern (Markierung = 0) und unmittelbaren Werten (Markierung = 1) unterscheidet. Daher hat ein Wert immer eine feste Größe und ist ein Zeiger oder ein unmittelbarer Wert.
Unmittelbare Werte speichern ihre Daten im höchstwertigen Teil des Wortes, das sind 31 Bits in 32-Bit-Systemen und 63 Bits in 64-Bit-Systemen. Zeiger speichern ihre Daten in Blöcken, die sich in einem sogenannten OCaml Heap befinden. Der OCaml Heap ist eine Gruppe von Blöcken, die vom Garbage Collector (GC) verwaltet werden. Ein Block ist ein Datenblock, dem ein Header vorangestellt ist. Der Header gibt die Größe der Daten und einige andere Metainformationen an, die vom GC verwendet werden. Block kann OCaml-Werte (Zeiger oder unmittelbare Werte) oder undurchsichtige Daten enthalten.
Zusammenfassend. Alle OCaml-Werte werden als Maschinenwörter dargestellt, die entweder Daten direkt im Wort speichern oder Zeiger auf Heap-allokierte Blöcke sind. Jeder Zeiger zeigt auf einen einzigen Block. Mehrere Zeiger können auf denselben Block zeigen. Solche Werte gelten als physikalisch gleichwertig. Einige Blöcke werden nicht durch Zeiger angezeigt. Solche Blöcke werden tot genannt und vom GC zurückgewonnen.
Der GC verwaltet Blöcke durch Zuweisen, Verschieben und Freigeben von Blöcken. Der GC selbst verwendet eine Arena, die entweder von dem C-Speicherzuordner (malloc) oder direkt von einem Kernel über den Memmap-Syscall (abhängig von einem bestimmten System und einer Laufzeit) erhalten wird.
Der GC ist generationsübergreifend. Dies bedeutet, dass Werte zuerst in einer speziellen Region eines Heapspeichers namens minor heap zugewiesen werden. Der Minor-Heap ist eine zusammenhängende Speicherregion fester Größe, die in der Laufzeit mit drei Zeigern dargestellt wird: der Zeiger beg
auf den Anfang des Minor-Heaps, ein Zeiger end
auf das Ende des Minor-Heaps und der Zeiger cur
an den Anfang des freien Bereichs des Minor Heaps. Wenn ein Block zugewiesen ist, wird cur
um die Größe des Blocks erhöht. Dann wird der Block mit Daten initialisiert. Wenn im Minor-Heap kein freier Speicherplatz mehr vorhanden ist (d. H., Dann ist end - cur
kleiner als die erforderliche Blockgröße), wird ein untergeordneter GC-Zyklus ausgelöst. Der GC analysiert alle im Minor Heap gespeicherten Blöcke und kopiert alle Blöcke, auf die mindestens ein Zeiger verweist, auf den Major Heap . Danach wird der Zeiger cur
auf beg
gesetzt.
Im Major Heap kann ein Block auch mehrmals während eines Prozesses namens compaction kopiert werden. Der Kompaktor kann versuchen, Blöcke in seiner Arena neu anzuordnen, um eine kompaktere Darstellung des Heap zu erreichen.
Da der OCaml GC ein beweglicher GC ist, kann er die Heap-allokierten Daten beliebig kopieren. Obwohl es moving genannt wird, kopiert es tatsächlich immer noch. Das heißt, wenn ein Block von dem Minor-Heap in den Haupt-Heap verschoben wird, wird er tatsächlich nur bit-kopiert und somit dupliziert. Das Block-Phantom in dem Minor-Heap kann für eine willkürliche Zeitdauer leben, bis es durch einen neu zugewiesenen Wert überschrieben wird. Wenn ein Objekt während der Komprimierung verschoben wird, wird es ebenfalls kopiert und kann während des Prozesses überschrieben werden. Und natürlich versteht es sich von selbst, dass ein Block, sobald er einmal tot ist, für eine beliebige Zeit auf einem Haufen überleben kann, bis er vom GC wiederverwendet wird.
Das bedeutet, dass, wenn ein Geheimnis im OCaml-Heap landet, es unberechenbar wird, da der GC es mehrfach willkürlich und unvorhersehbar replizieren kann. Daher können wir Geheimnisse nur in unmittelbaren Werten oder in Regionen speichern, die nicht vom GC kontrolliert werden. Wie bereits erwähnt, zeigen alle OCaml-Werte, die Zeiger sind, immer auf einen Block im OCaml-Heap. Ein Block kann Daten direkt enthalten oder er könnte einen Zeiger enthalten, der außerhalb des Speicher-Heaps zeigt. Die so genannten benutzerdefinierten Blöcke können ihre Informationen im OCaml-Heap speichern oder nicht und werden in einer bestimmten Darstellung jedes benutzerdefinierten Blocks gespeichert. Zum Beispiel stellt die Bigararray-Bibliothek benutzerdefinierte Blöcke bereit, die ihre Nutzdaten außerhalb des OCaml-Heaps speichern. Ein Bigarray ist also ein benutzerdefinierter Block, der zwei Felder hat: einen Zeiger und eine Größe. Es ist ein undurchsichtiger Block, d. H. Der GC wird diese beiden Werte niemals als OCaml-Werte behandeln und niemals weder der Größe noch dem Zeiger folgen. Die Daten, auf die ein Zeiger zeigt, befinden sich außerhalb des OCaml-Heaps und werden entweder von malloc
oder by memmap
zugewiesen (tatsächlich kann es sich um beliebige ganze Zahlen handeln und sogar um einen Stack oder um statische Daten. Das ist wirklich wichtig, solange wir Bigarrays nur als ptr,len
Paar behandeln.
Dies alles macht Bigarrays ideal zum Speichern von Geheimnissen. Wir können sicher sein, dass sie nicht vom GC verschoben werden, wir können sie überschreiben, um den Informationsverlust zu verhindern, sobald sie freigegeben sind.
Wir sollten vorsichtig sein und niemals zulassen, dass ein Geheimnis von unserem sicheren Ort in den OCaml-Heap kopiert wird. Das bedeutet, dass selbst wenn unser Hauptspeicher ein sicheres Bigarray ist, die Information immer noch undicht ist, wenn wir den Inhalt in eine OCaml-Zeichenkette kopieren. Wenn wir die Informationen zum ersten Mal in die OCaml-Zeichenfolge einlesen und dann in Bigarray kopieren, wird die Information immer noch undicht. Daher ist jede Schnittstelle, die OCaml-Heap-allocated-Werte verwendet, unsicher und sollte nicht verwendet werden. Zum Beispiel können wir OCaml-Kanäle nicht zum Lesen oder Schreiben von Geheimnissen verwenden (wir sollten auf Speicherzuordnung oder ungepufferte E / A-Funktionen zurückgreifen, die vom Unix-Modul bereitgestellt werden). Immer wenn Sie den Datentyp string
von einem Bigarray erhalten, werden Ihre Daten mit allen Konsequenzen kopiert.
Ich würde einen Wert vom Typ bytes
verwenden, im Wesentlichen ein veränderbares Array von Bytes:
Sie können mit Bytes.fill
überschreiben, nachdem Sie fertig sind.
Tags und Links garbage-collection memory-management ocaml