Sandbox ohne Netz: gVisor, runsc und die Kunst, einem Scanner das Internet wegzunehmen
Container teilen sich den Host-Kernel. Für eine Malware-Scan-Pipeline ist das zu wenig. Warum gVisor als User-Space-Kernel die richtige zweite Schicht ist und wie ein Scanner gänzlich ohne Netzwerk gebaut wird.
Inhalt dieses Beitrags
- Problem
- Kurze Antwort
- Tiefgang
- Was gVisor im Kern anders macht
- Performance-Charakteristik
- Einbindung in docker-compose
- Wann eine einzelne Egress-Insel sinnvoll ist
- Was gVisor nicht ersetzt
- Abgelehnte Alternativen und Mythen
- Was Sie jetzt tun sollten
- Wie Dernium hier hilft
- Verifikation
- Offene Punkte
- Häufige Fragen
- Was genau ist der Unterschied zwischen gVisor und einem normalen Container?
- Brauche ich gVisor für jede Anwendung?
- Wie verhindert network_mode none, dass Daten abfließen?
- Was kostet gVisor an Geschwindigkeit?
- Wie aktualisiere ich Virensignaturen, wenn die Scanner kein Internet haben?
Problem
Linux-Container (leichtgewichtige, voneinander abgeschottete Ausführungsumgebungen) sind leichtgewichtig, weil sie sich mit dem Host einen Kernel (den zentralen Betriebssystem-Kern) teilen. Namespaces (getrennte Sichtbarkeitsbereiche), Cgroups (Ressourcen-Grenzen) und Seccomp (ein Filter für Systemaufrufe) trennen die Sichtbarkeit, aber die Ausführungsumgebung bleibt derselbe Linux-Kernel. Ein Kernel-Exploit aus einem Container heraus (Dirty Pipe, Dirty Cred, und viele andere der letzten Jahre) bricht die Isolation und landet direkt auf dem Host.
Für die meisten Web-Anwendungen ist das hinnehmbares Restrisiko. Für einen Malware-Scanner ist es untragbar. Der Scanner öffnet per Definition Dateien, die potentiell darauf ausgelegt sind, genau solche Lücken auszunutzen. Ein Container-Breakout (das Ausbrechen aus dem Container auf den Host) beim Öffnen eines eingeschleusten Samples ist keine theoretische Möglichkeit, sondern ein als durchaus realistisch einzuplanendes Szenario.
Für wen ist das? Für alle, die wissen wollen, wie man verdächtige Dateien sicher isoliert untersucht.
Kurze Antwort
Zwei Maßnahmen zusammen ergeben einen Scanner, der gefährliche Dateien öffnen kann, ohne dass selbst ein erfolgreicher Ausbruch Schaden ins Internet tragen könnte:
- gVisor (Google, Open Source, Apache 2.0) schiebt eine zweite Kernel-Schicht zwischen den Container und den Host-Kernel. Ein Kernel-Exploit im Container trifft diese Zwischenschicht, nicht den echten Host-Kernel.
network_mode: nonegibt dem Container überhaupt keine Netzwerk-Schnittstelle, nicht einmal eine lokale. Ein ausgebrochener Schadcode findet keinen Weg nach draußen.- Ergänzt wird das durch read-only Root (nur lesbares Wurzel-Dateisystem), tmpfs mit
noexec(RAM-Arbeitsbereich ohne Ausführungsrecht),cap_drop: ALL(Entzug aller Sonderrechte) undno-new-privileges(keine nachträgliche Rechte-Eskalation).
Die technische Komponente runsc ersetzt dabei die normale OCI-Runtime (die Standard-Software, die Container startet) und betreibt den Container mit einem eigenen User-Space-Kernel namens Sentry, der Systemaufrufe abfängt und nur auf ein kleines Subset echter Host-Aufrufe abbildet.
Tiefgang
Was gVisor im Kern anders macht
Ein normaler Container macht einen Syscall (Systemaufruf, mit dem ein Programm Dienste des Kernels anfordert) über die Kernel-Schnittstelle des Hosts. gVisor hängt sich zwischen: der Anwendungs-Syscall wird per ptrace (oder per KVM, der Hardware-Virtualisierung) abgefangen und an Sentry weitergereicht, einen in Go geschriebenen User-Space-Kernel (ein Kernel, der als gewöhnliches Programm und nicht mit Host-Rechten läuft). Sentry implementiert Hunderte Linux-Syscalls selbst und ruft nur eine kleine Kernmenge an echte Host-Syscalls auf, um I/O und Speicher zu managen.
Das hat zwei Konsequenzen:
- Die Angriffsfläche gegen den Host-Kernel schrumpft drastisch. Der Container kann in Sentry so viel bohren wie er will, aber Sentry selbst ruft nur wenige, gut geprüfte Host-Syscalls auf.
- Nicht alle Syscalls sind in Sentry implementiert. Manche Anwendungen brechen beim Umstieg.
Datei-I/O läuft über einen zweiten Prozess namens Gofer, der als Dateizugriffs-Proxy für Sentry fungiert. Gofer hält die wenigen Dateihandles, Sentry bekommt nur die Daten.
Performance-Charakteristik
gVisor ist langsamer als runc (die übliche Container-Runtime). Die typische Bandbreite:
- Rein CPU-gebundene Arbeitslasten (Hashing, Komprimierung, Krypto): 10 bis 20 Prozent Overhead (Mehraufwand an Rechenzeit).
- Syscall-intensive Workloads (viele kleine File-I/O-Operationen, Socket-Verbindungen): 30 bis 60 Prozent Overhead.
- Netzwerk-intensive Workloads: variabel, mit
--network=hostbzw. geteiltem Network-Stack besser, mit eigenem Network-Stack schlechter.
Für einen Scanner, der pro Sample einige Sekunden bis Minuten an CPU-Arbeit verrichtet und nur gelegentlich I/O macht, ist der Overhead in der Praxis machbar. Der Zugewinn an Sicherheit wiegt die zusätzliche Wartezeit mehr als auf.
Einbindung in docker-compose
Ein Service unter runsc:
services:
scanner:
image: myscanner:latest
runtime: runsc
network_mode: none
read_only: true
tmpfs:
- /work:rw,size=512M,mode=0700,nodev,nosuid,noexec
cap_drop: [ALL]
security_opt:
- no-new-privileges:trueDie Kombination der Direktiven:
runtime: runscwählt gVisor statt runc.network_mode: nonegibt dem Container keine Netzwerkschnittstelle, nicht einmal loopback (die lokale Schleife, über die ein Rechner mit sich selbst spricht).read_only: truemacht das Wurzel-Dateisystem lesend; Schreibpfade sind explizit per tmpfs zu gewähren.tmpfs(ein Dateisystem im Arbeitsspeicher) unter/worklegt den Arbeitsbereich in den RAM, mitnoexecgegen Tropfen-Ausführung (das Ablegen und Starten eingeschleuster Schaddateien).cap_drop: [ALL]entfernt alle Linux-Capabilities (fein granulierte Sonderrechte).no-new-privilegesverhindert Privilege-Eskalation via setuid-Binaries (Programme, die mit erhöhten Rechten starten).
Ein Scanner mit dieser Konfiguration kann ein Sample öffnen, analysieren, Ergebnisse in tmpfs schreiben und per Pipe oder shared volume an den übergeordneten Worker übergeben. Exfiltrations-Pfade ins Internet (Wege, über die Daten heimlich abfließen könnten) existieren im Normalbetrieb nicht.
Wann eine einzelne Egress-Insel sinnvoll ist
Signaturen müssen aktualisiert werden. ClamAV-Feeds, SaneSecurity, YARA-Forge, all das braucht Internet-Zugang zu externen Quellen. Die Lösung ist ein eigener, deutlich kleinerer Container (ein Update-Daemon, ein im Hintergrund laufender Dienst), der als einziger Teil der Pipeline Zugang zu einer expliziten Allowlist (Positivliste erlaubter Ziele) hat: nur diese Domains, nur HTTPS, nichts sonst. Er schreibt heruntergeladene Signaturen in ein Volume, aus dem die Scanner read-only lesen.
Die Scanner selbst bleiben network_mode: none. Der Update-Container liegt mit seiner internen Signatur-Ablage auf einem separaten internen Netz und ist nach außen per Firewall-Regel auf genau die erlaubten Domains eingeschränkt.
Was gVisor nicht ersetzt
gVisor ist ein zweiter Verteidigungsring, kein Ersatz für Härtung im Inneren des Containers. Seccomp-Profile, minimal-privilegierte User-Accounts, read-only Root, Capabilities-Drop bleiben Pflicht. Die Schichten ergänzen sich: Seccomp engt ein, was der Container überhaupt anfragt; gVisor fängt ab, was doch durch die Maschen schlüpft.
Abgelehnte Alternativen und Mythen
"runc mit Seccomp reicht." Seccomp filtert Syscalls, lässt aber eine große Oberfläche gegen den Host-Kernel offen. Für gemeinsam genutzte Web-Services ausreichend, für Malware-Scanner zu schmal.
"Firecracker wäre besser, das ist eine richtige micro-VM." Firecracker (AWS Lambda, Fly.io) startet eine kleine KVM-VM (eine echte virtuelle Maschine mit Hardware-Trennung) pro Workload. Die Isolation ist stärker als gVisor (echter Hypervisor), der Setup-Aufwand aber merklich höher und die Start-Latenz pro Job größer. Für einen Scanner mit hohem Durchsatz kann gVisor das pragmatischere Maß sein.
"Kata Containers sind dasselbe." Kata startet ebenfalls eine leichte VM pro Container (über QEMU oder Cloud Hypervisor). Stärkere Isolation als gVisor, aber auch höherer Overhead. Im Umfeld von OCI-kompatiblen Setups eine valide Alternative.
"Volle Hypervisor-VMs pro Scan." Funktioniert, kostet aber Sekunden Startzeit pro Sample. Für niedrige Volumina akzeptabel, für Massen-Scans zu teuer.
"Nabla, sandboxing via unikernel." Forschungsprojekt, produktionsreife Werkzeuge fehlen.
Was Sie jetzt tun sollten
- Identifizieren Sie die wirklich gefährdeten Arbeitslasten: alles, was nicht vertrauenswürdige Dateien öffnet oder fremden Code ausführt. Nur dort lohnt der Mehraufwand von gVisor; für gewöhnliche Web-Services ist er meist unnötig.
- Testen Sie Ihre Arbeitslast unter gVisor, bevor Sie umstellen - mit
docker run --runtime=runsc-, weil nicht jeder Systemaufruf unterstützt wird und manche Programme brechen. - Kappen Sie das Netzwerk konsequent mit
network_mode: nonefür alles, das keinen Internet-Zugang braucht. Das ist die wirksamste Einzelmaßnahme gegen das Abfließen von Daten. - Härten Sie zusätzlich von innen: read-only Root, tmpfs mit
noexec,cap_drop: ALLundno-new-privileges. Diese Schichten kosten nichts und wirken unabhängig von gVisor. - Wenn ein Teil doch ins Internet muss (etwa für Signatur-Updates), isolieren Sie ihn in einen eigenen kleinen Container mit enger Domain-Allowlist statt das Netz für alle zu öffnen.
- Planen Sie den Update-Pfad für gVisor selbst ein: runsc hat eigene Schwachstellen und gehört regelmäßig aktualisiert.
Wie Dernium hier hilft
Bei Dernium Scan und Dernium Clean laufen unsere Engines und Konvertierer in einer wie hier skizzierten Konfiguration: gVisor-Runtime, network_mode: none, read-only Root, tmpfs-Arbeitsbereich mit noexec, cap_drop ALL, no-new-privileges. Signatur-Updates (ClamAV, yara-forge) laufen über einen getrennten Update-Daemon mit enger Domain-Allowlist; die Scanner selbst sehen nie das Internet. Dynamische Detonation in Firecracker-VMs (das kontrollierte Ausführen einer Datei, um ihr Verhalten zu beobachten) als Folgeschritt zu rein statischer Analyse ist ein bekanntes Muster, das wir derzeit nicht selbst betreiben - für akute Fälle verweisen wir auf VMRay oder Joe Security.
Verifikation
- gVisor-Projekt: gvisor.dev.
- USENIX ATC 2019 Paper: "The True Cost of Containing" (Young et al.), Analyse der gVisor-Architektur und -Performance.
- OCI-Runtime-Spezifikation: opencontainers.org.
- Testen:
docker run --runtime=runsc --network=none hello-world;docker inspect $(container) | jq '.[0].HostConfig.Runtime'. - Performance-Benchmarks: öffentlich von Google (eigenes Blog), Cloudflare (Einsatz in Workers), von externen Forschungsgruppen.
Offene Punkte
Syscall-Kompatibilität. Sentry implementiert die wichtigsten Linux-Syscalls, aber nicht alle. Manche Workloads (insbesondere solche mit unüblichen Systemfunktionen wie perf_event_open, kexec, io_uring) brechen unter gVisor. Testen, bevor eine Scan-Pipeline live geht.
Kernel-Update von Sentry. gVisor selbst ist Software mit eigenen CVEs (öffentlich erfassten Schwachstellen). Der Update-Pfad für runsc ist ebenso wichtig wie für den Host-Kernel.
Beobachtbarkeit. Standard-Tools wie strace, ftrace, perf verhalten sich in gVisor-Containern anders oder funktionieren nicht. Wer Scanner debuggen muss, braucht alternative Wege (strukturierte Logs, Metriken, Audit-Logs auf dem Host-Volume).
Häufige Fragen
Was genau ist der Unterschied zwischen gVisor und einem normalen Container?
Ein normaler Container teilt sich den echten Betriebssystem-Kern mit dem Host; eine Lücke in diesem Kern lässt sich aus dem Container heraus ausnutzen. gVisor schiebt einen eigenen, in Software nachgebauten Kernel dazwischen, der die allermeisten Systemaufrufe selbst beantwortet und nur sehr wenige, geprüfte Aufrufe an den echten Kern durchreicht. Das verkleinert die Angriffsfläche erheblich.
Brauche ich gVisor für jede Anwendung?
Nein. Für die allermeisten Web-Anwendungen ist der gemeinsame Kernel ein akzeptables Restrisiko, und der Mehraufwand von gVisor lohnt nicht. Sinnvoll ist gVisor dort, wo eine Anwendung gezielt nicht vertrauenswürdige Inhalte verarbeitet - etwa ein Datei-Scanner, ein Datei-Konverter oder eine Sandbox für fremden Code.
Wie verhindert network_mode none, dass Daten abfließen?
Der Container bekommt schlicht keine Netzwerk-Schnittstelle zugewiesen, nicht einmal die lokale Schleife. Selbst wenn Schadcode aus der Anwendung ausbräche, fände er keine Verbindung, über die er gestohlene Daten verschicken oder Befehle empfangen könnte. Die Daten kommen ausschließlich über kontrollierte Pfade wie geteilte Dateibereiche herein und hinaus.
Was kostet gVisor an Geschwindigkeit?
Das hängt stark von der Arbeitslast ab. Reine Rechenarbeit verliert grob 10 bis 20 Prozent, viele kleine Datei- oder Netzwerkoperationen können 30 bis 60 Prozent kosten. Für einen Scanner, der pro Datei mehrere Sekunden rechnet und nur selten I/O macht, fällt das in der Praxis kaum ins Gewicht.
Wie aktualisiere ich Virensignaturen, wenn die Scanner kein Internet haben?
Über einen getrennten, kleinen Hilfscontainer, der als einziger Teil der Anlage Internet-Zugang hat, und zwar nur zu einer fest definierten Liste erlaubter Domains über HTTPS. Dieser lädt die Signaturen herunter und legt sie in einem geteilten Bereich ab, aus dem die abgeschotteten Scanner sie nur lesen. So bleibt der gefährliche Teil ohne Netz.