Aus Linux-Magazin 04/2010

Shellskripte aus der Stümper-Liga - Folge 7: Mangelnde Ressourcenkontrolle

Nicht nur Programmierer stellen Unsinn mit der Bash an. Wenn perfide Betriebsstörer den Rechnerbetrieb lahmlegen hat der verantwortliche Systemverwalter vorher zuviel Nachsicht walten lassen. Lästigen Denial-of-Service-Attacken kann er nämlich vorbauen.

Linux-Systeme gelten als stabil. Um eins abstürzen zu lassen, muss sich ein Angreifer schon mit handoptimiertem Maschinencode [1] auskennen und dazu möglichst Rootrechte besitzen. Das zumindest denken viele Admins. In Wirklichkeit bringt ein Bash-Skript von 6 Byte so maches System in die Knie, wenn sein Verwalter nicht präventive Maßnahmen trifft (siehe Listing 1). Daher sollte niemand, der sich nicht den berechtigten Zorn der Kollegen zuziehen will, der wichtige ungespeicherte Dokumente geöffnet hat oder der mit dem Rechner die Brennstäbe eines Kernkraftwerks steuert, die Kommandosequenz abtippen.

Listing 1: Explosive
Fork-Bombe

01 $> PATH=.
02 $> echo 'a & a' > a
03 $> chmod 755 a; /bin/bash a

Das Programm kommt einem Virus nahe, trägt es doch manch ähnlichen Wesenszug: Es repliziert sich rasend schnell und saugt damit Schritt für Schritt die Lebensenergie aus einem vormals gesunden Rechner. Der Echo-Befehl schreibt ein kurzes Programm in die Datei »a«: Als erstes ruft es sich selbst auf, allerdings im Hintergrund, gekennzeichnet durch den Ampersand »&«. Der ist in der Bash gleichzeitig auch ein Trennzeichen, ganz ähnlich wie das Semikolon – eben nur mit dem Unterschied, dass der vor dem Separator stehende Prozess nicht erst terminieren muss, bevor die Shell den nächsten beginnt. Im Anschluss startet die Bash »a« noch ein zweites Mal. Da dieser Prozess nicht im Hintergrund läuft, beendet sich der erste Aufruf erst, wenn die zweite Neuinkarnation terminiert. Die ihrerseits beendet sich erst, wenn ihr zweiter Kindprozess terminiert und so weiter – also effektiv nie.

Pyramidenbau

Damit laufen nach dem ersten Durchlauf des Skriptes drei Prozesse, im zweiten sieben, 15 in der dritten Iteration und so weiter. Ein Blick auf Abbildung 1 zeigt die Ausrüstung eines typischen Desktop-PC: Core-2-CPU mit 3,0 GHz, 2 GByte Hauptspeicher, von denen nach dem Start von Kubuntu noch rund 1600 MByte frei sind, wenn man den entbehrlichen Plattencache einrechnet. Dank dynamischer Bibliotheken und moderner Speicherverwaltung teilen sich zwar die meisten »a«-Prozesse die Libc und den größten Teil des Bash-Binarys, aber jeder einzelne Prozess belegt auch rund 2 MByte an individuellen Tabellen im Kernel und prozessspezifischen Variablen im Userspace. Hier wirkt sich vor allem die das exponentielle Wachstum der Zahl an Tasks aus, wie Abbildung 2 anschaulich illustriert.

Abbildung 1: Das Testsystem für die Fork-Bombe ist eigentlich gut ausgestattet: Die Core-2-CPU mit 3,0 GHz verfügt über 2 GByte Hauptspeicher. Trotzdem zwingt ein Shell-Einzeiler das System binnen Sekunden in die Knie.

Abbildung 1: Das Testsystem für die Fork-Bombe ist eigentlich gut ausgestattet: Die Core-2-CPU mit 3,0 GHz verfügt über 2 GByte Hauptspeicher. Trotzdem zwingt ein Shell-Einzeiler das System binnen Sekunden in die Knie.

Abbildung 2: Kommt das rekursive Skript erst einmal ins Rollen, ist es nur schwer zu stoppen. Selbst ein »killall«-Befehl verpufft mitunter, da neue Prozesse schneller sprießen als das Tool beenden kann.

Abbildung 2: Kommt das rekursive Skript erst einmal ins Rollen, ist es nur schwer zu stoppen. Selbst ein »killall«-Befehl verpufft mitunter, da neue Prozesse schneller sprießen als das Tool beenden kann.

Wie kann es aber sein, dass so ein einfaches Programm ein Linux-System so profan zum Absturz zwingt? Die Antwort lautet, dass das System gar nicht abstürzt, sondern sich einfach unerträglich abbremst. Ist irgendwann der virtuelle Hauptspeicher voll, fängt Linux an zu optimieren, sucht nach kleinen Stückchen freien Speichers. Oft wird es dabei sogar fündig, allein der Aufwand gerät immer unverhältnismäßiger – zumal das virale Skript jeden Speicher sofort verschlingt und doppelt so viel nachfordert.

Emsiges Umsortieren

Irgendwann klappt nicht einmal mehr das und der Systemaufruf »fork()« erhält »EAGAIN« oder »ENOMEM« als Antwort, je nachdem ob gerade dem Userspace oder dem Kernel der Speicher ausgegangen ist: Dann terminiert tatsächlich der betroffene Prozess und gibt seinen Speicher wieder frei. Einer Hydra gleich sprießen jedoch einem seiner vielen Brüder zwei neue Köpfe. Der Anwender stellt das dadurch fest, dass die Load rapide ansteigt, da viel mehr Prozesse zur Ausführung bereit sind, als die CPU-Kerne abzuarbeiten in der Lage sind. Genau dieses Verhältnis drückt die Load aus [2].

Noch unmittelbarer ist der Effekt, dass sich Fenster nur noch langsam aufbauen und schließlich sogar die Maus ruckweise bewegt. Das ist insofern bemerkenswert, als dass kurzlaufende, höher priorisierte Interrupt-Routinen die Maus bewegen, zumindest im Vergleich zu erheblich komplexeren Fenster-Aufbauten moderner grafischer Oberflächen.

Rationierung

Nach jeder Katastrophe stellt sich die Frage nach den Verantwortlichen. Wer für den vermutlich notwendigen Kaltstart zur Rechenschaft zu ziehen ist, scheint schwer auszumachen. Die Kernelentwickler könnten die Betriebssystemressourcen etwas konservativer zuteilen und etwas früher neue Anfragen nach Prozessen, CPU und Speicher abschlägig behandeln. Tatsächlich ist ja nicht zuletzt das Optimieren der Restressourcen ein wesentlich Grund für das Malheur. Andererseits rühmt sich – nicht ganz zu unrecht – der Linux-Kern eben für genau diese Effizienz. Daher vergibt er schon einmal etwas mehr Ressourcen, als er wirklich leisten kann. Dieses Überbeanspruchen ist in der Praxis oft nützlich, da nur wenige Prozesse alle angeforderten IT-Rohstoffe wirklich auskosten. Das Beispiel ist schon ein Grenzfall.

Dennoch haben Systemverwalter einige Mittel zur Hand, um die Zuteilung der Rechenleistung zu steuern. Die Systemaufrufe »getrlimit()« und »setrlimit()« beschränken eine Reihe von wichtigen Parametern, wie die Zahl der erlaubten Prozesse oder die Menge an Hauptspeicher pro Prozess (siehe Tabelle 1). Der Mechanismus kennt ein Hard- und ein Softlimit. Nur Root kann die harte Grenze verändern, normale Prozesse dürfen die Werte nur zu ihren Ungunsten verschieben. Diese Einstellung nennt der Kern Softlimit. Die Grenzen konfiguriert der Admin für eine laufende Shell mit dem Builtin »ulimit«, alle Kindprozesse erben fortan diese Einstellungen. Alternativ lassen sich unter vielen Distributionen in »/etc/security/limits.conf« Vorgaben festlegen, die für alle Anwender gelten. Der Aufruf »ulimit -a« zeigt den aktuellen Stand an.

Tabelle 1: Auswahl
an limitierbaren Ressourcen

 

Ressource

Beschreibung

Einheit

core

Größe eines Coredumps

KByte

data

Größe des Datenspeichersegmentes eines
Prozesses

KByte

fsize

Dateigröße

KByte

memlock

Größe des im RAM verankerten Speichers

KByte

nofile

Anzahl offener Dateien

Files

rss

RSS Speichergröße

KByte

stack

Größe des Stacks

KByte

cpu

CPU-Zeit

Minuten

nproc

Anzahl an Prozessen pro PID

Prozesse

as

Virtueller Speicher

Bytes

locks

Zahl an Dateisystemsperren

Locks

sigpending

Zahl wartender Signale

Signale

msgqueue

Speicher für Posix-Messagequeues

Bytes

nice

Prozesspriorität (Nice-Wert)

Priorität

Klare Grenzen ziehen

Selbst mit einfachen Shellskripten und reinen Userrechten lässt sich ein Linux-System in die Knie zwingen, indem eine rekursive Fork-Bombe Unmengen an Prozessen erzeugt. Auf einem Einzelplatzsystem trifft das nur denjenigen, der vor dem Rechner sitzt, im Datacenter sind schnell sehr viele Anwender betroffen. Dem beugt der Administrator mit dem Shell-Builtin »ulimit« vor. Eine typische Sitzung eins Desktops besteht aus etwas mehr als 100 Prozessen, mit 200 sollten also die meisten Anwender auskommen. Ähnliche Werte lassen sich für Hauptspeicher und Dateiparameter erheben.

Infos

[1] Nils Magnus, “Bit-Tuning: effizienten Assembler-Code erzeugen”, Linux-Magazin 02/10, S. 112.

[2] Dr. Neil Gunther, “Leistungsdiagnostik: Load Average enträtselt und erweitert”, Linux-Magazin 08/07, S. 84.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben