====== Capabilities (LCBS) ====== Lyx bindet jedes Programm an ein **Capability-Modell**: Der Compiler erzeugt aus den deklarierten Berechtigungen einen seccomp-Filter, der zur Laufzeit jeden nicht angeforderten Systemaufruf hart abbricht. Das Modell ist **Zero-Privilege mit Default-Deny** — was nicht ausdrücklich gewährt wurde, ist verboten. Ohne Deklaration läuft ein Programm ungehärtet: der Compiler meldet ''Capability-Modell: NONE'' und erzeugt keinen seccomp-Filter. → [[lyx_-_programmiersprache:sprache:module-und-importe|Module und Importe]] · [[lyx_-_programmiersprache:sprache:ffi|FFI]] · [[lyx_-_programmiersprache:guides:do-178c|DO-178C]] ---- ===== 1. Deklaration ===== ''@capabilities'' steht vor allen anderen Deklarationen der Datei: @capabilities([system.exit, system.memory.heap]) import std.io; fn main(): int64 { PrintLn("Hallo"); return 0; } Mehrere Berechtigungen werden mit Komma getrennt. Alternativ erlaubt ''@capabilities(dynamic)'' die Auflösung zur Laufzeit. Ein unbekannter Name ist ein Compile-Fehler: @capabilities([zzz.erfunden]) → sema error: unbekannte Capability ---- ===== 2. Verfügbare Capabilities ===== ^ Bereich ^ Capability ^ Zweck ^ | System | ''system.exit'' | Prozess beenden | | | ''system.env'' | Umgebungsvariablen lesen | | | ''system.time'' | Systemzeit | | | ''system.rand'' | Zufallszahlen | | | ''system.memory.heap'' | Heap-Allokation (''brk'', ''mmap'') | | | ''system.memory.stack'' | Stack-Wachstum | | | ''system.unsafe.format_string'' | Format-String-FFI (''printf''-Klasse) | | Dateisystem | ''fs.read'' / ''fs.write'' | Lesen / Schreiben | | | ''fs.create'' / ''fs.delete'' | Anlegen / Löschen | | | ''fs.meta'' | Metadaten lesen (''stat'') | | | ''fs.perm'' | Metadaten **schreiben** (''chmod'', ''chown'') | | | ''fs.exec'' | Ausführbar öffnen | | Netzwerk | ''network.tcp.connect'' | Ausgehende TCP-Verbindung | | | ''network.tcp.bind'' | TCP-Port belegen (Server) | | | ''network.udp.connect'' / ''network.udp.bind'' | UDP ausgehend / gebunden | | | ''network.unix'' | Unix-Domain-Sockets | | | ''network.raw'' | Raw-Sockets | | Prozesse | ''process.exec'' | Programm starten | | | ''process.fork'' | Prozess duplizieren | | | ''process.exit'' | Prozess beenden | | | ''process.sched'' | Scheduling beeinflussen | | | ''process.signal'' | Signale senden — **derzeit nicht deklarierbar**, siehe unten | Das sind alle 25 gültigen Namen. Jeder andere wird mit ''unbekannte Capability'' abgewiesen. > **''process.signal'' lässt sich nicht schreiben.** Der Parser bricht mit ''expected ], got .'' ab, weil ''signal'' ein reserviertes Wort ist. Die Capability existiert im Compiler, ist über die Deklaration aber nicht erreichbar. > ([[https://github.com/SEOLizer/LyX-Compiler/issues/1198|Issue #1198]]) ==== Immer aktiv ==== Drei Capabilities sind implizit gesetzt, weil ohne sie kein Programm startet: ^ Capability ^ Syscalls ^ | ''system.exit'' | ''exit_group'' | | ''system.memory.heap'' | ''brk'', ''mmap(MAP_ANON)'' | | ''system.rand'' | ''getrandom'' (Initialisierung der Stack-Canaries) | ---- ===== 3. grant und restrict an Imports ===== Jeder ''import'' kann angeben, welche Berechtigungen die eingebundene Unit erhalten soll. ''grant'' zählt auf, was weitergegeben wird, ''restrict'' nimmt zurück: @capabilities([system.exit, system.memory.heap, fs.read]) import std.io grant [system.exit]; import std.fs grant [fs.read]; Beides lässt sich am selben Import kombinieren, und mehrere Imports stehen wahlweise in einer Deklaration: import std.io grant [system.exit], std.string grant [system.exit]; Eine Unit kann nur bekommen, was das aufrufende Modul selbst besitzt. Diese Prüfung greift zur Übersetzungszeit: sema error: grant enthält Capability die nicht in Parent-Capabilities ist > **Die Eindämmung wird nicht durchgesetzt.** Die Angaben werden geprüft, beschränken die importierte Unit aber nicht: Selbst ''%%import std.fs grant [];%%'' — der Unit wird nichts gewährt — liest anschließend anstandslos Dateien. Der erzeugte seccomp-Filter ist in allen Fällen identisch (71 Regeln), ''grant''/''restrict'' fließen also nicht ein. > > Wirksam ist allein die **Modulebene**: Fehlt ''fs.read'' im ''@capabilities'' der Datei, bricht derselbe Zugriff mit Exit 159 ab. Das Zero-Privilege-Modell steht — es endet an der Unit-Grenze. > > Zu beachten beim **Sicherheits-Score**: Er vergibt +10 für „alle Imports haben explizites grant". Dieser Posten beschreibt bis auf Weiteres eine Absicht, keine Eindämmung. > ([[https://github.com/SEOLizer/LyX-Compiler/issues/1231|Issue #1231]]) ---- ===== 4. @uses_caller_cap ===== Eine Funktion, die eine Berechtigung des Aufrufers benötigt, deklariert das ausdrücklich. Geprüft wird gegen die **aufrufende Funktion** — diese muss die Berechtigung selbst per ''@capabilities'' tragen. Die Angabe am Dateikopf genügt dafür nicht: @capabilities([system.exit, system.memory.heap, fs.read]) import std.io; @uses_caller_cap([fs.read]) fn LiesKonfiguration(): int64 { return 0; } @capabilities([fs.read]) fn main(): int64 { PrintLn(IntToStr(LiesKonfiguration())); return 0; } Fehlt das ''@capabilities'' an ''main'', wird der Aufruf abgelehnt: sema error: Caller hat nicht: fs.read (benötigt von @uses_caller_cap) Das gilt auch für implizite Capabilities: ''@uses_caller_cap([system.exit])'' verlangt ebenfalls ein ausdrückliches ''@capabilities([system.exit])'' an der aufrufenden Funktion. ---- ===== 5. FFI ist Fail-Closed ===== ''extern fn'' ohne passende Capability wird abgewiesen — nicht gewarnt, sondern abgelehnt: sema error: extern fn: unbekanntes FFI-Symbol erfordert @capabilities([...]) (FFI-Sandbox Fail-Closed) Ein FFI-Aufruf ist damit immer eine bewusste, sichtbare Entscheidung. ---- ===== 6. Sicherheits-Score ===== Jeder Build gibt eine Bewertung aus. Sie zeigt unmittelbar, was die Deklaration bewirkt: # ohne @capabilities Capability-Modell: NONE (kein @capabilities -- keine LCBS-Haertung) Sicherheits-Score: 33/45 + 0: seccomp fehlt +8: Grant-Modell -- 1 Import(e) ohne grant (-2 pro fehlendem grant) # mit @capabilities und vollständigen grants + seccomp (SECCOMP_RET_KILL_PROCESS, 53 Regeln) Sicherheits-Score: 45/45 +10: Grant-Modell -- alle Imports haben explizites grant + 5: seccomp aktiv Weitere Posten: W^X (getrennte PT_LOAD-Segmente), RELRO, und der Nachweis, dass keine Klasse-3-Extern-Funktion ohne ''system.unsafe.format_string'' verwendet wird. PIE ist derzeit nicht implementiert (+0). ---- ===== 7. Wirkung zur Laufzeit ===== Der erzeugte seccomp-Filter arbeitet mit ''SECCOMP_RET_KILL_PROCESS''. Ein nicht gewährter Systemaufruf beendet den Prozess sofort: @capabilities([system.exit, system.memory.heap]) import std.fs; fn main(): int64 { if (FileExists("/etc/hostname"c)) { return 1; } return 0; } $ ./programm Ungültiger Betriebssystemaufruf (Speicherabzug geschrieben) $ echo $? 159 Mit ''fs.read'' in der Liste läuft dasselbe Programm durch. ---- ===== 8. Einschränkungen im aktuellen Stand ===== Eine Capability kann **Argumente** tragen, die ihren Geltungsbereich einschränken: @capabilities([system.exit, system.memory.heap, fs.read(path: "/tmp")]) Der Argumentname wird geprüft — ein Tippfehler wie ''pfad:'' wird mit ''unbekanntes Capability-Argument'' abgewiesen. **''path:'' an einer fs-Capability wird seit lyxc 1.0.15E durchgesetzt.** Der Compiler legt je genanntem Pfad eine eigene Landlock-Regel an, mit genau den Zugriffsarten der Capability, zu der das Argument gehört. Ein Lesen außerhalb schlägt zur Laufzeit fehl: @capabilities([system.exit, system.memory.heap, fs.read(path: "/tmp")]) // ReadFile("/etc/hostname"c, …) -> -1; mit fs.read(path: "/etc") -> gelesene Bytes Zwei Regeln dazu: * Die pauschale ''"/"''-Regel entfällt genau dann, wenn **alle** fs-Capabilities einen Pfad tragen. Trägt auch nur eine keinen, bleibt sie stehen — ein einzelnes ''fs.write'' ohne ''path:'' hebt die Beschränkung der übrigen also faktisch auf. * Existiert ein genannter Pfad nicht, wird keine Regel angelegt und der Zugriff bleibt verboten (fail-closed). > **Nicht durchgesetzt werden ''host:'' und ''port:'' an Netzwerk-Capabilities sowie ''path:'' an ''fs.perm''.** Dort wird nur der Argumentname geprüft; der Geltungsbereich dokumentiert die Absicht, erzwingt sie aber nicht — die Capability wirkt für die Zugriffsentscheidung als reines Ja/Nein. Der Compiler weist auf jede solche Stelle mit ''Capability-Argument wird NICHT durchgesetzt'' hin. > ([[https://github.com/SEOLizer/LyX-Compiler/issues/1108|Issue #1108]]) ---- **Weiterführende Seiten:** * [[lyx_-_programmiersprache:sprache:module-und-importe|Module und Importe — grant/restrict im Kontext]] * [[lyx_-_programmiersprache:sprache:ffi|FFI — externe Funktionen einbinden]] * [[lyx_-_programmiersprache:sprache:attributes-pragmas|Attribute und Pragmas]]