11.1 Warum Sicherheit im MUD überhaupt nötig ist
In einem Singleplayer-Programm ist Sicherheit oft nebensächlich.
In einem MUD wie dem Midgard laufen jedoch hunderte oder tausende Objekte
gleichzeitig – geschrieben von vielen verschiedenen Magiern.
Jedes Objekt kann prinzipiell mit jedem anderen Objekt interagieren.
Ohne Sicherheitsmechanismen könnte:
- jedes Objekt beliebige andere Objekte zerstören
- Spielerwerte manipuliert werden
- Quests gefälscht oder zurückgesetzt werden
- Spieler teleportiert, gefesselt oder blockiert werden
- der gesamte MUD instabil werden
Deshalb gibt es im Midgard MUD ein ausgefeiltes Sicherheitsmodell, das auf
UID, EUID und klaren Verantwortlichkeiten basiert.
Als Anfänger musst du dieses Modell nicht bis ins letzte Detail beherrschen,
aber du musst die Grundidee verstehen. Lies dazu auch die
[ Regeln ]
des MUDs — vor allem die Abschnitte zu
[ Fehlerausnutzung ].
Wenn du im Code einen Bug entdeckst, hilft dir
[ bug ]
bzw.
[ fehler ],
ihn zu melden.
11.2 UID – Die Identität eines Objekts
Die UID (User ID) eines Objekts sagt:
„Zu wem gehört dieser Code?“
Im Midgard MUD ist die UID normalerweise an den Verzeichnispfad gekoppelt.
Ein Objekt unter /players/alice/ hat in der Regel die UID alice.
Wichtige Eigenschaften der UID
- Die UID wird vom Driver/Master gesetzt
- Sie ist nicht frei änderbar
- Sie bestimmt Besitz und Verantwortlichkeit
- Sie ist Grundlage für viele Sicherheitsprüfungen
Als Faustregel:
Die UID sagt, wem der Code gehört – nicht, was er darf.
Beispiel: UID abfragen
// Debug-Ausgabe der UID eines Objekts
write("UID: " + getuid(this_object()) + "\n");
Als Anfänger wirst du getuid() selten brauchen,
aber es ist wichtig zu wissen, dass diese Identität existiert.
11.3 EUID – Die effektive Berechtigung
Die EUID (Effective User ID) ist entscheidender als die UID.
Sie beantwortet die Frage:
„Mit welchen Rechten handelt dieses Objekt gerade?“
Ein Objekt kann Code „im Auftrag“ einer anderen UID ausführen.
Genau das ermöglicht sichere Delegation.
Vergleich aus der realen Welt
- UID: Dein Name im Ausweis
- EUID: Dein Dienstausweis für eine bestimmte Aufgabe
Beispiel:
Ein Standardobjekt gehört dem Magier alice,
darf aber zeitweise mit der EUID root arbeiten,
um eine kontrollierte Aufgabe auszuführen.
EUID abfragen
// Effektive UID anzeigen
write("EUID: " + geteuid(this_object()) + "\n");
In der Praxis wird die EUID meist automatisch von der Mudlib gesetzt.
Als Anfänger solltest du sie fast nie selbst ändern.
11.4 Warum es gefährlich wäre, alles zu erlauben
Wenn du die Konsequenzen einmal durchspielst, wird der Sinn des Sicherheitsmodells
sofort klar. Stell dir vor, jedes Objekt dürfte jederzeit beliebige Dinge tun:
destruct(other_object); // Objekt zerstören
other_object->SetProp(P_LEVEL, 100); // Werte ändern
clone_object("/secure/master"); // Master clonen
write_file("/secure/passwd", "haha"); // Passwörter überschreiben
Was wären die Folgen — selbst ohne böse Absicht?
- Ein Bug könnte versehentlich einen Spieler zerstören
(
destruct(zufaelliges_objekt) ruft auch Spielerobjekte auf!).
- Ein fehlerhaftes Item könnte Quest-Status durcheinanderwürfeln,
weil es einfach Properties anderer Objekte überschreibt.
- Ein böswilliges Objekt (auch versehentlich „böswillig“ durch
einen schlecht geschriebenen Trigger) könnte massiven Schaden anrichten:
Spielergüter zerstören, Eintragungen verfälschen, andere Objekte durch Endlosschleifen
lahmlegen.
- Spieler-Vertrauen würde verloren gehen — niemand möchte ein MUD,
in dem ein Item den eigenen Charakter beschädigen kann.
- Gemeinschaftliche Entwicklung wäre nicht möglich: Jeder Magier
könnte unbeabsichtigt die Arbeit anderer kaputtmachen.
Deshalb prüft der Driver bei vielen Operationen:
„Darf dieses Objekt das gerade tun?“
— und nutzt dafür UID/EUID. Das macht das System nicht 100 % unangreifbar
(kein System ist das), aber es errichtet Hürden, die für gutgläubige Bugs
und versehentliche Fehler ausreichen.
Konkret läuft das so: Bei einer riskanten Operation ruft der Driver eine Funktion
im Master-Objekt (z.B. privilege_violation(), valid_write()),
die mit „ja“ oder „nein“ antwortet. Sagt der Master „nein“, gibt es einen Runtime-Error
oder die Funktion liefert einfach 0 zurück.
11.5 Typische sicherheitsrelevante Operationen
Bestimmte Aktionen gelten als „gefährlich“ und sind besonders geschützt. Wenn du
sie aufrufst, prüft der Driver dein Recht dazu — und blockiert die Aktion, falls
nicht erlaubt. Hier die wichtigsten:
destruct() — Objekte komplett zerstören.
Der Driver prüft, ob du den Master-Status hast oder das eigene Objekt destruieren willst.
move() — Objekte zwischen Containern bewegen.
Räume, Spieler und Container haben Schutzfunktionen, die das verhindern können.
seteuid() — Effektive UID ändern.
Geht meistens nur über den Master oder mit Privilegien.
set_driver_hook() — Driver-Hooks setzen.
Meist nur dem Master erlaubt.
shadow() — Funktionen anderer Objekte überschreiben.
Das Ziel-Objekt hat ein Veto-Recht über query_allow_shadow().
call_other() auf sensitive Objekte
(z.B. /secure/master, /secure/login).
- Zugriff auf
/secure/ per
read_file() oder write_file() — der Master prüft jeden
Pfad.
shutdown(), send_udp(),
set_extra_wizinfo() und andere mit privilegiertem Charakter.
- Datei-Operationen außerhalb deines Bereichs:
write_file("/players/anderer/...", ...) wird vom Master abgelehnt.
Wenn du als Anfänger versuchst, so etwas „einfach zu machen“,
wirst du oft kryptische Fehlermeldungen sehen wie
„privilege violation: rename_object“ oder einfach 0 als Rückgabewert
ohne Erklärung. Das ist kein Bug — das ist Schutz.
Deine Aufgabe ist es dann, den Master-Eintrag (siehe /doc/master/) zu lesen
und zu verstehen, warum dein Aufruf abgelehnt wurde.
Was tun, wenn du etwas Privilegiertes brauchst?
Wenn du wirklich eine privilegierte Aktion brauchst (z.B. ein Tool, das Logfiles
anderer Magier lesen darf), gibt es zwei legitime Wege:
- Frage einen Erzmagier (EM), ob dein Objekt eine spezielle EUID bekommen kann.
- Lege das Objekt unter
/p/service/ oder /p/daemon/ ab —
dort haben Objekte oft erweiterte Rechte (aber Code-Review ist Pflicht).
Was du nicht machen sollst: das Sicherheitssystem zu „umgehen“ versuchen.
Das fällt im Code-Review auf, und es führt zu den Bugs, die das System eigentlich
verhindern soll.
11.6 Defensive Programmierung: Dein bester Schutz
Sicherheit bedeutet nicht nur UID/EUID,
sondern auch robusten Code.
Grundregeln
- Vertraue niemals Eingaben blind
- Prüfe
objectp(), mappingp(), pointerp()
- Rechne damit, dass Objekte verschwinden
- Baue klare Abbruchbedingungen ein
Beispiel: Unsicherer Code
// UNSICHER
void do_something(object ob) {
ob->SetProp(P_HP, 0);
}
Sichere Variante
// SICHERER
void do_something(object ob) {
if (!objectp(ob)) return;
if (!living(ob)) return;
if (!interactive(ob)) return;
ob->SetProp(P_HP, 0);
}
Defensive Checks sind kein Overhead – sie sind Pflicht in einer
verteilten, persistenten Spielwelt.
11.7 Zugriff kontrollieren: Wer darf eine Funktion nutzen?
Häufig willst du verhindern, dass „irgendwer“ eine Funktion aufruft.
Typisches Beispiel: Debug- oder Admin-Funktionen.
Einfache Prüfung
void reset_room() {
if (!this_player() || !IS_ARCH(this_player())) {
write("Das darfst du nicht.\n");
return;
}
// Reset-Logik
}
Wichtig:
Verlasse dich nicht darauf, dass eine Funktion „schon niemand ruft“.
In LPC kann jedes Objekt prinzipiell jede Funktion aufrufen,
sofern sie nicht geschützt ist.
11.8 Häufige Anfängerfehler bei Sicherheit
Fehler 1: „Das sieht keiner“
Falsch. Objekte können Funktionen direkt aufrufen,
auch ohne Spielerbefehl.
Fehler 2: setuid/seteuid ohne Verständnis
Das ist extrem gefährlich.
Als Anfänger: Finger weg, außer ein Mentor sagt dir explizit,
was du tun sollst.
Fehler 3: Vertrauen in fremde Objekte
Nur weil ein Objekt „offiziell“ aussieht,
heißt das nicht, dass es sich korrekt verhält.
Prüfe immer, was du bekommst.
Fehler 4: Keine Fehlerbehandlung
catch() ist dein Freund, wenn du fremden Code aufrufst.
11.9 catch() – Sicherheit bei fremdem Code
Wenn du Code aufrufst, den du nicht kontrollierst,
solltest du ihn absichern.
// Sicherer Funktionsaufruf
mixed res;
if (catch(res = other_object->DoSomething())) {
write("Ein Fehler ist aufgetreten.\n");
return;
}
Ohne catch() könnte ein Laufzeitfehler
dein Objekt oder den Raum lahmlegen.
11.9a Was der Master für dich entscheidet
Wenn dein Code etwas Sicherheitsrelevantes macht (Datei lesen, Datei schreiben,
ein Objekt shadowen, einen Hook eintragen, …), fragt der Driver das Master-Objekt.
Du musst diese Master-Funktionen nicht selbst schreiben – aber du solltest wissen,
warum bestimmte Aufrufe schweigend scheitern oder einen privilege violation-Fehler werfen.
valid_read(path, uid, fun, ob) – darf ob in path lesen?
valid_write(path, uid, fun, ob) – darf ob in path schreiben?
privilege_violation(op, who, arg, arg2) – bei „gefaehrlichen“ Operationen
(set_driver_hook, bind_lambda, send_udp,
shadow_add_action, shutdown, …)
query_allow_shadow(victim) – bevor ein Objekt geshadowed wird
Konsequenz für dich: Wenn ein save_object() in deinem Code stillschweigend
nichts tut, liegt das oft an einer falschen UID/EUID des aufrufenden Objekts –
siehe /doc/master/valid_write.
11.9b secure/-Verzeichnisse für eigene Quests
Magier duerfen unter /d/<region>/<magier>/secure/ Verzeichnisse anlegen,
die andere Magier nicht lesen können. Das ist gedacht für Quest-Lösungen,
Raetsel und schwer balancierte NPCs.
- Leserechte hat nur, wer dort auch Schreibrechte hat.
- Achtung: Unterverzeichnisse von
secure/ haben keinen Schutz.
Schuetze pro Datei, nicht pro Pfadbaum.
- Nicht ganze Gebiete dort ablegen – sonst kann dir niemand mehr helfen, wenn etwas hakt.
Empfohlenes Setup über zentrale Defines:
#define HOME(x) "/d/region/magier/meingebiet/"+x
#define NPC(x) HOME("npc/"+x)
#define OBJ(x) HOME("obj/"+x)
#define ROOM(x) HOME("room/"+x)
#define SECURE(x) HOME("secure/"+x) // hier liegen z.B. Lösungen
11.9c Effizienz ist auch Sicherheit (Ticks & TLE)
Der Driver gibt jedem Ausführungsthread ein Tick-Budget (zur Zeit ca. 1.500.000 Ticks).
Lange Schleifen, viele call_other() oder rekursive Aufrufe knacken das Limit
– dann gibt es einen „too long evaluation“-Fehler (TLE), und dein Code bricht
mitten in der Aktion ab. Im schlimmsten Fall fehlt dann z.B. eine Aufräumphase.
Anfänger-Reflexe gegen TLE:
- Schleifen klein halten; große Mengen in
call_out-Stücke aufteilen.
get_eval_cost() messen, wenn du eine teure Funktion testest.
- Pro Heartbeat möglichst wenig tun –
set_heart_beat() abschalten,
wenn der NPC alleine im Raum steht.
- NICHT per
limited() kuenstlich „schuetzen“ –
limited() kann nur reduzieren, nicht erhoehen, und macht das Problem oft schlimmer.
11.10 Zusammenfassung
Sicherheit im Midgard MUD ist kein Selbstzweck.
Sie ermöglicht, dass viele unabhängige Entwickler
gemeinsam an einer stabilen Welt arbeiten können.
- UID sagt, wem Code gehört
- EUID sagt, was ein Objekt darf
- Viele Operationen sind bewusst eingeschränkt
- Defensive Programmierung ist Pflicht
- Vertraue nie blind fremdem Code
Wenn du diese Prinzipien beachtest, wirst du nicht nur
sichereren Code schreiben, sondern auch weniger
schwer auffindbare Bugs produzieren.
Weiter zu Kapitel 12
Im letzten Kapitel fassen wir alles zusammen:
Best Practices, saubere Architektur, typische Anfängerfehler
und wie du langfristig guter LPC-Code schreibst.
Kapitel 12 lesen
Best Practices & saubere LPC-Architektur