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_jailim Kerneljailctlim Userlandjailmgrals 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
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.
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.
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→ Hostjail_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.
Damit ist die grundlegende Domänentrennung definiert.
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:
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. |
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.
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
mmapbrk- 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.
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.
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.
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.
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:
SIGTERMan die gesamte Prozessgruppe- definierte Wartezeit
- falls notwendig:
SIGKILL
Ziel ist kein “Management-Framework”, sondern reproduzierbares und deterministisches Verhalten.
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
/devpertmpfs - 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:
- Basissystem vorbereiten:
# jailmgr bootstrap
- 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
- Alle Jails starten:
# jailmgr start --all
Damit werden:
- ein gemeinsames Basis-System initialisiert (
bootstrap) - 100 isolierte Jails erzeugt (
www00–www99) - je eine eigene
httpd-Instanz auf Ports8000–8099gestartet - Logging und Lebenszyklus automatisch integriert
- alle Instanzen konsistent aktiviert (
start --all)
Der gesamte Ablauf bleibt transparent, shell-nativ und ohne zusätzliche Laufzeitumgebung.
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
stdoutundstderr - 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
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.
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.
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.
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.