MIDGARDAudhumbla 0.7

Kapitel 11: Sicherheit & Stabilität

Sicherheit ist eines der wichtigsten, aber am wenigsten verstandenen Themen für LPC-Anfänger. In diesem Kapitel lernst du Schritt für Schritt, warum UID und EUID existieren, wie die Mudlib dich schützt und wie du selbst sicheren Code schreibst, ohne Angst haben zu müssen, etwas „kaputt zu machen“.

Weiter zu Kapitel 12

Best Practices, typische Fehler & saubere LPC-Architektur

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