Sandbox ohne Netz: gVisor, runsc und die Kunst, einem Scanner das Internet wegzunehmen

5 Min Lesezeit Serie: malware #sandbox#gvisor#isolation#malware

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.

Problem

Linux-Container sind leichtgewichtig, weil sie sich mit dem Host einen Kernel teilen. Namespaces, Cgroups und Seccomp 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 beim Öffnen eines eingeschleusten Samples ist keine theoretische Möglichkeit, sondern ein als durchaus realistisch einzuplanendes Szenario.

Kurze Antwort

gVisor (Google, Open Source, Apache 2.0) schiebt eine zweite Kernel-Schicht zwischen den containerisierten Prozess und den Host-Kernel. Die Komponente runsc ersetzt den OCI-Runtime und startet den Container mit einem eigenen User-Space-Kernel (Sentry), der Syscalls abfängt und auf einem minimalen Subset echter Host-Syscalls abbildet. Ein Kernel-Exploit im Container trifft auf Sentry, nicht auf den Host-Kernel.

Kombiniert mit network_mode: none (der Container bekommt überhaupt keine Netzwerk-Schnittstelle) ergibt sich ein Scanner, der ausgeführt werden kann, ohne dass selbst ein erfolgreicher Breakout ins Internet exfiltrieren könnte.

Tiefgang

Was gVisor im Kern anders macht

Ein normaler Container macht einen Syscall über die Kernel-Schnittstelle des Hosts. gVisor hängt sich zwischen: der Anwendungs-Syscall wird per ptrace (oder per KVM) abgefangen und an Sentry weitergereicht, einen in Go geschriebenen User-Space-Kernel. 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:

  1. 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.
  2. 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 typische Bandbreite:

  • Rein CPU-gebundene Arbeitslasten (Hashing, Komprimierung, Krypto): 10 bis 20 Prozent Overhead.
  • Syscall-intensive Workloads (viele kleine File-I/O-Operationen, Socket-Verbindungen): 30 bis 60 Prozent Overhead.
  • Netzwerk-intensive Workloads: variabel, mit --network=host bzw. 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:

compose.yml
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:true

Die Kombination der Direktiven:

  • runtime: runsc wählt gVisor statt runc.
  • network_mode: none gibt dem Container keine Netzwerkschnittstelle, nicht einmal loopback.
  • read_only: true macht das Wurzel-Dateisystem lesend; Schreibpfade sind explizit per tmpfs zu gewähren.
  • tmpfs unter /work legt den Arbeitsbereich in den RAM, mit noexec gegen Tropfen-Ausführung.
  • cap_drop: [ALL] entfernt alle Linux-Capabilities.
  • no-new-privileges verhindert Privilege-Eskalation via setuid-Binaries.

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 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), der als einziger Teil der Pipeline Zugang zu einer expliziten Allowlist 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 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.

Wie Dernium hier hilft

Bei Dernium Scan und Dernium Clean laufen ihre 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 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. 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).