Sub-Millisekunden-Sandboxes mit Copy-on-Write
Wie Copy-on-Write-Speicher-Forking VM-Sandboxes ermöglicht, die in unter einer Millisekunde starten, und was das für Serverless und Sicherheitsisolation bedeutet.

Den Start eines Docker-Containers dauert etwa 500 Millisekunden. Eine Firecracker-microVM braucht rund 125 Millisekunden, ein V8-Isolate etwa 5 Millisekunden. Doch eine neue Generation leichtgewichtiger Sandboxes, die Copy-on-Write-Speicher-Forking nutzt, kann eine isolierte Ausführungsumgebung in unter 1 Millisekunde hochfahren, oft im Bereich von 50 bis 200 Mikrosekunden. Das ist schnell genug, um für jeden einzelnen Funktionsaufruf eine frische Sandbox zu erstellen.
Das ist nicht nur eine inkrementelle Verbesserung. Es ist eine qualitative Veränderung dessen, was Sandboxing leisten kann. Kostet die Erstellung einer Sandbox 500 ms, erzeugt man sie sparsam und verwendet sie wieder. Kostet sie 50 μs, erzeugt man sie für jede nicht vertrauenswürdige Eingabe, jeden Plugin-Aufruf und jede Benutzeranfrage. Das Sicherheitsmodell verschiebt sich von „Mandanten isolieren“ zu „einzelne Operationen isolieren“.
Was Copy-on-Write eigentlich bedeutet
Copy-on-Write (CoW) ist eine Technik des Betriebssystems, bei der man eine „Kopie“ eines Speicherbereichs anlegt, ohne tatsächlich Daten zu kopieren. Original und Kopie zeigen auf dieselben physischen Speicherseiten, die als nur lesbar markiert sind. Sie sind nicht zu unterscheiden, beide sehen identische Daten. Kopiert wird erst, wenn einer von beiden in eine Seite schreiben will. Dann fängt der Kernel den Schreibzugriff ab, kopiert genau diese eine Seite und lässt den Schreibvorgang auf der Kopie weiterlaufen.
Der Systemaufruf fork() in Unix nutzt das seit den 1990er-Jahren. Beim Forken eines Prozesses bekommt das Kind eine vollständige Kopie des Speichers des Elternprozesses, aber dank CoW werden keine Daten tatsächlich kopiert. Ruft das Kind sofort exec() auf (was üblich ist), ersetzt es seinen Speicher komplett, und die CoW-Seiten werden einfach freigegeben. Das Forken war nahezu kostenlos.
// Classic fork() — copy-on-write in action
pid_t pid = fork();
// At this point:
// - Parent and child share ALL physical memory pages
// - Both have identical virtual address spaces
// - Pages are marked read-only in both
// - Zero data has been copied
if (pid == 0) {
// Child process
// Reading shared_data is free — same physical page
int value = shared_data[0];
// Writing triggers CoW — only THIS page gets copied
shared_data[0] = 42;
// Now child has its own copy of this one page
// Parent's page is unchanged
}
Vom Fork zur Sandbox
Die Grundidee von CoW-Sandboxes lautet: Statt eine frische VM oder einen Container von Grund auf zu booten, startet man eine vorbereitete „Vorlage“ (mit Laufzeitumgebung, Bibliotheken und initialem Zustand) und forkt sie dann per CoW zu sofortigen Kopien. Jede Kopie beginnt genau dort, wo die Vorlage aufgehört hat, vollständig initialisiert und bereit zur Ausführung, läuft aber in ihrem eigenen isolierten Speicherbereich.
Der Unterschied in der Performance ist enorm. Ein klassischer VM-Start umfasst: einen Kernel laden, die Hardware initialisieren, Dateisysteme einhängen, das Init-System starten, den Anwendungscode laden und die Laufzeitumgebung initialisieren. Selbst mit aggressiver Optimierung (Firecracker entschlackt dies deutlich) fallen dabei immer noch Hunderte Millisekunden Initialisierungsarbeit an.
CoW-Forking überspringt all das. Die Vorlage hat die Initialisierung bereits erledigt. Der Fork erzeugt eine vorinitialisierte Kopie in Mikrosekunden. Die „Startkosten“ sind nur die Verwaltungsarbeit des Kernels für einen neuen Adressraum und das Duplizieren der Seitentabelleneinträge, also einige tausend Operationen, unabhängig davon, wie viel Speicher die Vorlage nutzt.
Wo das das Spiel verändert
Serverless-Funktionen
Kaltstarts sind der Fluch des Serverless-Computings. AWS Lambda braucht für einen Kaltstart 100 bis 500 ms, bei JVM-basierten Laufzeitumgebungen noch mehr. Für latenzkritische Workloads ist das inakzeptabel. Dadurch werden Nutzer gezwungen, Instanzen warmzuhalten (was den Sinn von Serverless untergräbt) oder unvorhersehbare Latenzen in Kauf zu nehmen.
Mit CoW-Sandboxes sinken Kaltstarts auf unter eine Millisekunde. Jeder Aufruf kann ein „Kaltstart“ sein, denn Kaltstarts sind praktisch kostenlos. Man braucht keine Warm-Pools, verschwendet keinen Speicher durch untätige Instanzen und hat keinen veralteten Zustand zwischen Aufrufen. Jede Funktionsausführung bekommt eine unberührte, isolierte Umgebung, ohne die Initialisierungskosten zu zahlen.
Plugin- und Erweiterungssysteme
Nicht vertrauenswürdige Plugins sicher auszuführen gehört zu den schwierigsten Problemen der Softwareentwicklung. Browser haben das für JavaScript mit V8-Isolates gelöst. Bei beliebigem Code, also kompilierten Erweiterungen, Skriptsprachen und Binär-Plugins, waren die Isolationsoptionen dagegen begrenzt: Container (zu langsam für Isolation pro Anfrage) oder WebAssembly (begrenztes Ökosystem und eingeschränkte Sprachunterstützung).
CoW-Sandboxes bieten einen Mittelweg: beliebigen Code in einer isolierten Kopie der Host-Umgebung ausführen, mit Einrichtung und Abbau in unter einer Millisekunde. Das Plugin sieht eine vollständige OS-Umgebung (Dateisystem, Netzwerk, Bibliotheken), aber seine Änderungen bleiben eingeschlossen. Wenn die Sandbox beendet wird, verschwinden alle Änderungen. Das ist ideal für nutzergelieferten Code in CI-Systemen, Notebook-Umgebungen und Build-Tools.
Sicherheitsisolation
Bei der Verarbeitung nicht vertrauenswürdiger Eingaben, etwa beim Parsen einer hochgeladenen PDF, beim Rendern von nutzerbereitgestelltem HTML oder beim Ausführen einer Datenbankabfrage, begrenzt eine isolierte Sandbox den Schaden durch einen Exploit. Hat der PDF-Parser einen Buffer-Overflow, gewinnt der Angreifer die Kontrolle über eine Wegwerf-Sandbox, die ohnehin gleich zerstört wird, und nicht über den Anwendungsserver.
Dieser Ansatz, also Prozessisolation für jede nicht vertrauenswürdige Operation, war mit klassischem Sandboxing bisher unpraktisch, weil der Overhead die eigentliche Verarbeitungszeit überstieg. Wenn das Parsen einer PDF 10 ms dauert, ergibt es keinen Sinn, 500 ms in die Containererstellung zu stecken. Aber 100 μs für die CoW-Sandbox-Erstellung sind praktisch vernachlässigbar.
Die Implementierungsdetails
Ein praktisches CoW-Sandbox-System muss neben dem bloßen Aufruf von fork() mehrere Probleme lösen.
- Speicherbuchhaltung. CoW macht den Speicherverbrauch mehrdeutig. Nutzt eine Vorlage 1 GB und forkt man 100 Kopien, die jeweils 10 MB verändern, liegt der physische Speicherbedarf bei etwa 2 GB (1 GB geteilt + 100 × 10 MB eigene Seiten) und nicht bei 100 GB. Der Kernel unterscheidet geteilte und private Seiten, aber für einen genauen Verbrauch pro Sandbox muss man
/proc/[pid]/smapsauswerten. - Dateisystemisolation. CoW deckt den Speicher ab, Schreibzugriffe auf Dateien brauchen aber eine eigene Isolation. Overlay-Dateisysteme (overlayfs) bieten CoW-Semantik für Dateien: Die Sandbox sieht das Dateisystem der Vorlage, Schreibzugriffe landen aber in einer separaten Ebene. Beim Beenden der Sandbox wird das Overlay verworfen.
- Netzwerkisolation. Jede Sandbox braucht einen eigenen Network-Namespace, um Störungen zu vermeiden. Linux-Namespaces leisten das, doch ihre Erstellung verursacht messbaren Overhead. Manche Systeme nutzen einen Pool vorab erstellter Namespaces.
- Ressourcenlimits. Eine Sandbox, die unbegrenzt Speicher allokiert oder ungebremst CPU verbraucht, ist ein Vektor für Denial-of-Service. cgroups setzen Ressourcenlimits (Speicher, CPU, I/O), aber das Anlegen und Entfernen von cgroups kostet Zeit. Auch hier hilft Pooling.
- Deterministische Bereinigung. Beim Beenden einer Sandbox müssen alle ihre Ressourcen (Speicher, Dateideskriptoren, Netzwerkverbindungen, IPC-Objekte) zuverlässig freigegeben werden. PID-Namespaces helfen: Beendet man den Init-Prozess des Namespaces, werden alle Nachfahren mitbeendet.
CoW vs. WebAssembly-Sandboxes
WebAssembly (Wasm) ist die andere wichtige leichtgewichtige Sandboxing-Technologie. Der Vergleich lohnt sich, weil beide grundlegend unterschiedliche Abwägungen treffen.
Wasm-Sandboxes führen Code in einer speichersicheren virtuellen Maschine mit einem linearen Speichermodell aus. Die Sandbox kann nichts außerhalb ihres linearen Speichers erreichen: kein Dateisystem, kein Netzwerk, keine Systemaufrufe (außer sie werden explizit über WASI bereitgestellt). Das ist extrem sicher, aber einschränkend. Bestehender Code muss für Wasm neu kompiliert werden, und nicht alle Sprachen lassen sich sinnvoll nach Wasm übersetzen.
CoW-Sandboxes führen nativen Code in einer isolierten OS-Umgebung aus. Die Sandbox hat Zugriff auf eine vollständige OS-Schnittstelle (eventuell eingeschränkt durch seccomp-Filter), kann beliebige Binaries ausführen und nutzt normale Systembibliotheken. Das ist weniger restriktiv, aber auch weniger sicher. Die Isolationsgrenze ist das Prozessmodell des Betriebssystems, das eine größere Angriffsfläche hat als die minimale VM von Wasm.
Wähle Wasm, wenn du den ausgeführten Code kontrollierst, dein Workload sauber nach Wasm kompiliert und du die stärkstmögliche Isolation brauchst. Wähle CoW-Sandboxes, wenn du beliebige bestehende Binaries ausführen musst, dein Workload Betriebssystemfunktionen (Dateisystem, Netzwerk, Kindprozesse) benötigt und dir Kompatibilität wichtiger ist als eine minimale Angriffsfläche.
Die Falle: fork() in Multithread-Programmen
Beim fork() gibt es eine bekannte Falle: Es kopiert nur den aufrufenden Thread. Hat der Elternprozess 20 Threads, bekommt das Kind nur einen. Mutexe, die die anderen 19 Threads halten, sind im Speicher des Kindes weiterhin als belegt markiert, doch die haltenden Threads existieren nicht mehr. Das Kind verklemmt sich beim ersten Versuch, einen dieser Mutexe zu holen.
CoW-Sandbox-Systeme umgehen das, indem sie sicherstellen, dass der Vorlagenprozess im Moment des Forkens single-threaded ist. Typischerweise heißt das: Alles in der Vorlage initialisieren (Bibliotheken laden, Laufzeitumgebung einrichten, Anfangszustand vorbereiten), dann alle Threads außer dem Hauptthread stoppen, forken und jedes Kind bei Bedarf neue Threads starten lassen. Die Initialisierungskosten fallen einmal an, der Fork umgeht die Threading-Gefahr.
Einige neuere Ansätze verwenden userfaultfd oder eigene Page-Fault-Handler, um CoW-ähnliche Semantik ganz ohne fork() umzusetzen. Das vermeidet das Multithreading-Problem, bringt aber mehr Komplexität mit sich und erfordert engere Abstimmung auf Kernel-Ebene.
Worauf man achten sollte
Sub-Millisekunden-Sandboxing steckt noch in den Kinderschuhen, aber die Bausteine sind solide (fork, Namespaces, cgroups und overlayfs sind alle ausgereift). Die Systeme, die darauf aufbauen, für Serverless-Computing, CI/CD und sichere Codeausführung, zeigen, dass Isolation pro Operation im großen Maßstab praktikabel ist. Mit zunehmender Reife dieser Werkzeuge wird die Annahme, Sandboxing sei teuer, genauso veraltet sein wie die Annahme, Garbage Collection sei zu langsam für Echtzeitanwendungen. Der Overhead schwindet, und die Sicherheitsvorteile von „alles sandboxen“ lassen sich kaum mehr ignorieren.


