Plugin-Sandbox und Bedrohungsmodell¶
Was der Server tut, um ein Plugin zu isolieren: die Prüfungen bei der
Installation, die Schutzschichten zur Laufzeit, die Sandbox-Modi und was
jede davon trotzdem nicht verhindern kann. Der Code liegt in
internal/adapters/driven/pluginrunner (Analyse bei der Installation,
Sandbox), cmd/evey-plugin-launcher (Landlock und der seccomp-Filter des
Kernels), sandbox/sandbox.c (der LD_PRELOAD-Filter) und
internal/adapters/driving/pluginhost (die Host-API, die Plugins
aufrufen). Die Gestaltung ist durch
ADR-0012,
ADR-0013 und
ADR-0014 festgelegt. Wenn Sie ein
Plugin schreiben und wissen möchten, was Sie angeben dürfen und was
abgelehnt wird, lesen Sie Berechtigungen und Slots.
Welche Sandbox Ihre Maschine bekommt, steht in
Plugin-Sandbox einrichten.
Bedrohungsmodell¶
Plugins laufen als eigene Prozesse, die mit dem Server über gRPC auf Unix-Sockets kommunizieren. Der Server vertraut standardmäßig keinem Plugin-Code. Die Risiken, gegen die er schützt, vom schwersten zum leichtesten:
- Identitätsdiebstahl - ein Plugin gibt sich als ein anderes aus, um dessen Berechtigungen zu nutzen.
- Rechteausweitung - ein Plugin bricht aus seiner Sandbox aus und führt Befehle als Server aus.
- Dateizugriff - ein Plugin liest oder schreibt außerhalb seines eigenen Verzeichnisses, vor allem die Geheimnisse des Servers.
- Datenabfluss übers Netz - ein Plugin schickt Daten an einen Host seiner Wahl.
- Missbrauch von Ressourcen - Mining, Fork-Bomben, Erschöpfen von Arbeitsspeicher oder Platte.
- Lieferkette - verschleierter oder kompilierter Code, den die Analyse nicht lesen kann.
flowchart LR
zip[Paket] --> ex[Entpacken des ZIP<br/>Grenzen, kein Symlink, kein setuid]
ex --> man[Manifest<br/>Berechtigungen]
man --> an[Statische Analyse<br/>CRITICAL lehnt ab]
an --> sig[Signatur<br/>cosign-Regel]
sig --> prev[Vorschau für den Administrator<br/>nimmt an oder lehnt ab]
prev --> run[Laufzeit]
subgraph run[Laufzeit - jeder Modus]
fs[Dateisystem<br/>nur das eigene Verzeichnis]
net[Netz<br/>kein IP-Socket, Fetch-Vermittler]
sys[Systemaufrufe<br/>seccomp-Sperrliste]
uid[Eigener Benutzer<br/>oder User-Namespace]
sock[Host-Socket<br/>Identität über SO_PEERCRED]
res[Ressourcenwächter<br/>Speicher, PIDs, Platte, nice]
end
Bei der Installation¶
Entpacken des ZIP¶
Bevor irgendeine Datei geschrieben wird (pluginrunner/zip.go):
- höchstens 5.000 Einträge, 100 MB pro Datei, 500 MB unkomprimiert insgesamt, und ein Kompressionsverhältnis von 100:1 oberhalb von 1 MB unkomprimiert (Dekompressionsbomben);
- Einträge, deren Pfad das Archiv verlässt (
..), werden abgelehnt; - symbolische Links werden abgelehnt;
- setuid-, setgid- und Sticky-Bits werden entfernt.
Manifest und Berechtigungen¶
Das Manifest gibt die Berechtigungen an, die ein Plugin verlangt. Eine unbekannte Berechtigung lässt die Installation scheitern. Danach werden sie durchgesetzt:
subprocesserlaubt Kindprozesse. Ohne diese Berechtigung lehnt der Kernel das Erzeugen von Prozessen ab (-deny-fork), und derLD_PRELOAD-Filter lehnt für interpretierte Plugins zusätzlichexecveab.native-binaryerlaubt, kompilierten Code oder Archive mitzuliefern, die die Analyse nicht lesen kann.- API-Berechtigungen (
api.library,api.user,api.metadata,media.read,media.write,notification.sender...) werden bei jedem Aufruf an den Host vonPluginPermissionInterceptorgeprüft.
Die vollständige Liste steht in Berechtigungen und Slots.
Statische Analyse¶
Jede Datei des Pakets wird von
pluginrunner.AnalyzePlugin
geprüft, nach einem Normalisierungsschritt, der übliche Verschleierungen
rückgängig macht (Aliase, verkettete Zeichenketten, f-Strings):
| Kategorie | Schwere | Beispiele |
|---|---|---|
shell_exec |
WARNING / CRITICAL | os.system, subprocess.run, child_process.exec, PHP system()/shell_exec()/proc_open(), preg_replace /e, Ruby Kernel.exec/system, open('\|cmd') (CRITICAL); Backticks in Ruby (WARNING) |
network |
WARNING / CRITICAL | requests.get, fetch, Reverse- und Bind-Shells |
filesystem |
WARNING / CRITICAL | /etc/passwd, .ssh/, rm -rf, Operationen mit symbolischen Links |
obfuscation |
WARNING / CRITICAL | eval, __builtins__, base64 -d \| bash und andere Dekodierungen, die in eine Shell geleitet werden (CRITICAL); Base64-Dekodierung, Hex-Folgen (WARNING) |
privilege |
CRITICAL | setuid, chmod 4777, sudo, Signale an PID 1 |
cryptomining |
CRITICAL | stratum+tcp, xmrig, cryptonight |
runtime_install |
CRITICAL | pip install, npm install, curl ... \| sh |
dynamic_import |
CRITICAL | __import__(), importlib.import_module |
sandbox_escape |
CRITICAL | vm.runIn*, process.binding, new Function, __subclasses__(), __mro__ |
deserialization |
CRITICAL / WARNING | pickle.loads, marshal.loads, __reduce__; WARNING: yaml.load() ohne Loader |
native_code |
CRITICAL / WARNING | ctypes.CDLL, ffi.Library; WARNING: unsafe in Go |
compiled_binary |
WARNING, dann BLOCK | .so, .dll, .exe, .wasm, .jar |
embedded_archive |
WARNING, dann BLOCK | .zip, .tar.gz |
mime_mismatch |
wie CRITICAL behandelt | eine Endung, die ein Bild vorgibt, mit anderem Inhalt |
suspicious_url |
WARNING | ein Schema außer HTTP in einer URL im Code |
unvendored_deps |
WARNING | requirements.txt oder package.json ohne mitgelieferte Module |
Ein Fund der Stufe CRITICAL oder ein mime_mismatch lehnt die Installation
ab. compiled_binary und embedded_archive lehnen sie ab, außer das
Manifest gibt native-binary an.
Was die Analyse nicht garantieren kann
Sie arbeitet mit Mustern. Sie löst bekannte Arten der Verschleierung auf
(Zeichenketten, die in mehreren Schritten zu etwas Aufrufbarem verkettet
werden, Zugriff auf eingebaute Funktionen über den Namen, Ausbrüche über
Function.constructor), jede mit einem Regressionsbeispiel in
tests/fixtures/plugins/malicious/. Sie kann aber nicht jede
Umwandlung zur Laufzeit verfolgen (strrev("metsys"), eine Nutzlast,
die aus einer Datei gelesen und dekodiert wird). Kommentare werden vor der Analyse
entfernt. Die Grenze, die wirklich durchgesetzt wird, ist die Sandbox zur
Laufzeit, nicht die Analyse: Ein Plugin, dessen Muster die Analyse
nicht erkennt, kann trotzdem die Daten des Servers nicht lesen, keinen
gesperrten Systemaufruf machen, seine Ressourcenlimits nicht
überschreiten und keine Host-Methode aufrufen, für die es keine
Berechtigung hat.
Signatur¶
Ein Paket kann mit cosign signiert werden (cosign sign-blob --key, ECDSA
P-256 über den SHA-256 des ZIP). Der öffentliche Schlüssel des Autors reist im
Paket als cosign.pub; Evey prüft damit die abgetrennte .sig, im eigenen
Prozess und offline (pluginrunner/cosign.go), und berechnet den
Fingerabdruck des Schlüssels (sha256: über den öffentlichen DER-Schlüssel).
Daraus folgen drei Dinge:
- Vertrauen: Der Fingerabdruck steht in der Liste der Vertrauenswürdigen
Herausgeber der Instanz oder nicht (
Verwaltung → Plugins, oder beim Start überEVEY_PLUGINS_COSIGN_PUBLIC_KEYbefüllt). Ein vertrauter Schlüssel zeigt den Namen des Herausgebers; ein unbekannter zeigt seinen Fingerabdruck und die Aktion Diesem Schlüssel vertrauen. - Kontinuität: Der Fingerabdruck wird bei der Installation am Plugin
festgehalten. Ein Update, das mit einem anderen Schlüssel signiert ist
(
publisher-changed) oder nicht mehr signiert ist (signature-lost), wird unabhängig von der Regel abgelehnt, bis der Administrator im Update-Dialog den neuen Herausgeber akzeptiert (accept_new_publisher). - Regel:
EVEY_PLUGINS_COSIGN_POLICYlegt fest, was das Urteil bewirkt.
| Regel | Wirkung |
|---|---|
off |
Die Signatur wird nicht geprüft und nichts wird festgehalten. |
warn (Standard) |
Das Urteil wird festgehalten und angezeigt; unsignierte, nicht vertraute und ungültige Pakete werden installiert. |
enforce |
Nur ein Paket, das von einem vertrauenswürdigen Herausgeber signiert ist, wird installiert oder aktualisiert. |
Eine Signatur beweist, dass das Paket dasjenige ist, das sein Herausgeber veröffentlicht hat, und dass ein Update vom selben Herausgeber stammt. Über den Code sagt sie nichts; den beurteilen die Analyse, die Berechtigungen und die Sandbox.
Vorschau für den Administrator¶
POST /api/v1/admin/plugins/preview führt die Analyse aus, ohne zu
installieren. Der Administrator sieht die verlangten Berechtigungen, jeden
Fund mit Datei, Zeile und Kategorie, die im Code gefundenen URLs und den
SHA256 des Pakets. Der vom Administrator bestätigte Hash wird beim Update geprüft, und eine Erstinstallation lässt sich über ein #sha256:-Fragment in der URL festlegen:
Das analysierte Paket ist also das installierte. Ein Update
zeigt den Unterschied bei Berechtigungen, Slots und Hosts, und eine neue
Berechtigung verlangt eine ausdrückliche Bestätigung.
Für erhöhte Berechtigungen ist der Administrator die Vertrauensgrenze: Evey zeigt die Angriffsfläche, der Administrator nimmt an oder lehnt ab.
Zur Laufzeit¶
Sechs Schichten gelten in jedem Sandbox-Modus; wie die ersten beiden durchgesetzt werden, hängt vom Modus ab.
Dateisystem¶
Ein Plugin sieht sein eigenes Verzeichnis und sein Socket-Verzeichnis, nicht
die Daten des Servers. Dafür sorgt eine Landlock-Freigabeliste (landlock)
oder eine Liste erlaubter Mounts von bubblewrap (bubblewrap, strict). Im
Modus basic auf einem Kernel ohne Landlock gibt es keine Beschränkung pro
Pfad: Dann schützen der eigene Benutzer und die Dateirechte die Geheimnisse
des Servers.
Netz¶
Ein Plugin hat keinen direkten Netzzugang, egal was es angibt: Jeder
IP-Socket wird abgelehnt (-deny-inet in landlock und basic, ein
Netzwerk-Namespace in bubblewrap und strict). Ein Plugin mit der
Berechtigung network ruft die Methode Fetch des Hosts über seinen
Unix-Socket auf. Der Host, in
internal/adapters/driving/pluginhost/fetch.go:
- prüft das Ziel gegen die Liste
external_fetchdes Plugins (genaue Hosts, ohne Beachtung von Groß- und Kleinschreibung; ein Platzhalter*.example.comwird bei der Installation abgelehnt; ein Port-Suffix wird ignoriert); - erlaubt nur
httpundhttpsund nurGETundHEAD; - verbindet sich über einen SSRF-sicheren Client (
internal/pkg/ssrf), der private, reservierte und link-lokale Adressen nach der DNS-Auflösung ablehnt. DNS-Rebinding und Weiterleitungen werden also erneut geprüft (eine Weiterleitung zu einem Host außerhalb der Liste wird abgelehnt); - hängt keine Zugangsdaten des Servers an, begrenzt den Antwortinhalt auf 8 MiB und die Anfrage-Header auf 8 KiB und bricht nach 20 s ab.
Das schließt die Wege, die direkter Ausgang offen ließ (IP-Adressen wie
169.254.169.254, ein eigener DNS-Resolver des Plugins). Was bleibt: Der
Vermittler bestimmt, wohin ein Aufruf geht, nicht was er mitnimmt. Siehe
Bekannte Grenzen.
Empfehlung für Betreiber
Der Vermittler verlagert die SSRF-Angriffsfläche auf den Host. Eine
Ausgangsregel auf dem Container evey-server, unabhängig vom Code,
fügt einen zweiten Schutz hinzu: Verwerfen Sie 169.254.0.0/16 und
beschränken Sie das interne Netz auf die Endpunkte von Datenbank und
Suche.
Systemaufrufe¶
Zwei seccomp-Filter lehnen dieselben gefährlichen Systemaufrufe ab:
- Der Kernel-Filter, installiert von
evey-plugin-launcherkurz bevor er das Plugin startet (seccomp_linux.go). Das Plugin und seine Kinder erben ihn, und er deckt auch kompilierte, statische Binärdateien ab. Eine falsche Architektur in einem Systemaufruf beendet den Prozess. Angewendet inlandlockundbasic. - Der
LD_PRELOAD-Filter,sandbox/sandbox.c, geladen in interpretierte Plugins, diesubprocessnicht angeben. Er lehnt zusätzlichexecve/execveatab und markiert das Plugin als nicht auslesbar (non-dumpable). Angewendet inlandlock,basicundstrict. Fehlt die Bibliothek, wird der Start eines solchen Plugins abgelehnt (plugin.error).
Abgelehnte Systemaufrufe:
| Kategorie | Systemaufrufe |
|---|---|
| Debugging | ptrace, process_vm_readv, process_vm_writev |
| Signale | tgkill, rt_sigqueueinfo, rt_tgsigqueueinfo an jeden Prozess außer dem Plugin selbst (beide Filter); kill ebenso im Kernel-Filter, an jedes Ziel im LD_PRELOAD-Filter; tkill im Kernel-Filter (der LD_PRELOAD-Filter erlaubt es an das Plugin selbst); pidfd_send_signal, pidfd_getfd (stiehlt einen Dateideskriptor). Eine Laufzeit, die ein Signal erneut auslöst oder ihre eigenen Threads unterbricht, funktioniert weiter. Ab Landlock ABI 6 kann ein Plugin außerdem keinem Prozess außerhalb seiner Sandbox ein Signal schicken |
| Mounts | mount, umount2, pivot_root, chroot |
| Namespaces | unshare, setns und clone mit einem Flag CLONE_NEW* (unshare unter anderem Namen), für jedes Plugin |
| Kernel-Module | init_module, finit_module, delete_module |
| BPF, perf | bpf, perf_event_open |
| Hardware-E/A | ioperm, iopl (nur x86-64, wo es sie gibt) |
| Swap | swapon, swapoff |
| Systemsteuerung | syslog, reboot, settimeofday, clock_settime, clock_adjtime, adjtimex |
| Kexec | kexec_load, kexec_file_load |
| Bausteine für Kernel-Exploits | io_uring_setup, io_uring_enter, io_uring_register, userfaultfd |
| Identität | setuid, setgid, setreuid, setregid, setresuid, setresgid, capset |
| Schlüsselbund | add_key, request_key, keyctl |
Für ein Plugin ohne subprocess lehnt der Kernel außerdem das Erzeugen von
Prozessen ab (fork, vfork, clone ohne CLONE_THREAD; clone3
antwortet ENOSYS, damit die C-Bibliothek auf clone zurückfällt); Threads
funktionieren weiter.
Das ist eine Sperrliste: open, read, write, mmap, Unix-socket
und connect bleiben erlaubt. Pfade beschränkt die Dateisystem-Schicht,
nicht seccomp. In bubblewrap wird der Launcher nicht benutzt: Eine
statische Binärdatei bekommt dort keinen seccomp-Filter und wird durch die
Namespaces und die Liste erlaubter Mounts begrenzt.
Ein eigener Benutzer¶
Ein Plugin kann als Benutzer 1001 (evey-plugin) laufen, getrennt vom
Server (1000). Dann kann es die Geheimnisse des Servers allein wegen der
Dateirechte nicht lesen. EVEY_PLUGINS_UID_SEPARATION steuert das:
| Wert | Wann der Wechsel passiert |
|---|---|
auto (Standard) |
nur wenn die Sandbox zu basic aufgelöst wird, der einzige Fall, in dem die Dateirechte der einzige Schutz der Geheimnisse sind |
on |
immer, als zusätzliche Schicht |
off |
nie |
Den Wechsel macht der Launcher, der cap_setuid hält; der Server selbst
läuft ohne Rechte oder Capabilities. Der Launcher lehnt jedes Ziel unter
1000 ab. Kann er nicht wechseln, wird ein Plugin in basic abgelehnt (der
Benutzer ist dort der einzige Schutz), während es in landlock mit einer
Warnung als Benutzer des Servers läuft. In bubblewrap und strict
übernimmt der User-Namespace diese Rolle.
Ist der Wechsel aktiv, sind der Signierschlüssel (0600) und die
Sicherungen (Verzeichnis 0700, Dateien 0600) zur Laufzeit nur für den
Besitzer lesbar, und das gilt auch für ein per Bind-Mount eingehängtes
/data. Alle Plugins teilen sich den Benutzer 1001: Voneinander getrennt
sind sie durch die Dateisystem-Schicht, nicht durch den Benutzer.
Authentifizierung am Host-Socket¶
Jedes Plugin hat seinen eigenen Socket,
/tmp/evey/plugins/<plugin_id>/host.sock, bedient von seinem eigenen
gRPC-Server. Bei jedem Aufruf liest der Server die PID des Aufrufers aus dem
Kernel (SO_PEERCRED, das im Benutzerbereich nicht gefälscht werden kann)
und vergleicht sie mit der PID, die beim Start des Plugins festgehalten
wurde. Die Identität des Plugins kommt aus dem Socket, nie aus einem Header.
Ein Plugin kann sich also nicht als ein anderes ausgeben, nicht den Socket
eines anderen Plugins aufrufen und keine Aufrufe über einen Kindprozess
leiten. Eine abweichende PID wird als Warnung protokolliert.
Ressourcenlimits¶
Ein Wächter im Benutzerbereich (cgroups sind in einem Container ohne
Privilegien nicht verfügbar) liest alle 2 Sekunden /proc/<pid> und hält
ein Plugin an, das ein Limit überschreitet
(ADR-0013):
| Limit | Standard | Geprüft |
|---|---|---|
| Arbeitsspeicher (RSS) | 256 MiB | bei jedem Durchlauf |
| Prozesse und Threads | 64 | bei jedem Durchlauf (nur die Thread-Anzahl des Hauptprozesses) |
| Speicherplatz seines Verzeichnisses | 100 MiB | bei jedem 15. Durchlauf, etwa alle 30 s |
| CPU-Priorität | nice 10, beim Start gesetzt |
- |
Derselbe Durchlauf markiert ein Plugin, das einen CPU-Kern etwa 30 s lang
über 90 % auslastet, und hält ein Plugin mit hardware.gpu an, das eine
Grafikeinheit auf dieselbe Weise auslastet. Ein einziger Satz Ressourcenlimits, gesetzt
unter Verwaltung → Plugins → Ressourcenlimits, gilt für jedes Plugin; die
Umgebungsvariablen EVEY_PLUGINS_MEMORY_MB, EVEY_PLUGINS_PIDS_MAX,
EVEY_PLUGINS_DISK_MAX_MB und EVEY_PLUGINS_CPU_NICE legen die
Standardwerte beim Start fest.
Abschottung der Oberfläche¶
Die Oberfläche eines Plugins läuft in einem
<iframe sandbox="allow-scripts">, ohne Zugriff auf denselben Ursprung,
ohne Formulare und Popups, unter einer Content-Security-Policy, die
Verbindungen nur zu seinen Hosts aus external_fetch erlaubt. Die Brücke
(frontend/packages/client/src/plugins/hostProtocol.ts) begrenzt, was sie
von der Seite verlangen kann: toast höchstens 3 pro Sekunde, 200 Zeichen,
kein HTML; navigate nimmt nur /home, /library, /media, /player,
/settings und /search an; resize zwischen 50 px (60 auf Handy und
Fernseher) und 800 px. Slots werden bei der Installation geprüft.
Härtung des Servers¶
Beim Start markiert sich der Server als nicht auslesbar
(PR_SET_DUMPABLE=0,
hardening_linux.go): Ein Prozess, der
als derselbe Benutzer läuft, kann seinen Speicher oder seine Umgebung nicht
über /proc lesen. Das schützt den Signierschlüssel im Speicher und die
Passwörter für Datenbank und Suche, auch auf einem Kernel ohne Landlock.
Benachrichtigung bei Abstürzen¶
Der Runner veröffentlicht ein Ereignis plugin.error, wenn einem Plugin,
das die Sandbox-Bibliothek braucht, diese fehlt, oder wenn ein Plugin mit
einem Fehler endet. Der Benachrichtigungsdienst leitet es über die
eingerichteten Kanäle an den Administrator weiter, und das
Sicherheits-Dashboard hält es fest.
Sandbox-Modi¶
Der Modus legt fest, wie Dateisystem und Netz eingeschränkt werden.
EVEY_PLUGINS_SANDBOX_MODE setzt den Anfangswert (Standard auto); er
lässt sich unter Verwaltung → Plugins → Ressourcenlimits →
Sandbox-Modus ändern. Der Runner wendet ihn in
applySandbox
an. In jedem Modus gilt: kein direkter IP-Ausgang, der Ressourcenwächter und
die Authentifizierung am Host-Socket.
| Schicht | landlock | basic | bubblewrap | strict |
|---|---|---|---|---|
| Dateisystem | Landlock-Freigabeliste | keine ohne Landlock (nur Dateirechte) | Liste erlaubter Mounts | Liste erlaubter Mounts |
| Netz | -restrict-net + -deny-inet |
-deny-inet |
Netzwerk-Namespace | Netzwerk-Namespace |
| Kernel-seccomp (auch statische Binärdateien) | ja | ja | nein | nein |
LD_PRELOAD-seccomp (interpretierte Plugins ohne subprocess) |
ja | ja | nein | ja |
| Eigener Benutzer | mit on |
ja (auto) |
User-Namespace | User-Namespace |
| Ressourcenlimits | ja | ja | ja | ja |
auto (Standard)¶
Wird einmal, beim ersten Start eines Plugins, zum stärksten Modus
aufgelöst, den der Host unterstützt: landlock, wenn der Kernel Landlock
durchsetzt und die Binärdatei evey-plugin-launcher gefunden wird (neben
der Server-Binärdatei, im $PATH oder unter EVEY_PLUGIN_LAUNCHER); sonst
bubblewrap, wenn sich eine Test-Sandbox tatsächlich aufbauen lässt
(ProbeSandbox),
was in der Praxis Bare Metal oder einen privilegierten Container bedeutet;
sonst basic. Landlock kommt zuerst, weil es keine Privilegien braucht
und unter dem Standard-Sicherheitsprofil von Docker läuft. Der aufgelöste
Modus wird protokolliert und in der Zeile Host-Sandbox unter
Verwaltung → Plugins → Sicherheits-Dashboard angezeigt.
| Bereitstellung | Was auto ergibt |
|---|---|
| Docker auf einem aktuellen Kernel | landlock: Beschränkung pro Pfad, kein direkter Ausgang, gefährliche Systemaufrufe abgelehnt, auch für statische Binärdateien. Der Normalfall. |
| Docker auf einem alten Kernel ohne Landlock, z. B. einem alten NAS | basic: Geheimnisse geschützt und Netz gekappt, aber für alle lesbare Dateien sichtbar. Siehe Plugin-Sandbox einrichten. |
| Bare Metal | bubblewrap: die stärkste Abschottung von Dateisystem und Prozessen, ohne seccomp-Filter für statische Binärdateien. |
landlock¶
- Dateisystem: eine Landlock-Freigabeliste, die der Launcher anwendet.
Systemverzeichnisse (
/usr,/lib,/lib64,/bin,/sbin,/etc,/app) nur lesbar; von/devnurnull,zero,full,randomundurandom, damit die Knoten der Grafikkarte und das Terminal des Servers außer Reichweite bleiben; das Verzeichnis und das Socket-Verzeichnis des Plugins lesbar und beschreibbar; alles andere verweigert, auch/dataund andere Plugins. Ein Plugin mithardware.gpubekommt seinen Render-Knoten als einzige Ausnahme. - Netz:
-restrict-netund-deny-inet; Ausgang nur überFetch. - Systemaufrufe: der Kernel-Filter und der
LD_PRELOAD-Filter. - Voraussetzung: ein Kernel 5.13 oder neuer mit Landlock in der Liste
der aktiven LSM, und der Launcher (beides trifft auf das offizielle Image
mit einem aktuellen Host zu). Ein ausdrücklich gesetztes
landlockstartet kein Plugin, das es nicht isolieren kann.
bubblewrap¶
- Dateisystem und Prozesse: sechs abgetrennte Namespaces (
user,pid,uts,ipc,cgroup,net) und eine Liste erlaubter Mounts; sonst gibt es darin nichts (buildBwrapCmdinpluginrunner/runner_bwrap.go).
| Pfad | Zugriff |
|---|---|
/usr, /bin, /sbin, /lib, /lib64 |
nur lesen |
/etc/ssl, /etc/ca-certificates, /etc/resolv.conf, /etc/nsswitch.conf, /etc/{alpine,os}-release |
nur lesen (nie /etc als Ganzes: kein passwd, shadow, ssh) |
| das Verzeichnis des Plugins, sein Socket-Verzeichnis | lesen und schreiben |
/proc |
ein eigener Mount |
/dev |
ein minimaler künstlicher Satz (null, zero, random, urandom, tty, pts) |
/tmp |
ein leeres tmpfs |
/data, /var, /home, /root, /run und die Verzeichnisse anderer
Plugins existieren darin nicht. Ein Canary-Plugin
(tests/fixtures/plugins/canary-sandbox-runtime/) prüft diese Anordnung
gegen einen laufenden Stack.
- Netz: der Netzwerk-Namespace, für jedes Plugin.
- Systemaufrufe: kein seccomp-Filter; die Namespaces sorgen für die
Abschottung.
- Voraussetzung: die Binärdatei bwrap und die Systemaufrufe
mount/unshare. In Docker braucht das ein gelockertes
Sicherheitsprofil, praktisch ist das also ein Modus für Bare Metal.
strict¶
bubblewrap plus der LD_PRELOAD-Filter (nur interpretierte Plugins).
basic¶
Der letzte Rückfallmodus von auto: der eigene Benutzer, der Kernel-Filter, der
LD_PRELOAD-Filter, -deny-inet und Landlock, wenn der Kernel es hat. Auf
einem Kernel ohne Landlock gibt es keine Beschränkung pro Pfad: Ein Plugin
kann nicht sensible, für alle lesbare Dateien lesen und die Prozessliste
sehen. Die Geheimnisse des Servers bleiben durch den eigenen Benutzer und
PR_SET_DUMPABLE geschützt, das Netz ist gekappt, und gefährliche
Systemaufrufe werden abgelehnt, auch für kompilierte Binärdateien.
Konfiguration¶
| Variable | Standard | Wirkung |
|---|---|---|
EVEY_PLUGINS_SANDBOX_MODE |
auto |
auto, landlock, bubblewrap, strict oder basic. Auch unter Verwaltung → Plugins änderbar. |
EVEY_PLUGINS_UID_SEPARATION |
auto |
auto, on oder off. Nicht vereinbar mit no_new_privs auf dem Container. |
EVEY_PLUGINS_COSIGN_POLICY |
warn |
off, warn oder enforce. |
EVEY_PLUGINS_COSIGN_PUBLIC_KEY |
keiner | Optional: ein öffentlicher PEM-Schlüssel, der beim Start zu den vertrauenswürdigen Herausgebern hinzugefügt wird. |
EVEY_PLUGINS_MEMORY_MB, EVEY_PLUGINS_PIDS_MAX, EVEY_PLUGINS_DISK_MAX_MB, EVEY_PLUGINS_CPU_NICE |
256, 64, 100, 10 | Standardwerte der Ressourcenlimits beim Start; der Verwaltungsbereich überschreibt sie, für alle Plugins zugleich. |
Das offizielle Image bringt evey-plugin-launcher (Landlock und den
Kernel-Filter) und bubblewrap mit. bubblewrap braucht außerdem User-
Namespaces ohne Privilegien auf dem Host. Welche Einrichtung zu welcher
Maschine passt, in einfachen Worten:
Plugin-Sandbox einrichten.
Bekannte Grenzen¶
| Grenze | Auswirkung | Was sie heute begrenzt |
|---|---|---|
Keine Beschränkung pro Pfad in basic auf einem Kernel ohne Landlock |
Ein Plugin kann nicht sensible, für alle lesbare Dateien und die Verzeichnisse anderer Plugins lesen | Geheimnisse geschützt durch den eigenen Benutzer und PR_SET_DUMPABLE; auto bevorzugt Landlock oder bubblewrap |
| Alle Plugins teilen sich den Benutzer 1001 | Im Modus basic kann ein Plugin Dateien lesen, die anderen Plugins gehören |
Signale an andere Prozesse sind blockiert (seccomp, LD_PRELOAD und bubblewrap blockieren sie alle); PR_SET_DUMPABLE verhindert das Lesen anderer Prozesse über /proc; Landlock und bubblewrap trennen die Plugin-Verzeichnisse |
| Seccomp ist eine Sperrliste | Datei- und Unix-Socket-Aufrufe bleiben erlaubt | Pfade sind durch Landlock oder bubblewrap beschränkt; IP-Sockets werden abgelehnt |
| Der Vermittler verlagert die SSRF-Angriffsfläche auf den Host | Ein Fehler in der Prüfung des Vermittlers könnte eine interne Adresse erreichen | Freigabeliste und SSRF-sicherer Client in Go; eine Ausgangsregel auf dem Container als unabhängige Schicht |
| Der Vermittler bestimmt, wohin ein Aufruf geht, nicht was er mitnimmt | Daten können zu einem Host auf der Liste des Plugins abfließen | Erlauben Sie nur Hosts, denen Sie diese Daten anvertrauen |
| Die Analyse arbeitet mit Mustern | Absichtliche Verschleierung kann Code verstecken | Vorschau für den Administrator; die Grenze ist die Sandbox, nicht die Analyse |
LD_PRELOAD deckt nur dynamische, interpretierte Plugins ab |
Eine statische Binärdatei in basic ohne Launcher entgeht ihm |
Das offizielle Image bringt immer den Launcher mit, dessen Kernel-Filter statische Binärdateien abdeckt |
Erreichbarkeit im Netz (außerhalb des Bedrohungsmodells für Plugins)¶
Zwei feste Regeln bestimmen, wer den Server überhaupt erreichen kann:
- Nur der HTTP-Port ist zum Veröffentlichen gedacht. Der gRPC-Port
(
EVEY_SERVER_PORT, Standard 9090) ist ein interner Transport, dessen einziger Client das HTTP-Gateway im selben Prozess ist; die HTTP-API liegt auf diesem Port plus eins. gRPC bindet standardmäßig an Loopback (EVEY_GRPC_BIND=127.0.0.1), weil die Sperre für den Fernzugriff eine HTTP-Middleware ist und gRPC nicht abdeckt. Wer die Bindung erweitert, bekommt eine Warnung im Protokoll; ihn per Firewall zu schützen ist dann Aufgabe des Betreibers. - Die Sperre für den Fernzugriff entscheidet anhand der einen
Client-Adresse, die an der Grenze des vertrauenswürdigen Proxys geprüft
wurde, nie anhand eines Anfrage-Headers. Ein Client kann sich nicht über
X-Forwarded-Forals127.0.0.1ausgeben, und eine Anfrage, die diese Grenze nie überquert hat, gilt als entfernt. Das ist eine Frage der Bereitstellung, keine Authentifizierung.
Siehe Fernzugriff und HTTPS und Server und Netzwerk.
Was die Protokolle enthalten dürfen¶
Das Serverprotokoll speist Protokolle & Debug, Fehlerberichte und
Sicherungen. Deshalb werden Zugangsdaten schon beim Schreiben einer Zeile
entfernt (internal/pkg/logger/redact.go): Ein Attribut, dessen Schlüssel
ein Geheimnis benennt (authorization, token, api_key, password,
pin, secret...), wird ganz ersetzt, und jede Zeichenkette mit einem
Bearer-Token, einem JWT oder einem Paar secret=value wird bereinigt. Die
Protokoll-Routen wenden dieselbe Regel beim Ausgeben auf ältere Zeilen an.
Für den Code heißt das: Schreiben Sie nie einen nicht geheimen Wert unter
einem Schlüsselnamen für Geheimnisse ins Protokoll (er wird maskiert), und
tragen Sie eine neue Art von Zugangsdaten in diese Liste ein, bevor sie je
protokolliert wird.
Eine Schwachstelle melden¶
Melden Sie vertraulich, nie in einem öffentlichen Issue:
security@eveyproject.org, die Adresse aus der
Sicherheitsrichtlinie des Repositorys. Sie gilt für die
Authentifizierung von Plugins, Berechtigungen und Ausbrüche aus der Sandbox
wie für alles andere. Testen Sie nicht gegen Evey-Instanzen, die Ihnen nicht
gehören.