NetBSD Jails - Ein Überblick über Architektur und Integration

NetBSD Jails - Ein Überblick über Architektur und Integration
By Matthias Petermann / on 20.02.2026

Warum dieses Projekt?

Mit zunehmender Leistungsfähigkeit von Hardware wurden mehrere Dienste auf einer einzelnen Maschine konsolidiert - zunächst für einen Betreiber, später zunehmend auch für unterschiedliche Kunden („Tenants“).

Vollvirtualisierung (umgangssprachlich: VMs) war eine naheliegende Antwort auf das Problem der Isolation. Sie trennt Systeme zuverlässig – ist jedoch nicht in jeder Situation die leichtgewichtigste Lösung.

Viele Betriebssysteme ergänzten daher die Hypervisor-Ebene um Isolation direkt im Kernel:

  • FreeBSD mit den ursprünglichen Jails
  • Linux mit Namespaces und cgroups als Grundlage für Docker, Podman, Kubernetes und verwandte Systeme

NetBSD hingegen verfügt bislang über keine vergleichbare, integrierte Kernel-basierte Prozess-Domänentrennung – obwohl der Kernel mit seinem kauth/secmodel-Fundament die strukturellen Voraussetzungen dafür bietet.

Der hier vorgestellte Jail-Ansatz schließt diese Lücke: eine NetBSD-native, kohärente Erweiterung des bestehenden Sicherheitsmodells um isolierte Prozess-Domänen.

Was bisher geschah…

Im Artikel “Zwischen chroot und Xen: pragmatische Prozess-Isolation auf NetBSD“ wurde ein Prototyp vorgestellt:

  • secmodel_jail im Kernel
  • jailctl im Userland
  • jailmgr als operativer Rahmen

Dieser Prototyp zeigte, dass ein NetBSD-natives Jail-Modell technisch tragfähig ist.

Seitdem wurde das Konzept geschärft, erweitert und systematisch strukturiert. Parallel entstand eine konsolidierte Spezifikation, die Zuständigkeiten, Integrationspunkte und Grenzen explizit beschreibt.

Der vorliegende Artikel unternimmt den Versuch, diese Architektur nicht als Sammlung einzelner Funktionen darzustellen, sondern als kohärentes System mit klaren Ebenen und definierten Zuständigkeiten.

Er soll nachvollziehbar machen:

  • wie die Isolation im Kernel verankert ist
  • wie Ressourcen kontrolliert werden
  • wie der Lebenszyklus einer Jail definiert wird
  • und wie sich alles in ein konsistentes Betriebsmodell einfügt
🐞 Experimenteller Prototyp

Der hier beschriebene Jail-Ansatz befindet sich im experimentellen Stadium.

Die Implementierung ist funktional, jedoch weder sicherheitsauditiert noch breit im produktiven Einsatz erprobt.

Es handelt sich um einen technischen Entwurf mit Prototyp – nicht um ein validiertes oder produktionsreifes Subsystem.

⚠️ Subsystem-Integration und Review-Bedarf

Teile der Implementierung sowie strukturelle Entwurfsentscheidungen wurden durch KI-gestützte Analyse bestehender Kernel-Subsysteme unterstützt.

Die Integrationspunkte folgen meinem aktuellen Verständnis der jeweiligen NetBSD-Subsysteme. Es ist gut möglich, dass erfahrene Kernel-Entwickler idiomatischere oder strukturell passendere Integrationsmuster kennen.

Insbesondere Integrationspunkte in:

  • UVM (Speicherpfad)
  • kauth/secmodel
  • NPF

wurden funktional umgesetzt, jedoch nicht formal durch erfahrene Kernel-Entwickler reviewed.

Der Entwurf versteht sich daher ausdrücklich als review-bedürftiger technischer Vorschlag – nicht als stilistisch kanonisierte oder projektweit abgestimmte Lösung.

Fachliche Kritik, alternative Integrationsansätze und Hinweise auf subsystem-spezifische Konventionen sind ausdrücklich willkommen.

💡 Quellcode und aktueller Stand

Die vollständigen Quellen befinden sich in meinem Fork des NetBSD-Source-Trees.

Branch: feature/jails
Basis: netbsd-10

Repository: https://github.com/MatthiasPetermann/netbsd-src/tree/feature/jails


1. Das Problem: Mehrere Dienste, ein Host

Mehrere Dienste auf einem System sind normal:

  • Webserver
  • Mailserver
  • Git-Server
  • Reverse Proxy

Ohne Isolation:

  • Prozesse sehen sich gegenseitig
  • Signale sind global möglich
  • Ressourcen verdrängen sich
  • Ports können versehentlich blockiert werden

Die Frage lautet also:

Wie trennt man Dienste voneinander, ohne gleich eine VM zu starten?


2. Die zentrale Idee: Eine zusätzliche Identität

Der Kern des Modells ist einfach:

Isolation entsteht durch eine zusätzliche Kernel-Identität.

Jeder Prozess besitzt Credentials. Diese werden um ein Feld erweitert:

jail_id

Regel:

  • jail_id = 0 → Host
  • jail_id > 0 → konkrete Jail

Der Kernel prüft bei sicherheitsrelevanten Operationen:

  • Prozess-Sichtbarkeit
  • Signal-Versand
  • Debug-Zugriffe

Wenn die jail_id unterschiedlich ist → Zugriff wird verweigert.

🤔 Merksatz
Isolation entsteht nicht durch Dateisystem-Tricks, sondern durch eine zusätzliche Identität im Kernel.

Damit ist die grundlegende Domänentrennung definiert.

ℹ️ Zum Begriff »Jail«

Der Begriff Jail stammt aus der BSD-Welt und wurde ursprünglich in FreeBSD geprägt. Dort bezeichnet er eine Betriebssystemerweiterung, die Prozesse in voneinander isolierte Umgebungen einschließt – wie in einer „Gefängniszelle“, aus der sie nicht ohne Weiteres ausbrechen können.

Die FreeBSD-Jail-Implementierung war eine wichtige konzeptionelle Inspiration für dieses Projekt.
Der hier beschriebene Ansatz orientiert sich an der Grundidee der Domänentrennung, verfolgt jedoch eine NetBSD-native Integration über das bestehende kauth/secmodel-Modell.


3. Die Architektur in drei Ebenen

Das Modell gliedert sich in drei klar getrennte Ebenen:

1. Kernel-Erweiterung, 2. Lebenszyklus einer Jail, 3. Systemintegration mehrerer Jails

Diese Ebenen bauen aufeinander auf – sie vermischen sich nicht.


4. Ebene 1 – Kernel-Erweiterung

Diese Ebene definiert die Isolation selbst.

Sie umfasst:

  • Identitätsprüfung (jail_id)
  • Sicherheitsprofile
  • Ressourcenmodell
  • Speicher-Hook (UVM)
  • Port-Reservierung
  • Firewall-Integration

4.1 Sicherheitsprofile

Beim Erstellen einer Jail wird ein Profil gewählt:

-p low | medium | high

Das Profil verändert nicht die Identitätsmechanik. Es aktiviert zusätzliche Prüfungen.

Profil Wirkung
low Prozess-Isolation zwischen Jails: Prozesse unterschiedlicher jail_id können sich nicht sehen und keine Signale austauschen.
medium Zusätzliche Einschränkungen sensibler Kernel-Operationen (u.a. Mounts, ausgewählte sysctl-Zugriffe und weitere potenziell missbrauchbare Schnittstellen). SysV IPC bleibt bewusst erlaubt (z.B. für PostgreSQL).
high Wie medium, jedoch mit zusätzlicher Deaktivierung von SysV IPC innerhalb der Jail. Maximales Hardening.
ℹ️ Alternative: secmodel_sandbox (Lua-Policies)

Ein interessanter alternativer Ansatz findet sich im Projekt secmodel_sandbox, das feingranulare Sicherheitsrichtlinien über in Lua geschriebene Policies definiert:

https://github.com/smherwig/netbsd-sandbox

Dort werden sicherheitsrelevante Entscheidungen zur Laufzeit über eine Skript-Engine ausgewertet. Dieser Ansatz erlaubt eine sehr feingranulare und flexible Policy-Definition.

Für das hier beschriebene Jail-Modell wurde bewusst ein anderer Weg gewählt: sicherheitsrelevante Prüfungen sind nativ in C implementiert und folgen einem klaren „fail fast“-Prinzip. Entscheidungen werden direkt im Kernel getroffen, ohne zusätzliche Policy-Interpretation zur Laufzeit.

Ziel ist ein pragmatischer, performanter Ansatz mit klar definierten Profilen und einer einfachen „out of the box“-Erfahrung – ohne zusätzliche Policy-Sprache oder Konfigurationskomplexität.


4.2 Ressourcenmodell

Isolation ohne Ressourcenbegrenzung ist unvollständig.

Dafür existieren zwei Modi:

security.models.jail.resource_mode
Modus 0 – RLIMIT

Per-Prozess-Limits. Einfach, aber aggregiert nicht.

Modus 1 – Aggregate (Standard)

Ressourcen werden pro Jail zusammengezählt.

Vor kritischen Aktionen:

  • fork
  • Jail-Eintritt
  • Speicherwachstum

wird geprüft:

Würde das Budget überschritten?

Wenn ja → Aktion wird verweigert.

💡 Admission Control
Das Ressourcenmodell folgt dem Prinzip der Admission Control: Aktionen werden bereits vor ihrer Ausführung geprüft und bei Budgetüberschreitung konsequent verweigert.

4.3 Speicherbegrenzung (UVM-Integration)

Die Speicherbegrenzung wird direkt im UVM-Subsystem verankert.

Dazu wurde ein Jail-spezifischer Hook in den Pfad der Speicherallokation integriert. Immer wenn ein Prozess virtuellen Speicher erweitert – etwa durch

  • mmap
  • brk
  • automatisches Stack-Wachstum

wird geprüft, ob der Prozess einer Jail mit aktivem Aggregat-Ressourcenmodell angehört.

Ist dies der Fall, erfolgt eine Budgetprüfung gegen das für diese Jail definierte Gesamtspeicherlimit.

Würde die neue Allokation das Budget überschreiten, wird die Operation unmittelbar mit ENOMEM abgelehnt.

Die Entscheidung erfolgt synchron im Allokationspfad – ohne nachgelagerte Korrekturmechanismen.

🔒 Präventive Speichersteuerung

Die Speicherbegrenzung erfolgt direkt im Allokationspfad des Kernels.

Es gibt keinen nachträglichen OOM-Killer. Stattdessen wird Speichererweiterung strikt vor ihrer Durchführung geprüft und gegebenenfalls verweigert.

Das System bleibt damit deterministisch: Überlastung wird verhindert, nicht nachträglich behandelt.


4.4 Port-Reservierung

Jails können optional exklusive TCP/UDP-Ports reservieren:

jailctl create -r 80,443 -n webjail <root>

Mit der Reservierung wird eine feste Zuordnung zwischen Port und jail_id hergestellt.

Wirkung:

  • Nur Prozesse dieser Jail dürfen die reservierten Ports binden.
  • Andere Jails oder der Host erhalten beim bind(2) einen Fehler.
  • Port-Konflikte werden bereits beim Erstellen der Jail erkannt.

Die Durchsetzung erfolgt im Kernel beim Socket-Binding, nicht über Konvention oder Firewall-Regeln.

🔒 Identitätsgebundene Ports

Port-Reservierung ist keine Netzwerkfilter-Regel, sondern eine Identitätsbindung.

Der Kernel verknüpft reservierte Ports direkt mit einer jail_id. Damit wird verhindert, dass ein Dienst versehentlich oder absichtlich einen fremden Port übernimmt.

Konfigurationsfehler werden deterministisch beim Binden des Sockets erkannt – nicht erst zur Laufzeit durch Firewall- oder Betriebslogik.


4.5 Firewall-Integration (NPF)

NPF-Regeln können explizit auf eine Jail referenzieren:

pass in proto tcp to port 443 jail webjail

NPF prüft im Paketpfad die dem Socket zugeordnete jail_id und vergleicht sie mit der in der Regel angegebenen Jail.

Damit wird Netzwerkzugriff direkt an die Prozessidentität gebunden.

Für eingehende Verbindungen ergänzt dies die Port-Reservierung. Der eigentliche Mehrwert liegt jedoch im ausgehenden Verkehr.

Auch für ausgehende Verbindungen kann die jail_id als Regelkriterium verwendet werden:

  • Zugriff nur auf ausgewählte Ports
  • Zugriff nur auf bestimmte Hosts
  • Zugriff nur auf definierte Subnetze

Damit lässt sich präzise festlegen, welche externen Ziele eine Jail überhaupt erreichen darf.

🔒 Egress-Kontrolle pro Jail

Die Identitätsbindung in NPF ermöglicht nicht nur Eingangsfilterung, sondern gezielte Egress-Kontrolle.

Eine Jail kann auf definierte Ports, Hosts oder Subnetze beschränkt werden – unabhängig von Interface- oder IP-Konfiguration.

Damit wird Netzwerkpolitik an die Prozess-Domäne gekoppelt, nicht an die Netzwerkumgebung.


5. Ebene 2 – Lebenszyklus einer Jail

Isolation definiert eine Domäne – aber noch keinen Dienst.

Ein isolierter Prozess kann starten, abstürzen, Kinderprozesse hinterlassen oder in undefiniertem Zustand verbleiben.

Für einen reproduzierbaren Betrieb benötigt eine Jail daher eine explizite Lebenszyklus-Verwaltung.

Diese Aufgabe übernimmt jailctl supervise.

Grundprinzip:

monitor (Host)
  └── workload (in Jail)

Der Monitor-Prozess läuft im Host-Kontext. Der eigentliche Dienst (Workload) läuft innerhalb der Jail.

Damit bleibt die Kontrollinstanz außerhalb der isolierten Domäne. Die Aufsicht unterliegt nicht denselben Beschränkungen wie der überwachte Prozess.

💡 Isolation ist kein Lebenszyklus

Der Kernel trennt Identitäten - aber er betreibt keine Dienste.

Eine Jail definiert eine isolierte Domäne, nicht deren Start-, Stop- oder Restart-Verhalten.

Der Supervisor ergänzt daher bewusst eine leichtgewichtige Betriebsebene oberhalb der Isolation.


Aufgaben des Supervisors

Der Supervisor übernimmt die minimale Betriebslogik für genau eine Jail.

Seine Verantwortung ist klar abgegrenzt:

  • Überwachung des Workload-Prozesses
  • Protokollierung von Start, Stop und Abstürzen
  • kontrollierte Neustarts mit Backoff
  • definiertes Stop-Verhalten

Beim Stoppen erfolgt eine abgestufte Beendigung:

  1. SIGTERM an die gesamte Prozessgruppe
  2. definierte Wartezeit
  3. falls notwendig: SIGKILL

Ziel ist kein “Management-Framework”, sondern reproduzierbares und deterministisches Verhalten.

💡 Trennung von Verantwortung

Kernel: erzwingt Isolation und Ressourcenregeln.
Supervisor: steuert den Lebenszyklus eines Dienstes.

Die Isolationsebene bleibt frei von Betriebslogik. Die Betriebsebene verändert keine Sicherheitsregeln.


6. Ebene 3 – Systemintegration mehrerer Jails

Während der Supervisor den Lebenszyklus einer einzelnen Jail steuert, übernimmt jailmgr die Integration mehrerer Jails in das Gesamtsystem.

Seine Aufgabe ist nicht Orchestrierung im Container-Sinne, sondern strukturierte Systemeinbindung.

jailmgr kümmert sich um:

  • Overlay-Dateisysteme
  • gemeinsames read-only Base-System
  • eigenes /dev per tmpfs
  • Autostart via rc.d
  • Status- und Verwaltungsfunktionen

jailmgr ergänzt damit eine konsistente Betriebsschicht, ohne eine eigene Container-Runtime oder Image-Infrastruktur einzuführen.

jailmgr verfolgt dabei ein klares Ziel: Systemintegration soll so komfortabel und intuitiv wie möglich sein – ohne zusätzliche Abstraktionsschicht oder fremde Infrastruktur.

Die Bedienung bleibt bewusst einfach. Jails sind normale Systembestandteile, keine Objekte in einer separaten Runtime.

Die Nutzung folgt dabei einem klaren Ablauf:

  1. Basissystem vorbereiten:
# jailmgr bootstrap
  1. 100 Jails mit jeweils eigener HTTP-Instanz definieren:
# for i in seq -f "%02g" 0 99; do
jailmgr create -a
-s "/usr/libexec/httpd -I 80$i -X -s -f /"
-r 80$i
-f local3 -o info -e info
-t www$i www$i
done
  1. Alle Jails starten:
# jailmgr start --all

Damit werden:

  • ein gemeinsames Basis-System initialisiert (bootstrap)
  • 100 isolierte Jails erzeugt (www00www99)
  • je eine eigene httpd-Instanz auf Ports 8000–8099 gestartet
  • Logging und Lebenszyklus automatisch integriert
  • alle Instanzen konsistent aktiviert (start --all)

Der gesamte Ablauf bleibt transparent, shell-nativ und ohne zusätzliche Laufzeitumgebung.

🤔 Komfort ohne Parallelwelt

Das Ziel von jailmgr ist keine Container-Plattform, sondern eine möglichst reibungslose Nutzung der Kernel-Isolation.

Die Bedienung bleibt Shell-nativ und transparent. Komplexität wird nicht durch zusätzliche Infrastruktur gelöst, sondern durch saubere Systemintegration.


7. Logging und Nachvollziehbarkeit

Kernel und Supervisor haben getrennte Zuständigkeiten.

Der Kernel protokolliert strukturelle Ereignisse (Erstellung, Zerstörung, Ressourcen-Verweigerung).

Der Supervisor protokolliert den Lebenszyklus (Start, Stop, Absturz, Neustart).

Die eigentlichen Anwendungslogs werden dabei bewusst nicht über ein spezielles Logging-Framework erzwungen. Ganz im Sinne der 12-Factor-App-Prinzipien werden Logs als Event-Streams behandelt.

Überwachte Prozesse schreiben ihre Ausgaben nach stdout und stderr. Die Applikation muss keine spezielle Logging-API der Laufzeit implementieren.

Der Supervisor übernimmt:

  • Zuordnung einer Syslog-Facility pro Jail
  • getrennte Log-Level für stdout und stderr
  • Weiterleitung der Streams an den Syslog-Daemon des Hosts

Dort können die Logs regelbasiert:

  • in separate Logdateien überführt
  • zentral aggregiert
  • rotiert
  • oder weiterverarbeitet werden
🚀 Logging als Stream, nicht als API

Anwendungen schreiben Logs auf stdout und stderr.

Der Supervisor übernimmt die Weiterleitung an den Syslog-Daemon des Hosts und trennt Facility sowie Log-Level sauber.

Damit bleibt die Applikation framework-unabhängig, während Logging zentral und regelbasiert im Basissystem verarbeitet wird.


8. Was Jails leisten – und wo ihre Grenze liegt

Jails definieren leichtgewichtige Prozess-Domänen innerhalb eines gemeinsamen Kernels.

Sie leisten:

  • Prozess-Domänentrennung
  • Signal-Isolation
  • Ressourcen-Budgetierung
  • Port-Exklusivität
  • Identitätsgebundene Firewall-Regeln
  • definierten Lebenszyklus

In der hier vorgestellten Form eignen sich Jails vor allem als betrieblicher Rahmen für vertrauenswürdige Dienste eines Tenants – etwa zur strukturierten Trennung mehrerer Applikationen auf einem Host.

Sie leisten nicht:

  • Isolation auf Hypervisor-Ebene
  • Schutz gegen aktiv bösartige oder kompromittierte Tenants
  • vollständige Trennung aller Kernel-Angriffsflächen

Ergänzende Einordnung

Für echte Multi-Tenant-Umgebungen, in denen unterschiedliche Sicherheitsdomänen einander nicht vertrauen, bleibt Isolation über virtuelle Maschinen die robustere Wahl – unabhängig davon, ob darüber Jails, Linux-Container oder Kubernetes betrieben werden.

Wer auf Shared-Kernel-Isolation setzt, geht implizit davon aus, dass der Kernel keine sicherheitsrelevanten Bugs enthält. Das ist eine Hoffnung – keine Sicherheitsarchitektur.

Ein praktikabler Ansatz kann ein hybrides Modell sein:

  • Aufteilung eines Hosts in mehrere VMs (je Sicherheitsdomäne)
  • innerhalb jeder VM strukturierte Unterteilung in mehrere Jails für einzelne Dienste

So entsteht eine zweistufige Isolation:

Hypervisor trennt Sicherheitsdomänen. Jails strukturieren Dienste innerhalb einer Domäne.

✅ Einordnung

Jails sind ein Werkzeug zur strukturierten Diensttrennung innerhalb eines Vertrauensbereichs.

Für harte Mandantentrennung und Schutz gegen bösartige Akteure bleibt Virtualisierung die angemessene Ebene.

Beide Ansätze schließen sich nicht aus – sie ergänzen sich.


9. Zusammenfassung

Die Architektur folgt einem einfachen Prinzip:

  • Eine zusätzliche Identität im Kernel
  • Präventive Ressourcensteuerung
  • Minimaler, klarer Lebenszyklus
  • Systemintegration ohne Parallel-Infrastruktur

Eine kohärente Erweiterung des bestehenden NetBSD-Sicherheitsmodells.


🧪 Aktueller Status

Der Stand ist weiterhin experimentell.

Die Architektur ist konsistent, die Implementierung funktional, aber weder auditiert noch produktiv breit erprobt.

Die formale Spezifikation ist hier einsehbar: https://raw.githubusercontent.com/MatthiasPetermann/netbsd-src/refs/heads/feature/jails/doc/specs/netbsd-jails-specification.txt

Der aktuelle Quellcode befindet sich im Branch feature/jails meines NetBSD-Forks: https://github.com/MatthiasPetermann/netbsd-src/tree/feature/jails


10. Ausblick

Der nächste Schritt ist keine Feature-Erweiterung, sondern Review und Erprobung.

Die systemnahen Teile des Projekts – secmodel_jail und jailctl, die UVM-Integration, das Ressourcenmodell und die NPF-Anbindung – benötigen fundierten Code-Review durch erfahrene Kernel-Entwickler. Der aktuelle Stand ist funktional, aber nicht ausreichend geprüft.

Parallel dazu soll der Ansatz mit realen Workloads über längere Zeiträume betrieben werden. Im Fokus stehen:

  • Stabilität der Identitätsmechanik
  • korrektes Verhalten im UVM-Allokationspfad
  • robuste Durchsetzung der Aggregat-Limits
  • deterministisches Verhalten unter reproduzierbarer Last

Geplant ist gezieltes Test-Tooling, um definierte Speicher-, Fork- und I/O-Szenarien systematisch zu erzeugen und Grenzfälle kontrolliert zu provozieren.

Die NPF-Erweiterungen sind strukturell vom Kern getrennt und lassen sich bei Bedarf isoliert zurücknehmen. Dennoch gilt auch hier: ohne Review kein Vertrauen.

jailmgr wird sich im praktischen Einsatz weiterentwickeln – etwa im Hinblick auf persistente Overlays, Integration mit Volume-Management (z. B. LVM oder ZFS) und Einbindung in Backup-Workflows. Welche Erweiterungen sinnvoll sind, wird der Betrieb zeigen.

Langfristiges Ziel ist ein stabiler, gereviewter Stand für den Dauerbetrieb – etwa auf einem Homeserver – sowie die Portierung des Patchsets auf NetBSD 11.

🤔 Perspektive: Schlanke Variante ohne Integration in andere Kernel-Subsysteme

Für meine eigenen Anwendungsfälle bietet bereits die reine Identitäts- und Prozessisolation einen erheblichen Mehrwert – auch ohne UVM-Integration oder NPF-Anbindung.

Eine reduzierte „Light“-Variante, die sich auf:

  • zusätzliche Kernel-Identität
  • Prozess- und Signal-Isolation
  • minimale secmodel-Integration

beschränkt und bewusst auf Eingriffe in Speicher- und Netzwerkpfade verzichtet, wäre deutlich einfacher zu reviewen und zu integrieren.

Perspektivisch ist daher denkbar, einen solchen schlankeren Kernansatz als diskussionsfähige Basis für eine mögliche Integration in NetBSD vorzuschlagen.


Bildnachweis: Das NetBSD-Logo ist eine eingetragene Marke der NetBSD Foundation, Inc. Verwendung im Rahmen redaktioneller Berichterstattung gemäß den Richtlinien zur Logonutzung.