====== 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]]