FollowSymLinks vs. SymLinksIfOwnerMatch

11. Juli 2014 von Jonas Pasche

Wir haben bei Uberspace.de Mist gebaut – da gibt es wenig zu beschönigen. Unsere Konfiguration des Apache-Webservers hatte – immer schon – die Option FollowSymLinks standardmäßig aktiviert, damit der Apache, nun ja: Symlinks folgt. Das ist wichtig, weil Symlinks weit verbreitet sind und nicht zuletzt auch die Verzeichnisstrukturen vieler Webapplikationen intensiv auf Symlinks setzen.

tl;dr

  • Unsere Apache-Konfiguration wies eine Konfigurationsschwäche auf, die es über Symlink-Attacken lokalen Usern ermöglichte, Dateien außerhalb des eigenen DocumentRoots einzusehen, sofern jene für den Apache lesbar waren.
  • Das Problem betraf explizit nicht den Inhalt von Home-Verzeichnissen anderer User und auch nicht deren Datenbanken – aber die Inhalte in den DocumentRoots anderer User, sofern die darin abgelegten Dateien world-readable waren.
  • Ebenfalls betroffen war die /etc/shadow, die in unserem Setup zwar nicht für normale User, aber für den Apache-Webserver lesbar und damit auch durch die Symlink-Attacke angreifbar war.
  • Das Problem ist durch eine Policy-Änderung gefixt, die künftig statt Options +FollowSymLinks nur noch Options +SymLinksIfOwnerMatch erlaubt.
  • Wir mussten dazu .htaccess-Dateien von Usern, die Options +FollowSymLinks einsetzen, automatisiert anpassen, damit jene weiterhin funktionieren.

Das würde ich gerne im Detail verstehen.

Symlinks sind vom Rechteaspekt her erstmal kein Problem: Es gelten immer die Berechtigungen des Symlink-Ziels. Setze ich einen Symlink auf eine Datei, die ich nicht lesen darf, und greife dann auf den Symlink zu, kann ich – natürlich – auch weiterhin nicht die Datei lesen, die ich nicht lesen darf. Deshalb stellen Symlinks auch kein Sicherheitsproblem per se dar; spätestens auf der Shell gäbe es auch gar keine Möglichkeit (und auch keinen Anlass), jene zu verhindern.

Im Kontext des Apache-Webservers stellt sich die Situation allerdings etwas anders dar, was wir nicht ausreichend bedacht haben und worauf wir von einem aufmerksamen User hingewiesen wurden. Denn: Auf der Shell habe ich die Berechtigungen des Users, mit dessen Zugangsdaten ich gerade eingeloggt bin, also beispielsweise des User jonas. Dynamische Inhalte innerhalb des Webservers werden durch suEXEC ebenfalls mit den Rechten des jeweiligen Users, also beispielsweise des Users jonas, ausgeführt. Sie können damit also genau das lesen bzw. nicht lesen, was ich auch auf der Shell lesen bzw. nicht lesen kann.

Für die Auslieferung statischer Dateien hingegen gibt es keinen Kontextwechsel vom User apache zu jeweiligen User-ID wie z.B. jonas, das heißt, Dateien die in /var/www/virtual/$USER liegen, müssen world-readable sein, wenn der Apache sie direkt selbst ausliefern können soll, denn die Dateien gehören schließlich alle dem User und der Gruppe dessen, der sie hochgeladen hat – wären sie nicht world-readable, könnte der Apache sie nicht ausliefern. Dass andere User jene Dateien nicht einsehen können, wird dadurch geregelt, dass das Verzeichnis /var/www/virtual/$USER nur für den Eigentümer sowie für den Apache lesbar sind, aber eben nicht für andere User. Jene könnten insofern zwar von den Rechten der Zieldateien her jene lesen, aber da jene in einem Verzeichnis liegen, das ein Weiterkommen verhindert, wird das effektiv verhindert. Das funktioniert auch einwandfrei. (Wir stellen übrigens auch sicher, dass dann, wenn User das Verzeichnis /var/www/virtual/$USER eigenständig world-readable machen und damit die Barriere selbstständig einreißen, jene Berechtigungen automatisch wieder auf den sicheren Default zurückgesetzt werden und der betreffende User informiert wird.)

Und nun kommen die Symlinks ins Spiel. Wir hatten ja eben bereits klargestellt, dass der Symlink rechtemäßig nichts daran ändert, ob ein User auf eine Datei zugreifen kann oder nicht. Nun ist es in diesem Fall aber ja kein normaler Systemuser wie eben beispielsweise jonas, der dem Symlink zu folgen und die Zieldatei zu lesen hat, sondern es ist der User apache, dem es gestattet ist (und sein muss), die für User unzugängliche Barriere /var/www/virtual/$USER zu übergehen.

Das bedeutet im Klartext: Legt ein User A einen Symlink auf eine Datei von User B an, so kann er selbst davon zwar die Datei noch nicht lesen – legt er den Symlink aber innerhalb seines DocumentRoots an und ruft die entsprechende URL auf, die zu diesem Symlink führt, veranlasst er damit den User apache, dem Symlink zu folgen – und der darf das. Damit kann sich ein User indirekt Zugriff auf die Dateien anderer User verschaffen, sofern jene für den Apache lesbar sind, auch wenn der User die Datei selbst nicht lesen darf. Voraussetzung ist aber eben, dass er den Pfad zur Zieldatei kennen muss, denn er muss ja den Symlink erstmal konstruieren.

Das ist schlimm, keine Frage. Dateien anderer User enthalten potentiell sensible und/oder persönliche Daten, die nicht in falsche Hände geraten sollten. Ein zusätzlicher Schmerzpunkt ist, dass auch ein .htaccess-basierter Verzeichnisschutz hier mit diesem Angriffsvektor umgangen werden kann. Das macht diesen Angriff klar zu einem Fehler der Kategorie „OMFG, das hätte uns niemals passieren dürfen“. Es ist aber passiert.

Warum ist das zwar schlimm, aber vielleicht nicht so schlimm?

Es gibt eine Reihe von Aspekten, die eine möglich Panik auf der anderen Seite auch wieder entschärfen können.

  • Alles, was im Home-Verzeichnis /home/$USER liegt, ist nicht betroffen, denn dort hat der Apache bewusst keinerlei Zugriff. Das betrifft insbesondere Konfigurationsdateien wie die .my.cnf, die dein MySQL-Passwort enthält, deine Mailkonfiguration und dein Mailbestand, sowie die Daten selbst installierter Dienste wie z.B. einer MongoDB oder einer PostgreSQL-Instanz.
  • Auch deine MySQL-Datenbanken sind davon nicht betroffen, da der Apache auf jene prinzipiell keinen Zugriff hat.
  • Eine von den Berechtigungen her sensibel konfigurierte webbasierte Applikation ist ebenfalls nicht betroffen. Um’s an einem Beispiel deutlich zu machen: Nehmen wir eine hypothetische index.php mit Programmcode, die eine config.php per include() einbindet, die dann sensible Daten wie z.B. MySQL-Zugangsdaten beinhaltet – dann müsste lediglich die index.php für den Apache lesbar sein, die config.php jedoch nicht, denn diese wird nicht vom Apache gelesen, sondern vom PHP-Interpreter, der unter der ID des betreffenden Users läuft. Sie müsste insofern nicht world-readable sein und wäre dann auch nicht für die Symlink-Attacke anfällig gewesen. Einige Webapplikationen sind auch tatsächlich out-of-the-box so sensibel konfiguriert, aber machen wir uns nichts vor: Die Mehrheit der Applikationen ist das nicht. Wir weisen in unserem Wiki aber auch wiederholt darauf hin, dass eigenständig laufende Applikationen (also z.B. node.js-Dienste) ohnehin nichts im DocumentRoot zu suchen haben.
  • Es handelt sich nicht um ein Problem, das von beliebigen Dritten aus der Ferne ausgenutzt werden könnte. Das Setzen von Symlinks setzt einen lokalen Useraccount auf der jeweiligen Maschine voraus. Damit ist zum einen die Zahl der hypothetisch möglichen Angreifer sehr gering; zum anderen können Angreifer auch nicht gezielt auf einem System, auf dem sich ein bestimmtes „Opfer“ befindet, einen Account anlegen, denn neue Accounts werden immer nur auf die jeweils neuesten 2-3 Hosts verteilt; alle anderen sind für neue Accounts gesperrt.
  • Es ist die Kenntnis des konkreten Pfads und Dateinamens nötig, um einen Symlink dorthin konstruieren zu können. Ein Angreifer bräuchte also in jedem Fall schon gewisses Vorwissen über den Zielaccount.

Die /etc/shadow – ein Sonderfall

Einen Sonderfall nimmt die /etc/shadow ein – die Userdatenbank, die die Passwort-Hashes beinhaltet und die für User normalerweise auch nicht lesbar ist. Sie ist allerdings für den Apache lesbar, weil die Authentifizierung für den Zugriff auf die Webalizer-Statistiken darüber läuft, und damit ist sie ganz genauso für die Symlink-Attacke anfällig. Es war damit lokalen Usern auf dem System möglich, einen Symlink zu konstruieren, über den sie den Inhalt der /etc/shadow auslesen konnten. Auch hier gibt es jedoch einige Aspekte, die dies relativieren:

  • Nicht wenige User verwenden überhaupt kein SSH-Passwort, sondern loggen sich via SSH-Keys ein (was wir ohnehin empfehlen). In diesem Fall enthält die /etc/shadow keinerlei relevante Informationen.
  • Wir haben bewusst den Standard-Hashing-Algorithmus der eingesetzten Linux-Distribution explizit auf SHA-512 konfiguriert und damit auf den stärksten möglichen Hashing-Algorithmus, den die Distribution unterstützt. Der Einsatz eines Salt verhindert, dass fertige Tabellen aus „beliebten Passwörtern“ und ihren Hashwerten zum Einsatz kommen könnten, mit deren Hilfe man anhand eines Hashwerts das zugrundeligende Passwort einfach nachschlagen könnte.
  • Wir forcieren starke Passwörter, die sich wahlweise durch Länge oder durch die Verwendung unterschiedlicher Zeichenklassen auszeichnen und insofern potentiell nur ziemlich schwer durch Brute-Force-Attacken angegangen werden können.

Alles in allem ist eine Herbeiführung absichtlicher Hashkollisionen (grob vereinfacht ausgedrückt, das „Erraten des Passworts“) damit als sehr, sehr schwer anzusehen. Angesichts dessen, dass die Datei eben auch nicht in die Hände völlig unbekannter Dritter geraten ist, sondern maximal von lokalen Usern über Umwege eingesehen werden konnte, sehen wir hier nur einen minimalen Angriffsvektor. Dass eine regelmäßige Passwortänderung völlig unabhängig von einem konkreten Vorfall immer eine gute Idee ist, sollte mittlerweile wirklich zum Allgemeinwissen gehören.

Der Grund, dass seit Bekanntwerden der Möglichkeit der Symlink-Attacke die Webalizer-Statistiken nicht abrufbar waren, hängt genau damit zusammen; wir hatten hier die /etc/shadow bis zum endgültigen Fix zunächst nur noch für root lesbar gemacht, um zumindest diesen – via Twitter öffentlich bekannt gewordenen – Angriffsvektor ad hoc zu beseitigen.

Was haben wir nun letztlich geändert?

Die Lösung ist so trivial, dass wir uns wirklich doppelt und dreifach ärgern, das nicht gleich richtig gemacht zu haben (und uns ein kleines bisschen auch fragen, warum nicht von Haus aus die sicherere Variante der Default im Apache ist): Der Apache stellt mit SymLinksIfOwnerMatch eine Option bereit, die im Wesentlich exakt das gleiche tut wie FollowSymLinks, aber gleichzeitig überprüft, ob das Symlinkziel dem gleichen User gehört wie der Symlink selbst. Damit folgt der Apache Symlinks auf Dateien anderer User oder auf Dateien wie die /etc/shadow schlicht nicht mehr, und der Angriffsvektor ist weg. Das war’s auch schon, und das haben wir nun so umgestellt. Problem gelöst. Konkret haben wir die Einstellung

AllowOverride All

… wie folgt geändert:

AllowOverride AuthConfig FileInfo Indexes Limit Options=ExecCGI,Includes,Indexes,SymLinksIfOwnerMatch

Das entspricht einem All abzüglich der Option FollowSymLinks (es gibt nur keine elegante Möglichkeit, um „alles, außer …“ auszudrücken).

Das hat aber auch zur Folge, dass überall dort, wo User Options +FollowSymLinks in einer .htaccess-Datei hatten, eingegriffen werden musste. Bisher war diese Option schlicht redundant, weil Options +FollowSymLinks ja schon unsererseits global aktiviert war (das heißt, es war ohnehin unnötig, sie anzugeben, aber machte eben auch keine Probleme). Ab sofort ist aber nun eben nur noch Options +SymLinksIfOwnerMatch zulässig, und das heißt, dass ein und dieselbe .htaccess-Datei, die bisher einwandfrei funktioniert hat, künftig einen Internal Server Error werfen würde – obwohl die benötigte Funktionalität sich mit Options +SymLinksIfOwnerMatch problemlos abdecken ließe; es muss nur eben entsprechend geändert werden. Prominentes Beispiel dafür wäre unter anderem das Joomla!-CMS, das in seiner Default-.htaccess-Datei diesen Abschnitt vorgibt (insbesondere die Kommentarzeilen sind hier interessant):

# The line just below this section: 'Options +FollowSymLinks' may cause problems
# with some server configurations.  It is required for use of mod_rewrite, but may already
# be set by your server administrator in a way that dissallows changing it in
# your .htaccess file.  If using it causes your server to error out, comment it out (add # to
# beginning of line), reload your site in your browser and test your sef url's.  If they work,
# it has been set by your server administrator and you do not need it set here.
##

## Can be commented out if causes errors, see notes above.
Options +FollowSymLinks

Zwischen den Zeilen gelesen muss man wohl zu dem Schluss kommen, dass auch die Joomla!-Entwickler nicht wirklich vertraut mit der Existenz von Options +SymLinksIfOwnerMatch sind, ansonsten wäre das ein sensiblerer Default, der dann schon mit unserer bisherigen Config funktioniert hätte und auch mit der aktuellen funktioniert, und sonst hätte man auch nicht ein Auskommentieren der Zeile als einzige Alternative darstellen müssen. Aber egal, wir wollen nicht ablenken, es ist nur ein Beispiel dafür, dass es damit unsererseits auch noch weiteren Handlungsbedarf gab, als einfach nur die AllowOverride-Policy zu ändern.

Die Sache mit der Anpassung von .htaccess-Dateien

Wir haben lange im Team diskutiert, ob wir die betreffenden User lediglich informieren und ihnen dabei ggf. auch direkt erklären, was sie tun müssen. Das wäre nicht zuletzt aus Privatsphäregründen sicherlich die wünschenswertere Option gewesen, denn ungefragt an Dateien in User-Accounts herumzufuhrwerken, entspricht wirklich nicht unseren üblichen Maßstäben. Dass wir letztlich doch diesen Schritt gegangen sind, liegt an dem Preis, der dafür zu zahlen gewesen wäre, denn: Eine Information im Sinne von „Du musst hier dieses und jenes ändern, sonst geht am Tag X deine Site kaputt“ braucht Vorlauf, und da reden wir nicht von einem Tag, sondern wenigstens von einer Woche, oder vielleicht auch gleich vier Wochen, denn möglicherweise ist der betreffende User ja gerade in Urlaub und möchte sicherlich nicht zurückkehren und feststellen, dass seine Site seit drei Wochen nicht mehr läuft. Es wäre aber nun wirklich fahrlässig gewesen, eine derart lange Zeit einen uns zu jenem Zeitpunkt dann ja nun bereits bekannten Angriffsvektor einfach bestehen zu lassen. Es gab also im Wesentlichen drei Möglichkeiten für uns:

  1. Wir informieren die User, deren Sites Options +FollowSymLinks einsetzen, was sie tun müssen, damit ihre Site bei der Umstellung nicht kaputtgeht, und lassen die Sicherheitslücke sehenden Auges für ein gewisses Zeitfenster offen. Das geht irgendwie gar nicht.
  2. Wir informieren die User, deren Sites Options +FollowSymLinks einsetzen, was sie tun müssen, und fixen die Sicherheitslücke aber sofort. Die betreffenden Sites sind dann erstmal kaputt, bis die User dem Inhalt der Mail folgen. Das wäre für die betreffenden User sehr ärgerlich.
  3. Wir ändern bei den Usern, deren Sites Options +FollowSymLinks einsetzen, die Einstellung in Options +SymLinksIfOwnerMatch, deren Sites damit weiterhin funktionieren, und schließen die Lücke sofort. Das ist technisch top, aber privatsphärentechnisch nur schwer akzeptabel.

Alle drei Lösungen sind mehr oder weniger unschön; die letzte eben vornehmlich wegen des zuvor nicht explizit vom Eigentümer der jeweiligen Datei autorisierten Schreibzugriffs. Wir sind nach längerer Diskussion im Team aber letztlich einvernehmlich zu dem Schluss gekommen, dass es von den drei Möglichkeiten diejenige darstellt, die technisch minimal-invasiv ist, weil FollowSymLinks und SymLinksIfOwnerMatch ja letztlich exakt das Gleiche tun, nur Letzteres eben mit einem zusätzlichen Sicherheitscheck, und wir insofern den Sinn der Konfiguration nicht verändern. Die Änderung wurde letztlich mittels find und sed vorgenommen, so dass wir ganz explizit nur Dateien, die definitiv „.htaccess“ heißen, überhaupt haben automatisiert anfassen lassen, um anhand dieses Ergebnisses lediglich die Vorkommnisse von FollowSymLinks durch SymLinksIfOwnerMatch zu ersetzen. Es wurden unsererseits also keine anderen Dateien auf ihren Inhalt inspiziert, weder automatisiert noch mit dem menschlichen Auge. Wir hoffen insofern sehr auf das Verständnis der betreffenden User, dass wir hier letztlich eingreifen mussten, auch wenn wir überzeugt davon sind, dass die automatisierte Anpassung letztlich – zumindest technisch gesehen – in ihrem Sinne sein müsste.

Wurde die Lücke denn ausgenutzt?

Es liegt leider in der Natur der Sache, dass das nicht nachvollziehbar ist. Ein Symlink kann angelegt und im nächsten Moment schon wieder entfernt sein; solange er nicht über einen Backup-Zyklus hinweg bestand, gibt es insofern keinen Vergleichs-Datenbestand, anhand dessen man sagen könnte: Da war so ein böser Symlink. Wenn es einen gab und dann ein HTTP-Request darauf gemacht wurde, gibt es zwar einen Eintrag im access_log der jeweiligen Site, aber dem wiederum ist nicht anzusehen, ob der auf so einen böswilligen Symlink ging.

Und nun?

Lange Rede, kurzer Sinn: Das Problem ist gefixt. Die Configs von Usern sind soweit nötig angepasst, so dass – hoffentlich – nichts Wesentliches dabei kaputtgegangen sein sollte. Für uns und unsere User liegt hiermit ein trauriger Beleg vor, dass auch wir nicht unfehlbar sind. Wir haben unser Bestes getan, so damit umzugehen, wie wir es uns selbst als User eines anderen fiktiven Hosters gewünscht hätten, und hoffen darauf, dass das aus Sicht der meisten User auch das Richtige war. Letztlich bleibt uns nur, für diesen wirklich ärgerlichen Konfigurationsfehler um Entschuldigung zu bitten.

Gigantische Apache Errorlogs

20. Dezember 2013 von Christopher Hirschmann

Wer kennt das nicht: Hin und wieder wachsen die Errorlogs von Apache binnen kürzester Zeit zu gigantischen Größen an. Wer (zweifelhaftes) Glück hat bemerkt das nicht weil das System hohen I/O Load davon bekommt, sondern erst wenn die Festplatte voll ist. Da steht schnell die Frage im Raum: „Tut das Not? Das muss doch nicht sein!“

Muss es auch nicht.

Unter CentOS 5 haben wir dazu den Apache angewiesen seine Errorlogs einfach an das Tool multilog aus den daemontools zu übergeben, das hat sich dann darum gekümmert die Logs zu rotieren wenn sie zu groß wurden, sie zu komprimieren (um genau zu sein: es kann ein externes Programm auf die Logfiles loslassen, z.B. einen Kompressor) und alte Logfiles beizeiten wegzuräumen:

ErrorLog "|/command/multilog s9999999 c40 n10 '!/bin/gzip' -*PHP*Deprecated* -*PHP*Notice* -*EACCELERATOR*hit* /var/log/httpd/errors"

Obendrein konnten wir so gleich noch ein paar PHP-Meldungen direkt verwerfen, so dass sie gar nicht erst geloggt wurden. In der Prozessliste sah das dann so aus:


7951 ? S 0:00 | \_ supervise httpd
12563 ? Ss 0:10 | | \_ /usr/sbin/httpd -D FOREGROUND
12565 ? S 0:00 | | \_ /command/multilog s9999999 c40 n10 '!/bin/gzip' -*PHP*Deprecated* -*PHP*Notice* -*EACCELERATOR*hit* /var/log/httpd/errors

Unter CentOS 6 hat das leider nicht mehr funktioniert. Offenbar beendete multilog dem Apache nicht schnell genug wenn er sich neugestartet hat und dann blockierte die neu gestartete multilog-Instanz weil sie die Dateien nicht locken konnte, da die alte Instanz noch Locks darauf hielt.

Deswegen waren lange Zeit unsere mit CentOS 5 laufenden Uberspace-Hosts etwas besser vor ausrastenden Applikationen die wie bekloppt das Errorlog vollspammen geschützt als die neueren mit CentOS 6 laufenden Hosts. Das war zwar manchmal auch amüsant (Spitzenreiter war bisher ein 300 GB großes Logfile, das in unter 6 Stunden geschrieben worden war), aber vor allem war es nervig und ärgerlich. Deshalb haben wir hier schon länger nach einer neuen Lösung gesucht.

Nun liefert das Apache-Projekt selbst eine ansatzweise Lösung mit: das Tool rotatelogs. Das kann aber leider nicht alles was multilog kann, z.B. kann es keine alten Logfiles wegräumen und nicht selbst den Input filtern. Aber es kann wie multilog ein externes Programm aufrufen und auf die Logfiles loslassen. Das funktioniert aber etwas umständlich, wie die Manpage zum Parameter „-p“ verrät:

If given, rotatelogs will execute the specified program every time a new log file is opened. The filename of the newly opened file is passed as the first argument to the program. If executing after a rotation, the old log file is passed as the second argument. rotatelogs does not wait for the specified program to terminate before continuing to operate, and will not log any error code returned on termination. The spawned program uses the same stdin, stdout, and stderr as rotatelogs itself, and also inherits the environment.

Ich hab daher ein Skript gebastelt, das den Großteil der Schwächen von rotatelogs gegenüber multilogs ausgleicht. Es sorgt für eine Kompression der Logfiles, es räumt alte Logfiles weg und aus der Testphase sind noch ein paar (eher unelegante) Debugging-Funktionen übrig: logjanitor.

Es ist dafür gedacht zusammen mit rotatelogs so genutzt zu werden:

ErrorLog "|| /usr/sbin/rotatelogs -f -p /usr/local/sbin/logjanitor -L /var/log/httpd/error_log /var/log/httpd/error_log.%Y-%m-%d-%H_%M_%S 86400 8M"

Bei dieser Konfiguration landen Logfiles im Ordner /var/log/httpd/ und bekommen einen detaillierten Zeitstempel in den Namen. Sie werden nach 86400 Sekunden (24 Stunden) rotiert, oder wenn Apache neustartet, oder wenn sie größer werden als 8 MB. Bei all diesen Gelegenheiten wird logjanitor ausgeführt und erhält ggf. noch Parameter von rotatelogs dazu, wie im obrigen Zitat beschrieben. Wenn logjanitor einen zweiten Parameter erhält, nimmt es an dass es sich um einen Dateinamen handelt komprimiert diese Datei. Außerdem sucht es in /var/log/httpd/ nach unkomprimierten Dateien mit dem passenden Namensschema und komprimiert diese ebenfalls. Schließlich löscht es alte Logs die älter als 7 Tage sind.

Mit dem Schalter -v lässt sich logjanitor gesprächiger machen. Das ist aber nur zum Testen gedacht. Da die Position der Parameter 1 und 2 relevant ist, sollte -v (oder -h) frühestens als dritter Parameter übergeben werden. (Ja, das kann alles noch schöner gemacht werden.) In diesem Fall schreibt logjanitor nach /var/log/logjanitor/“ ein Log (Achtung: hier gibt es keine automatische Rotation).

Nachdem heute auf einem unserer Hosts mal wieder die Festplatte mit PHP-Warnings vollgelaufen war, hab ich die Testphase kurzerhand für beendet erklärt und das jetzt auf allen Uberspace-Hosts ausgerollt. Falls noch Fehler auftreten, müssen die jetzt eben sofort gefixt werden.

Da wir sicher nicht die einzigen mit diesem Problem sind, dachte ich ich veröffentliche das direkt, manchmal gibt’s dann ja direkt noch interessanten Input. Das ganze ist zwar etwas mit der heißen Nadel gestrickt und noch nicht so elegant wie ich es gerne hätte, aber die nächste Version kommt bestimmt.

Got root?

29. April 2013 von Christopher Hirschmann

Ein Kunde von uns sorgte neulich für Verwirrung, weil wir uns auf seinen Servern mit einem Mal nicht mehr einloggen konnten. Auf Nachfrage stellte sich dann heraus: Der Kunde hatte gerade eine neue Policy eingeführt und den Root-Account deaktiviert — und war noch nicht dazu gekommen uns zu informieren. Alles halb so wild, war ja glücklicherweise aufgefallen bevor irgendwas ausgefallen war und wir schnell auf die Server hätten zugreifen müssen/sollen. Aber es ergab sich dann doch eine Diskussion über den Sinn und Unsinn dieser Policy und da ich hier etwas länger an meiner Position dazu gefeilt habe, wollte ich sie hier nochmal wiedergeben.

Um unsere Sicht auf diese Dinge zu verstehen, sollte ich zunächst einmal erläutern, wie die Frage des administrativen Zugriffs bei uns gehandhabt wird:

  • Jeder von uns hat einen SSH-Key, den er für die Arbeit verwendet. Unsere SSH-PublicKeys sind auf all unseren Servern beim Root-Account hinterlegt. Sollte einer von uns gehen oder einer der SSH-PrivateKeys kompromitiert werden, müssen wir den PublicKey auf allen von uns administrierten Servern austauschen bzw. entfernen. (Was wir dann wohl mit parallel-ssh tun würden.) Macht eine Menge Arbeit, aber Dinge die selten vorkommen dürfen auch mehr Arbeit machen als Dinge die oft vorkommen. Wenn es irgendwann häufiger vorkommen sollte, werden wir es besser automatisieren.
  • SSH ist so konfiguriert, dass Logins als Root nur mit SSH-Keys möglich sind, keinesfalls mit dem Root-Passwort.
  • Keiner von uns hat individualisierte Accounts und dementsprechend auch keine Passwörter dazu. Solange der Login per SSH mit Key funktioniert, vermeiden wir Passwörter wo immer es geht. Passwörter häufig eintippen macht irgendwann bekloppt und deswegen führt das nur zu einfachen Passwörtern oder der Verwendung der Zwischenablage — letzteres führt wiederum zu sehr häufiger Verwendung desselben Passworts.
  • Wir pflegen eine GPG-verschlüsselte Liste mit den Root-Passwörtern für alle von uns betreuten Server. Diese Passwörter brauchen wir beinahe ausschließlich für den Login auf Konsolen oder wenn der SingleUserMode ein Passwort verlangt. Es gibt kein einheitliches Root-Passwort, außer bei Kunden die das entgegen unserem Rat bei all ihren Servern so wollen. Allerdings vermeiden wir aufgrund der leider allzu häufigen Problematik Sonderzeichen auf Konsolen einzugeben wo es geht diese Zeichen und wählen dafür eher lange alphanumerische Passwörter. (Ich vergebe z.B. nie Passwörter mit weniger als 23 Zeichen und bevorzuge eher 42 Zeichen. Diese Passwörter werden nur ganz selten gebraucht und können meist über die Zwischenablage kopiert werden, sie dürfen ruhig lang sein.)
  • Wir arbeiten auf Servern meist als root, weil wir keinen Vorteil darin sehen es nicht zu tun.

Soweit erstmal zum Admin-Zugriff und wie wir das handhaben und absichern. Bleibt die Frage, ob als Root gearbeitet werden sollte und wenn nicht warum. Das wiederum geht zurück auf die weit verbreitete Proklamation, dass der Root-Account in irgendeiner Weise eine generelle konzeptionelle Schwäche bei Unix sei und abgeschafft werden sollte. Das sehe ich explizit nicht so. Insbesondere finde ich, dass administratives Arbeiten auf einem Unix-System ohne Root-Account sehr schnell zu einer Plage wird, denn in der Regel läuft das dann immer auf sudo-Orgien und endloses Passwort-Eintippen hinaus (oder auf „sudo -i“).

(Ich will hier übrigens nicht über sudo herziehen oder davon abraten. Für unerfahrene User ist sudo meiner Ansicht nach genau das richtige, besonders wenn sie nicht „sudo -i“ verwenden. Es hilft Schusseligkeitsfehler zu vermeiden und vermittelt ein Gefühl dafür, welche Eingriffe größeres Gewicht haben. Aber es geht hier auch um die Handhabung von Servern. Unerfahrene User sollten keine Server administrieren, auch nicht mit sudo. Ich bin davon überzeugt, weil ich selbst mal ein unerfahrener User war und mich an die Lektionen auf dem Weg zum erfahrenen User erinnere.)

Das Problem mit dem root-Account aus meiner Sicht ist weniger, dass Admins damit arbeiten, als vielmehr dass das System damit arbeitet. Mittlerweile ist es glücklicherweise üblich geworden, dass nahezu alle Dienste entweder ohne Root-Rechte starten können oder einen Mechanismus enthalten, um nach dem Starten ihre Root-Rechte aufzugeben. Aber warum brauchen bestimmte Dienste beim Starten Root-Rechte? In den meisten Fällen entweder weil sie a) Kernel-Module laden müssen (oder ähnlich gewichtige Dinge tun müssen) oder b) sich an einen privilegierten Port binden müssen. Das mit den Kernel-Modulen lässt sich oft anders lösen und wird inzwischen meist auch umgangen. Das mit den Ports ist nach wie vor ein Problem. Warum braucht ein Webserver-Dienst beim Starten root-Rechte um sich an Port 80 binden zu dürfen? Eigentlich will man doch nur sicherstellen, dass ausschließlich der Dienst dem man das erlaubt hat sich an diesen Port bindet. Das ist mit den Root-Rechten streng genommen nichtmal erfüllt. Es wäre durchaus denkbar, dass sich ein anderer mit Root-Rechten startender Dienst an diesen Port bindet, obwohl er es gar nicht soll. Hier fehlt es Linux (genauso wie allen anderen mir bekannten unixoiden Betriebssystemen) an einer vernünftigen Lösung.

Dann gibt es noch ein paar andere Problembereiche, z.B. dass nur der Root-User Netzwerkinterfaces wie z.B. tun/tap-Devices aktivieren kann. Statt hier einem VPN-Dienst (der ein Primärziel für Attacken ist) beim Starten Root-Rechte zu geben die er erst abgeben muss, wäre es gewiss sinnvoller dem von diesem Dienst genutzten Account ein Sonderprivileg einzuräumen, um eben genau diese Art Interfaces (und keine anderen) aktivieren zu können. (Nebenbei: es fehlt auch an einem Mechanismus um zu überprüfen, ob ein Dienst tatsächlich wie verlangt nach dem Starten seine Rechte gedroppt hat.)

Warum aber sollten Admins nicht als root arbeiten?

Das vielleicht offensichtlichste Argument dagegen wäre wohl: Wer als Root arbeitet, kann als Root auch viel kaputt machen. — Ja, aber mit sudo geht das auch. Wer ständig Dinge tut die sudo erfordern, wird sich irgendwann angewöhnen nahezu jeden Befehl mit sudo zu beginnen oder eben „sudo -i“ zu verwenden. Diese „Schutzwirkung“ von sudo ist dann auch dahin. Admins sind hier letztlich nichts anderes als Spezialisten die eben wissen, wie sie mit ihren zum Teil gefährlichen Werkzeugen umzugehen haben ohne sich oder anderen Schaden zuzufügen.

Eines der häufigeren Argumente dagegen ist, dass das Root-Passwort über unsichere Kanäle eingegeben werden könnte. — Das trifft auf uns aber nicht zu. Wir verwenden kein telnet, wir verwenden kein FTP, wir schließen den Root-User bei solchen Diensten sogar explizit aus, wenn die Dienste das zulassen. Und wir erlauben bei SSH keinen Root-Login per Passwort. Wo wir gezwungen sind auf schwache Dienste zurückzugreifen (etwa: telnet-Zugriff auf Switche), umgehen wir die Schwäche indem wir den Zugriff nur über ein internes Netz hinter einem VPN erlauben.

Dann besteht noch die Gefahr, dass das Passwort kompromitiert wird. — Die können wir nicht eliminieren, wir können sie nur die Konsequenzen minimieren, indem wir für jeden Host ein anderes Passwort verwenden. Hin und wieder ändern wir die Passwörter auch. Letztlich kann aber eben auch ein SSH-Key verloren gehen und muss dann eben auch ausgetauscht werden. Eine weitere Maßnahme, über die wir bereits nachdenken, wäre das Aufteilen der GPG-Passwortliste in mehrere Listen, auf die auch nicht jeder von uns Zugriff hat. Das war bisher nicht nötig und sinnvoll, da bei uns bisher immer alle gleichermaßen für alle Server und Kunden zuständig waren, aber je mehr MitarbeiterInnen wir dazu bekommen, desto attraktiver wird dieser Schritt.

Hingegen vermehren sich die Probleme ohne Root-Account. Ich versuche das mal an einem typischen Beispiel gegenüber zu stellen:

Server geht in die Knie, SSH reagiert nicht, wir versuchen Zugriff über einen anderen Weg zu bekommen. Das ist in der Regel die virtuelle Konsole bei virtualisierten Hosts, oder es ist eine Art emulierte Konsole via KVM oder Serial-Over-LAN, beides in der Regel als Payload in einer verschlüsselten IPMI-Verbindung, oder eben eine richtige KVM-Verbindung über ein entsprechendes Gerät, auf das wiederum verschlüsselt zugegriffen wird.

Mit Root-Login: Root-Passwort aus der PW-Liste suchen, Konsole aufrufen, „root“ und Passwort auf der Konsole eingeben (oder besser: aus der Zwischenablage, wenn sie denn funktioniert), hoffen dass der Host noch in der Lage ist einen hereinzulassen, damit man was unternehmen kann.

Ohne Root-Login: User- und ggf. Root-Passwort aus der PW-Liste suchen, Konsole aufrufen, Username und Passwort eingeben, hoffen, dass der Host einen noch rein lässt. Dann „sudo -i“ und User-Passwort oder „su -l“ und Root-Passwort eingeben, hoffen dass der Host in seiner Not noch genügend Ressourcen frei hat, um einen auch hier noch reinzulassen, damit man endlich was unternehmen kann.

(Faierweise sei erwähnt, dass sich hinter „Konsole aufrufen“ noch eine Menge mehr Schritte verbergen können. Das tut hier aber nichts zur Sache.)

Oder nehmen wir meinen persönlichen Evergreen:

Die tolle, neue Linux-Distribution die ach so benutzerfreundlich und bunt und (zumindest für jemanden der nie wirklich an einem Mac gesessen hat) fast wie ein Mac daher kommt und selbstverständlich ohne bösen Root-Account auskommt wirft das Handtuch, das System bootet mit einem fetten Dateisystemfehler, ruft freundlicherweise die Recovery-Konsole auf mit der man das Dateisystem eventuell reparieren könnte und fragt in freundlichen, großen weißen Lettern:

Please enter Root-Password for Recovery: _

Und ich bekomme grüne Haut.

Alles in allem ist meine professionelle und persönliche Meinung (die bei uns im Team geteilt wird) dazu: Ja, am Root-Account offenbaren sich eine ganze Reihe konzeptioneller Schwächen der Rechtetrennung in unixoiden Betriebssystemen. Nein, das Arbeiten von Admins als root-User ist (bei hinreichend verantwortungsbewußten Admins) nicht eine davon.

Das heißt aber nicht, dass wir uns quer stellen würden, wenn ein Kunde Wert darauf legt, eben keinen Root-Account zu haben. Jemand von uns erläutert dann (wie ich in diesen Tagen) warum wir das selber nicht für sinnvoll halten und wir weisen ausdrücklich darauf hin, dass a) wir dann mit „sudo -i“ arbeiten werden und b) unbedingt, auf jeden Fall, ganz sicher vorher getestet werden sollte, ob die Linux-Distribution der Kundenwahl nicht auf der Recovery-Konsole doch nach einem Root-Passwort fragt, sonst kann es ein böses Erwachen geben — oder wie Murphy’s Law uns lehrt: sonst gibt es früher oder später ein böses Erwachen.

Ob der Kunde uns dann einen Admin-Account einrichtet oder individualisierte Accounts ist uns dabei relativ egal, wir würden selbst individuallisierte Accounts in unserer Passwort-Liste verzeichnen (damit hätte dann jeder von uns theoretisch die Zugangsdaten zu den Accounts der anderen), einfach weil wir grundsätzlich alle Passwörter dort sammeln. Denn uns ist eine verschlüsselte Passwortliste auf die nur wir fünf Zugriff haben lieber, als individuelle Passwörter die jeder einzelne von uns sich anders (und potentiell unsicherer) notiert und im Notfall vielleicht auch nicht findet.

Werkstattbericht: UEFI

21. Dezember 2012 von Christopher Hirschmann

Ich hab mich schon seit einer Weile für UEFI interessiert und früher durch Macs, seit anderhalb Jahren durch meinen neuen Laptop und in den letzten Wochen und Monaten nun auch bei den ersten neuen Servern die Gelegenheit gehabt, mich auch damit zu beschäftigen. (Auch im Freundeskreis tauchten schon diverse Geräte mit UEFI auf — oft gefolgt von den ersten Hilferufen.) Nun steht ein Urlaub an, das ist die perfekte Gelegenheit das gesammelte gefährliche Halbwissen und mühsam erkämpfte Wissen vorher mal in die Schriftform zu dumpen:

(Kleine Bitte vorweg: Wer mag, möge bitte versuchen mitzuzählen, wie oft sie/er beim Lesen die Hände vor den Kopf schlagt oder ähnliches tut. Über entsprechende Kommentare freue ich mich.)

UEFI

UEFI steht für Unified Extensible Firmware Interface und ist Katalog von vereinheitlichten Schnittstellen über die ein Betriebssystem auf die Firmware und die einzelnen Baugruppen eines Rechners zugreifen kann. Es ist als Ablösung für das arg in die Jahre gekommene BIOS gedacht und nachdem es lange Zeit nur bei Apple, einigen Desktop- und Laptop-Modellreihen von Dell und HP, sowie einem kleinen Teil des Servermarktes anzutreffen war, setzt es sich jetzt allmählich durch. (Was im wesentlichen daran liegen dürfte, dass Intel ab den SandyBridge-Prozessoren und -Chipsätzen anscheinend UEFI voraussetzt und Hardware-Hersteller damit gezwungen sind sich endlich vom BIOS zu trennen. Außerdem macht seit kurzem das Windows-8-Logo-Programm das ebenfalls nach UEFI verlangt den Herstellern Dampf.)

UEFI ist dabei nicht eine bestimmte Firmware, es ist nichtmal ein Synonym für Firmware. Ähnlich wie beim BIOS auch gibt es bei UEFI unterschiedliche Implementationen und das Siegel UEFI sagt im Grunde nicht viel über die Firmware aus, außer dass sie bestimmte Funktionen und APIs bereitstellt. Das ist so ähnlich wie beim Begriff Unix: es gibt (heutzutage) nicht das Unix, sondern es gibt sehr viele sehr unterschiedliche Betriebssysteme die unter anderem alle Funktionen und APIs bereitstellen die der Unix-Standard verlangt.

Exkurs: Was war an BIOS zuletzt so schlimm?

BIOS geht auf die frühesten Anfänge der PC-Ära zurück. So lange es „IBM-kompatible PCs“ gab, gab es BIOS (streng genommen gab es BIOS sogar schon vor den IBM-kompatiblen PCs). Aus Kompatibilitätsgründen haben sich mit der Zeit eine Reihe von alten Zöpfen angesammelt, die mehr und mehr zum Hindernis wurden. So läuft BIOS z.B. immer noch im 16-Bit-Modus der x86er CPUs — ist damit einer der Gründe, warum selbst Prozessoren der x86_64-Architektur so einen Museums-Modus noch mitbringen — und zunehmend schwieriger zu warten, nicht zuletzt weil allmählich die Programmierer die 16 Bit Architekturen noch gut kennen alle in Rente gehen. In diesem Modus kann nicht sonderlich viel Speicher adressiert werden, so dass bereits seit vielen Jahren Klimmzüge notwendig sind, um in PCs überhaupt signifikante Mengen Arbeitsspeicher und moderne Festplatten zu verbauen. Wer sich schon mal gefragt hat, warum manche Festplatten eine per Jumper zu setzende Einstellung haben, mit der ihre Kompatibilität auf 32 GB begrenzt wird… ältere BIOS-Versionen können nichts größeres adressieren. Und auch heute gilt noch: Boot-Partitionen gehören an den Anfang einer Festplatte, denn sonst kann der Bootloader-Code aus dem MBR diese nicht adressieren und folglich nicht Booten. Und bei Festplattengrößen jenseits der 2 TB kam das BIOS bzw. das eng damit verbundene MBR Partitionstabellenschema endgültig an seine Grenzen.

BIOS-Einstellungen können nur im BIOS gesetzt werden, es gibt keine einheitliche Schnittstelle, über die dies vom Betriebssystem aus erledigt werden kann. Das ist ärgerlich, denn es verhindert z.B., dass ein von einem Installationsmedium gebootetes Live-System nach erfolgreicher Installation eines neuen Betriebssystems zuverlässig dafür sorgen kann, dass dieses als nächstes gebootet wird. Erfahrene User mag das nicht mehr schrecken, aber ich trage immer noch einen kleinen Rest Hoffnung in mir, dass ich nicht bis an ihr Lebensende meinen Eltern und Großeltern bei all ihren PC-Problemen helfen muss. Da hilft es nur leider wenig wenn die Betriebssystem-Installationen einfacher werden, wenn die Benutzerfreundlichkeit des BIOS auf dem Stand von 1983 stehen bleibt. Meine Mutter kennt PCs ausschließlich mit Maus, dass man im BIOS die Maus nicht benutzen kann verwirrt sie. Kinder die heute aufwachsen werden vermutlich in einigen Jahren noch verwirrter sein, weil das BIOS kein Touch unterstützt.

Ärgerlicherweise gibt es die meisten BIOS-Versionen nur in Englisch, was sie gerade für unerfahrende User die Englisch nicht als Muttersprache erworben haben sehr unzugänglich macht — obendrein wenn diese in Teilen der Welt leben in denen Englisch noch eine viel fremdere Sprache ist als in Europa und Nordamerika. Auch Begrifflichkeiten sind vollkommen uneinheitlich und verwirrend. Ein CPU-Feature das bei Intel einen Namen und bei AMD einen anderen hat (etwa: Virtualisierungssupport) ist ja schon schlimm genug, aber natürlich muss es auch in jedem BIOS andere, verwirrende Namen bekommen und die entsprechende Einstellung muss natürlich in einem anderen Untermenü eines Untermenüs versteckt werden.

BIOS ist im Grunde nicht erweiterbar, bzw. nur auf Umwegen. Es ist ein ziemliches Gefrickel Option-ROMs wie sie viele RAID-Controller und Netzwerkkarten heute häufig mitbringen zu programmieren. Diese Option-ROMs müssen dann ein eigenes User Interface haben, denn sie können nicht in das des BIOS eingeklinkt werden. Folglich müssen die User sich noch eine Tastenkombination merken, um in das User Interface solcher Karten zu kommen.

Wie lange es gedauert hat, bis auf brandneuen PCs zuverlässig auch mit USB-Tastaturen das BIOS angesprochen werden konnte, will ich hier gar nicht weiter ausführen, sonst rege ich mir nur wieder auf.

Und schließlich ist da noch die Idiotie zu erwähnen, daß es in 30 Jahren PC-Architektur nie auch nur im Ansatz gelungen ist mal die Tasten zu standardisieren, die man beim Booten drücken muß um ins BIOS, die Boot Selection, das UI vom Option-ROM eines RAID-Controllers oder einer Netzwerkkarte zu kommen. Moderne PCs zeigen die nötigen Hinweise auf die Tastenkombinationen oft so kurz an, dass selbst schnelle Leser es nicht lesen können. Wie man z.B. an den zahllosen Admins erkennt, die es schon gar nicht mehr versuchen und einfach nur noch genervt eine mehr oder weniger rhythmische Folge von Esc, F1, F2, F4 und Del in die Tasten hämmern, immer wieder und wieder, weil sie ja auch nicht wissen in welcher halben Sekunde das BIOS diese Eingabe erwartet. (Wobei das natürlich alles noch nichts gegen die Firmware-Reboot-Prozedur vieler moderner Autos ist.)

Alles neu macht UEFI

Mit UEFI sollen all diese Absurditäten ein Ende haben (natürlich kommen neue hinzu, dazu später mehr). Bei UEFI ist es endlich möglich dem User ein User Interface zu präsentieren, in dem auch die Maus funktioniert (man munkelt sogar, es könnte irgendwann mal Touch-Support geben) und das nicht mehr aus Klötzchengraphiken besteht und höhere Auflösungen als 640×480 Pixel unterstützt. Die UEFI Oberflächen unterstützten häufig schon viele verschiedene Sprachen, mit UEFI wird aber sogar Support für nicht-lateinische Schriften, rechts-nach-links-läufige Schriftsysteme und theoretisch sogar für andere Ausgabegeräte wie Braille-Zeilen realistischer, was bei BIOS nur selten gemacht wurde. Auch kann das Betriebssystem auf die Bootreihenfolge Einfluss nehmen und es wird leichter, UEFI-Firmwareupdates einzuspielen.

UEFI bietet sogar einen eigenen, standardisierten Netzwerkstack, bei dem zu hoffen steht, dass das Arbeiten in PXE-Umgebungen endlich einfacher und einheitlicher wird. (Wobei abzuwarten ist, ob das am Ende auch funktioniert, bisher wurden offenbar fast alle UEFI-Firmwares ohne Netzwerkstack ausgeliefert.) Im Zusammenhang mit diesem Netzwerkstack wurde auch schon erwähnt, dass es für UEFI wohl Fernwartungsschnittstellen geben soll (wer hier jetzt das gleiche denkt wie ich: auch dazu später mehr).

Für Kommandozeilen-Liebhaber kommt UEFI häufig mit einer Shell daher (wenn nicht, lässt sie sich nachinstallieren), die interessante Möglichkeiten eröffnet und das Debugging erleichtern dürfte.

Es wurde auch schon viel darüber geschrieben, dass UEFI prinzipiell die Möglichkeit bietet plattformunabhängige Treiber für Hardwarekomponenten zu unterstützen. Die Idee ist wohl, dass eine Hardware beim Bootvorgang ihren Treiber in UEFI integriert und das Betriebssystem diesen Treiber später nutzen kann. Hier heißt es bisher erstmal abwarten, denn gesehen haben ich so ein Setup noch nicht und ich bin da anscheinend nicht der einzige.

Vor allem aber kommt UEFI mit vollem Support für ein neues Partitionierungsschema namens GPT, welches das alte, ebenfalls in die Jahre gekommene MBR-Schema ablösen wird. Zu GPT und ihrer Handhabung schreibe ich in Kürze nochmal gesondert etwas. Ich kann aber schonmal sagen, dass ich GPT wesentlich enthusiastischer entgegen sehe als UEFI (bis hin zu aufgeregter Schnappatmung, ich geb’s zu). GPT ist in allen Bereichen eine enorme Verbesserung gegenüber MBR und ich habe bisher noch keine Nachteile daran finden können, die in GPT selbst begründet lägen.

Wenn der MBR in Zukunft weg fällt, stellt sich natürlich die Frage, wie unter UEFI dann die Betriebssysteme booten sollen, denn der MBR enthält ja neben der Partitionstabelle vor allem den Chainloading-Code eines Bootloaders. Das ist was UEFI angeht meiner Meinung nach eines der wichtigsten Themen und ich gehe im weiteren vor allem darauf ein. Aber erstmal zu den Problemen die UEFI allgemein hat:

Die Dunkle Seite der Macht

Es klang bereits an einigen Stellen an: mit den vielen Neuerungen tun sich bei UEFI auch neue Probleme auf. Eines der größten Probleme für den Einstieg ist im Moment, dass UEFI-Firmware nicht gleich UEFI-Firmware ist. Ich habe bisher bei keinem einzigen Computer-Hersteller jemals eine Liste der Features ihrer UEFI-Firmware gesehen. Wenn ich auf Eigenschaften wie den Netzwerkstack angewiesen bin, muss ich bisher erstmal die Katze im Sack kaufen. Das wird ein bisschen dadurch aufgelöst, dass bestimmte Versionen des UEFI-Standards bestimmte Funktionen verlangen, so dass von einer Information wie „UEFI 2.1“ auf den Funktionsumfang geschlossen werden kann. Hier sehe ich allerdings das potentielle Problem, dass viele User Firmware-Version und Version des UEFI-Standards durcheinander bringen werden. Es ist nicht gut, User zu verwirren.

Auch bleibt abzuwarten wie gut und wie lange einmal ausgelieferte UEFI-Firmwares vom Hersteller supported werden. Dabei ist nicht nur interessant, ob Upgrades auf neuere UEFI-Standards (kostenfrei?) angeboten werden, sondern schon die Frage inwiefern Bugfixes und Sicherheitsupdates verfügbar gemacht werden ist spannend. BIOS setzt in diesem Bereich ein wirklich schlimmes Negativbeispiel: Wer hier Fehler oder gar Sicherheitslücken findet (und das wird auch bei UEFI garantiert passieren), wurde oft vom Computerhändler auf den Mainboard-Hersteller verwiesen, dieser verwies auf die Firma welche die Firmware für ihn geschrieben hat und jene sagte dann vielleicht „OK, in unserer Firmware fixen wir das, aber wir können ihnen nicht sagen, ob ihr Mainboard-Hersteller für dieses Mainboard einen Patch rausbringt“. Bleibt zu hoffen, dass das bei UEFI anders laufen wird. Bisher sehe ich leider noch keinen Anlass zum Optimismus.

Beim Thema Fernwartungs-Schnittstelle wird sicherlich nicht nur mir ein kalter Schauer über den Rücken gelaufen sein. Das eröffnet sicherlich interessante Möglichkeiten, aber es eröffnet auch sehr gruselige. Im Hinblick darauf, dass zumindest die deutschen Bedarfsträger sich beim Trojanisieren unserer Computer bisher ziemlich tölpelhaft anstellen, gerate ich hier noch nicht in Panik, aber ich bin beunruhigt. Bedarfsträger einer ganz anderen Art haben auch schon angeklopft und denken laut darüber nach, wie sie UEFI am sinnvollsten mit Trusted Platform Modulen und DRM verheiraten können um die Freiheit der User massiv einzuschränken. Als wenn die PC-Welt noch ein gescheitertes DRM-Konzept bräuchte, säumen ja noch nicht genug rauchende Ruinen den Weg.

Zum Thema Sicherheit ist dann natürlich noch zu erwähnen, dass eine vereinheitlichte Firmwareschnittstelle, die obendrein noch leicht erweiterbar ist und möglicherweise eine Fernwartungsschnittstelle hat selbstverständlich dafür sorgt, dass nicht nur das Auskommen von Viren- und Antiviren-Programmierern für die nächsten Jahre sicher sein dürfte, sondern höchstwahrscheinlich auch deren Rente.

Zu den plattformunabhängigen Treibern wurde wie bereits oben erwähnt schon viel geschrieben — etwa die Hälfte davon offenbar darüber warum das doch nicht oder nur eingeschränkt funktionieren wird. Da ich von hardware-naher Programmierung ohnehin fast nichts verstehe, warte ich hier einfach mal ab, ob ich da in den nächsten Jahren Demonstrationen von sehe oder nicht. Es wäre sicherlich ein praktisches Feature, aber ich gerate hier nicht in aufgeregte Schnappatmung. Als Linux-User sind meine Erfahrungen mit Treibern die vom Hardware-Hersteller stammen nicht so prickelnd gewesen, in der Regel sind Treiber aus dem Linux-Umfeld oder besser noch Treiber aus dem Linux-Kernel (an denen der Hersteller mitgearbeitet hat oder auch nicht) doch die besten.

Die Möglichkeit in Zukunft UEFI durch eine freie Alternative wie Coreboot zu ersetzen wird es nicht geben. Coreboot ist zwar nur ein kleines Projekt, aber wer mal eine Demonstration von denen gesehen hat, wird sich fragen warum man sich den Aufwand mit UEFI überhaupt gemacht hat, wenn man statt dessen eine freie Software wie Coreboot hätte nehmen und weiterentwickeln können. (Vielleicht liegt es daran, dass Coreboot auf Linux aufbaut und damit die GPL ins Boot holt. Eine BSD-Lizenz wäre hier sicherlich erheblich attraktiver.)

Und die Tastaturkürzel die das UEFI User Interface aufrufen wurden natürlich nicht vereinheitlicht oder die Hersteller halten sich nicht dran. :-(

Schließlich und endlich bleibt zu bemängeln, dass sich niemand wirklich Gedanken über die Übergangsphase zwischen BIOS und UEFI gemacht zu haben scheint und insbesondere bei den Softwareherstellern der Support für UEFI auch reichlich spät eintrifft. Was mich zum nächsten und erstmal wichtigsten Punkt bringt:

Der UEFI-Boot-Vorgang

Die wichtigste Änderung bei UEFI für User und Administratoren betrifft den Bootvorgang. Kurz zur Erinnerung, so bootet BIOS (vereinfacht):

  • Mainboard bekommt Strom, Firmware führt Power-on-self-test (POST) aus und piept bei Erfolg.
  • Prozessor erhält Strom und lädt die Start-Instruktionen vom Beginn des Festspeichers (das ist ein spezieller Speicherbereich) und lädt damit das BIOS
  • BIOS lädt aus seinem CMOS Speicher seine Konfiguration, sofern eine vorhanden ist.
  • BIOS führt einen simplen RAM-Check aus, dieser kann per Konfiguration abgebogen werden.
  • BIOS klappert unter Berücksichtigung seiner Konfiguration alle Adressen auf seinen Bussen ab um vorhandene Hardware zu erkennen und konfiguriert sie.
  • Zuletzt werden Speichergeräte angesprochen. BIOS versucht festzustellen von welchen überhaupt gebootet werden kann und von welchem gebootet werden soll.
  • BIOS liest die ersten 512 Byte von dem Speichergerät von dem gebootet werden soll in dem RAM und führt den darin enthaltenen Code aus und gibt damit die Kontrolle über den Rechner an diesen weiter.

Bei UEFI kommen nun vor allem für die letzten Schritte erheblich mehr Funktionen hinzu, „hinzu“ deshalb, weil UEFI durchaus auch in der Lage ist einen BIOS-Bootvorgang zu emulieren und dem Betriebssystem vorzumachen, es wäre von BIOS gebootet worden. Einem so gebooteten Betriebssystem stehen allerdings leider alle erweiterten Funktionen von UEFI nicht zur Verfügung.

Wenn aber ein UEFI-Boot ausgeführt wird, schaut sich UEFI die Speichergeräte etwas genauer an. Es sucht nicht einfach nur das vorkonfigurierte Gerät und liest mehr oder weniger blind die Instruktionen aus dessen ersten 512 Bytes, sondern es schaut ob es eine GUID Partitionstabelle (GPT) vorfindet, analysiert diese und sucht dort nach eventuell vorhandenen UEFI-Boot-Partitionen (die sich überall auf dem Speichergerät befinden können). Dann ist UEFI in der Lage FAT32-Dateisysteme (und ISO-Joliet) zu lesen. Wenn es also auf einem Speichergerät in einer GPT eine als bootfähig markierte Partition vorfindet in der so ein Dateisystem liegt, sucht es in diesem Dateisystem an einem bestimmten Ort nach geeigneten Dateien. Diese Pfade konstruieren sich wie folgt: „\EFI\BOOT\BOOT[$ARCH].EFI“, also z.B. „\EFI\BOOT\BOOTx64.EFI“. Wenn eine oder meherere Dateien (mehrere natürlich nur in verschiedenen Dateisystemen) vorhanden sind und es sich dabei um UEFI-Binaries handelt, kann es dem User diese Bootoption präsentieren und sie auf Verlangen laden. (Problem daran ist nur, dass UEFI nur bestimmte Pfade und Namen prüft, für alle anderen UEFI-Binaries die anderswo in der UEFI-Boot-Partition liegen oder anders heißen, muss man selbst eine Bootoption konfigurieren.)

Was können solche UEFI-Binaries sein? Einerseits natürlich Bootloader, das war ja klar. Aber es können auch Diagnose-Tools sein (zum Beispiel für Festplatten oder RAM), es kann ein Shell-Interpreter sein, der eine UEFI-Shell ausführt, theoretisch kann es alles mögliche sein, was man für UEFI kompilieren kann. Und das beste daran: man kann solche Tools auch wieder verlassen und kommt dann wieder in die UEFI-Umgebung zurück und zwar ohne Neustart. Angesichts dessen wie lange RAID-Controller manchmal beim Booten brauchen, ist das eine echte Erleichterung. (So wie ich diese Sache verstehe, können so auch Tools geladen werden, die auch nach dem Boot eines Betriebssystems aktiv bleiben, so genannte Runtime Services, darunter dürften dann wohl auch die ominösen plattformunabhängigen Treiber fallen. Das könnte aber auch für Debugger interessant sein. Und — natürlich — für Viren und Antiviren.) Prinzipiell können hier auch Dateisystemtreiber nachgeladen werden. Es ist also durchaus denkbar, dass Betriebssysteme wie Linux in Zukunft auf Bootloader verzichten und statt dessem UEFI um ext-, btrfs-, xfs-, und reiserfs-Treiber ergänzen (oder was sonst noch weit verbreitet ist) und dann UEFI direkt den Kernel booten lassen. Andersrum kann man den Kernel natürlich auch einfach in die FAT32-Partition packen, die UEFI ohnehin schon lesen kann.

Noch spannender wird diese Sache im Bezug auf Netzwerkumgebungen. Es ist absehbar, dass für UEFI NFS-, iSCSI- und FibreChannel-Binaries geschrieben werden, bzw. dies geschieht bereits. Aber auch für exotischere Systeme eröffnen sich hier neue Möglichkeiten. Grundsätzlich läßt sich sagen, daß die Einführung von neuen Lösungen in diesem Bereich in Zukunft einfacher werden dürfte, weil es eben nicht mehr erforderlich sein wird einen OptionROM auszuliefern (für den auch erstmal kompatible Sockel vorhanden sein müssen), sondern es mit einem Binary für UEFI getan sein könnte. Ich wäre ja mal an einem PXE-Boot interessiert der über IPsec läuft…

Leider konnte ich den Netzwerkboot unter UEFI noch nicht ausprobieren, mein anderthalb Jahre alter Dell Latitude Laptop hat zwar UEFI, allerdings konnte ich bisher keine Dokumentation darüber finden, ob hier ein UEFI-Netzwerkstack dabei ist oder nicht und wie man den wenn dann anspricht (ein PXE OptionROM ist natürlich vorhanden). Im Rechenzentrum haben wir zwar jetzt die ersten Server mit UEFI, in deren Einstellungsdialogen (die anders als bei Dell enttäuschend BIOS-ähnlich aussehen) sich auch die Option findet den Netzwerkstack zu (de)aktivieren, aber auch hier fehlt es bisher an Dokumentation. Die Manpage von efibootmgr (ein Programm um unter Linux UEFI-Booteinträge zu konfigurieren) stimmt mich nicht optimistisch:

CREATING NETWORK BOOT ENTRIES
A system administrator wants to create a boot option to network boot (PXE). Unfortunately, this requires knowing a little more information about your system than can be easily found by efibootmgr, so you've got to pass additional information - the ACPI HID and UID values.
These can generally be found by using the EFI Boot Manager (in the EFI environment) to create a network boot entry, then using efibootmgr to
print it verbosely. Here's one example:
Boot003*Acpi(PNP0A03,0)/PCI(5|0)/Mac(00D0B7F9F510)\ACPI(a0341d0,0)PCI(0,5)MAC(00d0b7f9f510,0)
In this case, the ACPI HID is "0A0341d0" and the UID is "0". For the zx2000 gigE, the HID is "222F" and the UID is "500". For the rx2000 gigE, the HID is "0002" and the UID is "100". You create the boot entry with: efibootmgr -c -i eth0 -H 222F -U 500 -L netboot

Alles klar, oder?

Ich freue mich schon darauf, Ungetümer wie „Boot003*Acpi(PNP0A03,0)/PCI(5|0)/Mac(00D0B7F9F510)\ACPI(a0341d0,0)PCI(0,5)MAC(00d0b7f9f510,0)“ von Hand in der UEFI-Shell eingeben zu müssen, wenn mal was debuggt werden muss. So kompliziert ist ja noch nichtmal mein Root-Passwort. Und selbstverständlich ist mir die ACPI HID meiner Netzwerkkarte absolut geläufig, die lerne ich noch vor meiner IP-Adresse! — Hier muss eindeutig noch erheblich nachgebessert werden, so wie das bisher gelöst ist, ist es selbst für routinierte Admins zu unhandlich.

Soviel erstmal allgemein zum Bootvorgang. Für User und Administratoren wird erstmal relevant sein, dass UEFI alle angeschlossenen Speichergeräte nach Bootoptionen durchsucht und diese in einer Bootauswahl präsentiert — soweit zumindest die Theorie. Die Praxis sieht leider anders aus:

Henne oder Ei?

Wer heute ein Betriebssystem installieren will, macht das mit CDs, DVDs, USB-Sticks oder einer PXE-Umgebung. (Oder verwendet etwa noch jemand Schlappscheiben?) Die CDs, DVDs und USB-Sticks die hierfür normalerweise verwendet werden sind für BIOS ausgelegt. UEFI kann diese allerdings über seine Legacy-Boot-Optionen booten. Soweit so gut. Erstmal.

Das Problem hieran ist: ein so gebootetes Betriebssystem kann nicht auf die neuen Funktionen von UEFI zugreifen. Es hat daher absolut keine Möglichkeit einen UEFI-Bootloader den es installiert in die UEFI-Konfiguration einzutragen. Schlimmer noch: Es hat nichtmal eine Möglichkeit festzustellen, ob der Computer auf dem es läuft überhaupt mit UEFI ausgestattet ist. Wem jetzt noch nicht das Augenlid zuckt: Bisher scheint kein Betriebssystem-Installer auf gut Glück einfach einen UEFI-Bootloader auf die Festplatte legen zu wollen, in der Hoffnung dass UEFI ihn dort finden und als Bootoption anbieten könnte. Der Grund ist offenbar, dass UEFI eben pro Bootpartition nur eine bestimmte Datei als Bootoption darstellt, nämlich in den meisten Fällen „\EFI\BOOT\BOOTx64.EFI“ — und die meisten Installer wollen genau dort offenbar nichts hinlegen, damit sie nicht eine Datei überschreiben, die dort schon liegen könnte. Na, zuckt es jetzt im Augenlid?

Die einzige Möglichkeit die in so einen Szenario bleibt, ist die vor den Kopf geschlagenen Hände wieder runter zu nehmen, sich ein Tutorial über die UEFI-Shell zu schnappen, diese zu Booten (ggf. muß sie vorher installiert werden) und in dieser Shell einen vorher installierten UEFI-Bootloader des Betriebssystems auszuwählen und zu booten. Wenn die UEFI-Shell sich mehr wie eine richtige Shell und weniger wie DOS anfühlen würde, würde mir das ja noch Spaß machen, aber den meisten Leuten sicherlich nicht. Ist das System einmal unter UEFI gestartet, kann man geeignete Tools verwenden um einen Booteintrag in UEFI anzulegen, damit der nächste Boot dann ohne Shell geht. Zumindest dieser letzte Schritt ist unter Linux mit efibootmgr ziemlich einfach, um nicht zu sagen trivial. Die vorigen Schritte sind es aber nicht.

Halten wir also fest: von einem per Legacy Boot geladenen System (egal ob es ein Installer, ein Live-System mit Installer oder ein fertig installiertes Betriebssystem ist) auf einen UEFI-Boot umzusteigen ist nur etwas für Leute die Spaß am Gerät und Nerven haben. Wer es hin bekommt darf sich freuen. Aber erzählt Euren Freunden gar nicht erst, dass das prinzipiell möglich ist, sonst müsst Ihr ihnen dabei helfen, wenn sie es nicht hinbekommen.

Der einzige für normale User gangbare Weg der zu einem nativen UEFI-Boot des Betriebssystems führt ist der über einen Installer, der schon per UEFI gebootet wird. (Hier gibt es wieder einen Anlass die Hände vor den Kopf zu schlagen: bisher geben viele UEFI-Systeme per default einem Legacy-Boot den Vorzug, auch wenn ein UEFI-Boot möglich wäre!) Ich konnte aktuell keine Linux-Distribution finden, die schon einen bei mir funktionierenden Installer für UEFI ausliefert. Bei Ubuntu, Fedora und OpenSuse scheint die Entwicklung am weitesten voran geschritten zu sein, dort sucht man leider relativ lange nach hilfreichen und guten Anleitungen. Bei ArchLinux sieht es auch schon gut aus, dort findet man auch schnell eine meiner Meinung nach tolle Anleitung.

Wie sieht es in der Praxis aus? — Ich hab versucht geeignete USB-Sticks zu erstellen (ich installiere nur noch vom USB-Stick, drehende Scheiben sind mir zu langsam und ich hasse das schrabbelnde Geräusch in den Laufwerken) und damit zu installieren. Bei Ubuntu liegt mein jüngster Versuch am weitesten zurück, mit 12.04 Precise Pangolin klappte es bei mir im Mai noch nicht (12.10 Quantal Quetzal teste ich voraussichtlich in den nächsten Wochen). Mit Fedora 16 Verne hatte ich kurz nach seinem Release auch kein Glück. OpenSuse mag ich nicht. Bei ArchLinux konnte ich vor zwei Monaten zwar den Installer unter UEFI booten, er hat jedoch keinen funktionsfähigen Bootloader installieren können. (Ich hab dann wieder zur UEFI-Shell gegriffen, dann ging es natürlich.)

Selbst wenn man dem Installer hilft (eigentlich erwarte ich aber, dass alles „out of the box“ funktioniert), gibt es später zum Teil noch Probleme mit dem Handling. Bei Ubuntu 12.04 wird nach Kernel-Updates die grub.cfg in den Ordner /boot/grub/ gelegt. Der Bootloader liegt aber in „/boot/efi/efi/grub/grub.efi“ und erwartet seine Config im selben Ordner unter „/boot/efi/efi/grub/grub.cfg“. Bei ArchLinux und CentOS hatte ich ähnliche Probleme.

Fedora und Ubuntu sind nun Linux-Distributionen die eher für den Desktop-Einsatz gedacht sind und die zumindest ich auf Servern eher nicht haben möchte. ArchLinux ist für Server schon interessanter, da habe ich es aber bisher nicht ausprobiert. In der Firma setzen wir auf unseren Servern bekanntermaßen vorwiegend CentOS ein und da scheint in der aktuellsten Version 6.3 der UEFI Support bisher nicht sehr weit fortgeschritten zu sein. Nun wird das Release von RHEL 6.4 schon erwartet und CentOS 6.4 dürfte zügig folgen. Ich rechne durchaus damit, dass RHEL 6.4 besseren UEFI-Support haben wird, denn gerade im Business-Bereich auf den RHEL ausgerichtet ist breitet sich UEFI durch SandyBridge und das Windows-8-Logoprogramm schnell aus. (Solange UEFI den LegacyBoot aber noch anbietet, ist RedHat hier wie alle anderen Betriebssystemhersteller noch nicht in Zugzwang.)

Gerade was den Einsatz auf der Arbeit angeht, habe ich aber noch ein Problem: Im Rechenzentrum hantieren wir nur ungern mit CD/DVD-Laufwerken und USB-Sticks, da wollen wir lieber mit unserer schönen, mit viel Mühe gepflegten PXE-Umgebung arbeiten. Und da wird es nun wirklich haarig. Ein normaler PXE-Boot per OptionROM ist für UEFI ein LegacyBoot, das bringt mich nicht weiter. Denn auch wenn ich bereit bin auf meinen privaten Rechnern oder auch meinem Arbeitsgeräten von der Firma mit der UEFI-Shell zu hantieren, bei Servern kommt das überhaupt gar nicht in die Tüte. Da muss das Setup zuverlässig, einfach und am besten automatisierbar ablaufen. (Und für User die nicht so drauf sind wie ich finde ich alles andere auch unzumutbar.)

Bei UEFI muß der PXE-Boot also offenbar über den Netzwerkstack von UEFI erfolgen. Die Dokumentation hierzu ist bisher leider noch sehr spärlich gesät und wie bereits erwähnt kommt offenbar auch längst nicht jede UEFI-Firmware mit einem eigenen Netzwerkstack daher. Bei unseren Servern scheint das zwar der Fall zu sein, aber bisher habe ich es dort noch nicht ans Laufen bekommen. Wenn es mir endlich gelingt, werde ich sicherlich wieder etwas dazu schreiben.

Ich fasse mal zusammen:

  • Viele (nicht alle) UEFI-Systeme booten bevorzugt im Legacy-Modus — wenn die User es nicht anders einstellen — auch wenn ein UEFI-Boot möglich wäre.
  • Aus einem per Legacy-Boot gestarteten Betriebssystem führt kein einfacher Weg zu einem UEFI-Boot.
  • Bisher kann zumindest ich nicht von vielen erfolgreichen UEFI-Boots von Betriebssystem-Installern „out of the box“ berichten.
  • Wie Netzwerk-Boots im UEFI-Modus funktionieren ist mir noch ein völliges Rätsel.

Das sieht mir alles sehr nach einem ernsthaften Henne-Ei-Problem aus. Und da kommt dann noch hinzu, dass die Industrie sich mit UEFI schon seit vielen Jahren ziert. Ich denke ich übertreibe nicht, wenn ich sage dass aus meiner Sicht hier genügend Hennen und Eier rumlaufen und -liegen für eine ganze Geflügelfarm.

Alles in allem muss ich sagen, dass mir das ganz und gar nicht gefällt. Wenn ich mal vergleiche mit der Zeit als ich mir angelesen habe wie PXE funktioniert und mir zum ersten Mal eine PXE-Umgebung gebaut habe, oder als ich zum ersten mal mit den Bootloader Uboot in Berührung kam, dann kommt UEFI dabei ganz schlecht weg. Ich hab wesentlich mehr Zeit darauf verwendet und weiß wesentlich weniger mit Gewissheit. Die Komplexität ist mir für mich selbst schon zu hoch, für normale User finde ich das wirklich unzumutbar.

Und als wenn das reine Booten von UEFI noch nicht kompliziert genug wäre, kommt jetzt auch noch Kryptographie dazu:

Secure Boot: Das Imperium schlägt zurück

Alle Leute die mit Computern Geld verdienen (außer den Viren- und Antiviren-Programmierern) wollen, dass unsere Computer sicherer werden. Es gab in der Vergangenheit schon Viren, die sich in den Bootprozess des Betriebssystems eingeschrieben haben und so dafür gesorgt haben, dass sie noch vor dem Betriebssystem geladen werden und so ein derart hohes Zugriffslevel auf dem Computer haben, dass Antiviren-Programme im Grunde gar keine Chance mehr haben sie zu finden. Wir können davon ausgehen, dass solche Angriffe wenn sie in Form von Viren in freier Wildbahn beobachtet werden, im wesentlich weniger sichtbaren Feld der gezielten Attacken auf ausgewählte Ziele schon viel länger und häufiger stattfinden. Deswegen wird im Rahmen von UEFI an einer Technik gearbeitet, die das Problem entweder lösen oder zumindest verlagern soll: UEFI Secure Boot.

Dabei soll UEFI (der damit implizit vertraut wird, wie berechtigt dieses Vertrauen ist wird sich noch zeigen) bevor sie Bootloader (oder Betriebssysteme direkt) bootet kryptographische Signaturen prüfen, die an der Software angebracht werden. Hierbei gibt es mehrere Probleme:

  • Im Hinblick darauf, wie lange BIOS sich gehalten hat, muss bei UEFI eine ähnlich lange Lebenszeit befürchtet werden. In 30 Jahren könnten bereits Quantencomputer existieren, wodurch alle Kryptographie die derzeit für Secure Boot vorgesehen ist null und nichtig wäre. Eine Prozedur wie man in so einem Fall zeitnah und den Markt möglichst weit durchdringend neue UEFI-Versionen durchsetzt die dann auf „Post Quantum Computing“-Kryptographie setzt ist noch völlig unklar. Hier wird m.E. etwas wenig voraus geplant.
  • Für die Signaturen wird mal wieder eine Public Key Infrastructure (PKI) benötigt. Und was bietet sich da besser an als die PKI von SSL, die wir auch bei HTTPS seit so vielen Jahren einsetzen. Wenn wir alle ganz fest die Augen zukneifen und uns die Finger in die Ohren stecken, dann merken wir garantiert nicht, dass diese PKI mittlerweile nur noch zu Hälfte steht und die andere Hälte schon eine rauchende Ruine ist.
  • Sie wie es aussieht, werden Geräte mit UEFI in jedem Fall mit einer Art vorinstalliertem Master-Key ausgeliefert werden. Sie werden Signaturen, die von diesem Key abgeleitet sind vertrauen. Offenbar wird eine CA wie Thawte diesen Key verwalten. Es steht also zu befürchten, dass Bedarfsträger und andere Malware-Autoren sich über kurz oder lang dort Signaturen besorgen wollen werden — und welches Unternehmen würde da Nein sagen wollen? Hinzu kommt noch, daß es in der Vergangenheit schon häufiger zu Schlampereien bei CAs gekommen ist — auch bei Thawte — durch die Dinge signiert wurden, die niemals hätten signiert werden dürfen. In der Vergangenheit wurde auch schon bei CAs eingebrochen.
  • Außerdem werden wohl mindestens die meisten PCs (bis auf Server, vielleicht) mit einem vorinstallierten Key von Microsoft ausgeliefert werden, damit sie ein vorinstalliertes Windows booten können oder man auf dem PCs problemlos Windows installieren kann.
  • Es ist eine Blacklist vorgesehen, über die Signaturen auch widerrufen werden können.
  • Es soll einen Setup-Mode geben, in dem der User eigene Keys denen er vertraut installieren kann. Ob dieser Modus auch für Enduser verfügbar sein wird, oder nur auf Buisness-PCs und Servern, ist derzeit nicht mit beruhigender Klarheit zu erfahren. Für’s erste sieht es so aus, als wolle zumindest Microsoft den Shitstorm vermeiden, der anstünde wenn PCs auf den Markt kämen, auf denen man nur Windows installieren kann. Aber ob das so bleibt? So ein Monopol kann sehr verlockend sein, besonders bei unseren handzahmen Kartellbehörden.
  • Von Apple war noch nicht viel zu hören darüber, ob und wie sie Secure Boot einsetzen werden und welche Keys sie vorinstallieren werden.
  • Es ist noch nicht klar, wie Linux in Zukunft auf PCs installiert werden wird. Es gibt verschiedene Möglichkeiten. Secure Boot abzuschalten wäre die einfachste, aber das würde den Eindruck erwecken, dass Linux zu installieren einen Sicherheitsnachteil bringt. Man könnte Linux direkt von UEFI booten lassen und müsste dann den Linux-Kernel signieren lassen (aufwendig, muß man bei jeder neuen Version wiederholen), oder man müsste den Bootloader signieren lassen (wobei es für Linux meiner Zählung nach mindestens fünf gibt: grub, grub2, lilo, elilo und uboot, auch hier müsste wieder jede neue Version signiert werden). Oder man entwickelt einen extrem simplen Chainloader der signiert wird, selten Updates bekommt und der nur dazu da ist einen Bootloader zu laden (das wird häufiger unter dem Begriff „shim“ diskutiert). Was auch immer man signieren lassen wird: die spannende Frage ist, wo die Signatur herkommt. Man könnte sich zum Beispiel eine Signatur bei Microsoft holen. Viele Linux-User werden sich mit der Hand Microsofts so nah an der Kehle ihres Betriebssystems vermutlich nicht wohl fühlen. Der Key könnte auch von einer CA stammen, die im Idealfall anders als Microsoft kein Eigeninteresse daran haben könnte Linux von bestimmten Geräten fernzuhalten, was aber noch nicht heißt, dass die garantiert immer unparteiisch sein wird, denn eine öffentliche Aufsicht über die Signatur-Verfahren ist nicht vorgesehen. Folglich gibt es wenn es zu Problemen kommen sollte auch erstmal keinen schnellen Klageweg, sondern es muss erstmal vor Gericht dargelegt werden wo das Problem ist und das Gericht muss das auch so sehen.
  • Microsoft veranstaltet routinemäßig für ihre Betriebssystem-Versionen Logo-Programme. Für Windows 8 sieht es derzeit so aus, als müssten PCs damit sie das Windows 8 Logo erhalten in jedem Fall Secure Boot unterstützen und bei Auslieferung auch aktiviert haben. Einzig bei Servern soll die Option bestehen, dass bei diesen Secure Boot zwar vorhanden, aber nicht aktiv ist. Wie das bei zukünftigen Windows-Versionen aussehen wird ist noch völlig unklar. Es zeichnet sich aber ab, dass Microsoft Secure Boot nutzen möchte, um neue Märkte abzustecken, so sollen nämlich Tablets mit Windows 8 so konfiguriert ausgeliefert werden, dass Secure Boot dort nicht deaktiviert werden kann. Wer solche Anstalten macht erinnert mich zwangsläufig an den berühmten Ausspruch Walter Ulbrichts: „Niemand hat die Absicht eine Mauer zu errichten.“
  • Nicht nur Software, auch Hardware die Firmware-Komponenten mitbringt (etwa RAID- und Netzwerk-Controller) muss für diese Software Signaturen vorweisen. Unter Umständen werden daher die Keys diverser Hardwarehersteller auf neuen Geräten vorinstalliert sein, was die Angriffsfläche auf die PKI noch weiter vergrößert. Vor allem aber werden Kompatibilitätsprüfungen beim Aufrüsten von Computern komplexer. Genau das, was man als Admin nicht hören will. Beim Aufrüsten oder beim Einbau von Ersatzteilen nicht nur mit BIOS- und Firmware-Versionen zu kämpfen, sondern auch noch mit Signaturen… klar, mach ich gerne, ich genieße es im Rechenzentrum in mieser Luft zwischen lauten Maschinen im Luftzug zu stehen und dabei Kryptographische Fingerprints zu vergleichen.

So ganz traue ich dieser Sache nicht nicht, aber ich mache mir auch keine allzu großen Sorgen. Wie zuletzt bei der Playstation 3 zu sehen war, ist der sicherste Weg für einen Hersteller seine Digital Restrictions so schnell wie möglich gebrochen zu bekommen der, Linux von seinen Geräten auszusperren. Niemand hat sich die Mühe gemacht die Schutzfunktionen der PS3-Firmware zu überwinden, solange Linux auf den Geräten einfach so ausgeführt werden konnte. Kaum wurde diese Funktion (im April 2010) weggenommen, stand schon der erste Jailbreak für die Playstation 3 zur Verfügung (August 2010). Und als Kollateralschaden konnte mit diesem Jailbreak nicht nur wieder Linux installiert werden, sondern kurze Zeit später ist auch der Kopierschutz der Plattform den Weg aller Kopierschutze gegangen.

Mach wir uns nichts vor: Es geht hier auch um Digital Restrictions. Und somit stellt sich hier die Frage was wichtiger ist: die PC Plattform insgesamt ein bisschen sicherer zu machen, oder wirtschaftliche Interessen einzelner Unternehmen durchzusetzen. Wenn mit Secure Boot wirklich nur die Plattform sicherer gemacht werden soll, dann wäre es in jedem Fall klug dafür zu sorgen, dass Linux- und BSD-Distributionen (und alle sonstigen Betriebssysteme) weiterhin problemlos installiert werden können und dafür auch Garantien zu geben. Malware-Entwickler und sogenannte Bedarfsträger werden ohnehin versuchen Secure Boot anzugreifen. Es ist aber nicht nötig die wesentlich größere Linux-Gemeinde hier mit ins Boot zu stoßen (Wortspiel nicht beabsichtigt) und durch ein Aussperren von Linux einen riesigen Anreiz für das Brechen von Secure Boot zu schaffen.

Unabhängig davon muss ich sagen, dass ich meine Computer nicht erst mit einem Jailbreak versorgen müssen möchte, selbst wenn ich zuversichtlich bin, dass dies nötigenfalls möglich wäre. (Aus dem gleichen Grund kaufe ich keinen iPhones mehr.) Auf Servern kommt das im Grunde überhaupt nicht in Frage (hier ist der Markt für Linux aber auch so groß, dass die Gefahr viel geringer ist), aber auch auf PCs oder gar Tablets möchte ich wirklich nicht, dass solche Unsitten da überhaupt erst einreißen.

Fazit

Ich sage es ganz offen: Ich hätte lieber Coreboot mit GPT-Support auf meinen PCs, auch auf den Servern, als UEFI. Aber wenn ich nur die Wahl zwischen BIOS und UEFI bekomme, nehme ich lieber UEFI. Aber nur knapp. Bei der Entwicklung von UEFI wurde m.E. viel zu sehr von der technischen Seite her gedacht und viel zu wenig an die künftigen User.

Hier muß die Schuld aber fair verteilt werden:

Da ist einerseits der technische Standard UEFI, der sicherlich erheblich schlanker und benutzerfreundlicher sein könnte. Ein auf das allernötigste reduzierter BIOS-Nachfolger würde letztlich auch die Angriffsfläche verkleinern. Gerade unter dem Sicherheitsaspekt verstehe ich ohnehin nicht, warum hier nicht zu einer gemeinsamen, quelloffenen Lösung gegriffen wurde. Einwände gegen die GPL kann ich an der Stelle verstehen, aber es spräche sicherlich nichts gegen etwas unter einer BSD-ähnlichen Lizenz. (Erinnert sich noch jemand an die schöne OpenFirmware der alten Suns? Beim bloßen Gedanken daran bekomme ich schon feuchte Augen.) Statt mehrerer, unabhängiger Implementationen könnte man sich auf eine oder zwei gut und öffentlich ge-audit-e Implementationen verlassen und mehr Energie in Updates für diese Software stecken. Wenn der Funktionsumfang überschaubar genug wäre, könnte man den Code vielleicht sogar durchbeweisen (falls das was bringt).

Auf der anderen Seite stehen Hardware- und Betriebssystemhersteller (von denen einige an UEFI mitgewirkt haben), die sich wirklich besser um die Benutzerfreundlichkeit hätten bemühen können. Wie üblich schneidet hier Apple besser ab als alle anderen, aber das will nicht viel heißen. Eine Schildkröte ist auch schneller als eine Schnecke, das macht sie noch lange nicht zu einem schnellen Tier. Vor allem aber wäre eine koordinierte Einführung von UEFI über einen kurzen Zeitraum von wenigen Jahren sicherlich sinnvoller gewesen als das nun seit bald 10 Jahren andauernde Rumgeeiere. (Wieder etwas, das Apple gut vorgemacht hat, dort erfolgte die Umstellung binnen eines einzigen Jahres.) Vor allem aber fehlt es UEFI an einem: einer vernünftigen, durchdachten und einfachen Migrationsstrategie die Henne-Ei-Probleme vermeidet.

Schließlich bleibt die politische Dimension. Im Grunde hat sich das Thema UEFI für mich schon fast erledigt gehabt, als überhaupt die Diskussion aufkam, ob man damit Betriebssysteme wie Linux von einer Plattform aussperren könnte. Warum wurde das nicht von vornherein abgebogen? Warum gibt es keine gemeinsame Organisation aller an UEFI interessierten, die einfach allen Betriebssystemen die glaubhaft machen können, dass sie bemüht sind keine Malware zu sein, Signaturen gibt? Es gibt bis heute keine einzige solche Digital Restriction Maßnahme, die nicht überwunden wurde. Aus der Alchemie wurde wenigstens irgendwann die Lehre gezogen, dass sich Gold eben nicht machen läßt. Wie lange wird es in der Computerindustrie noch dauern, bis die Lehre gezogen wird, dass Digital Restrictions einfach nicht funktionieren?

Alles in allem: UEFI kommt, soviel steht wohl fest. Es bringt ein paar schöne Neuerungen und leider auch einen Haufen neuer Probleme. Vielleicht wird’s dann ja was mit dem UEFI-Nachfolger. Wir sprechen uns in 30 Jahren.

Da waren’s plötzlich 0b101 (bzw. 5)

24. Oktober 2012 von Daniel Heitmann

Von Uberspace habe ich (Daniel) auf kuriosem Wege erfahren. Kurz nach dem CCC-Congress 27c3 in Berlin twitterte ich über Daniel J. Bernsteins Vortrag „High-speed high-security cryptography: encrypting and authenticating the whole Internet“ und landete daraufhin wohl auf Jonas Radar. Den noch jungen @ubernauten-Account fand ich kurz darauf durch Zufall unter meinen Followern und klickte neugierig auf den Link, den man bei Twitter als Webseite hinterlegen kann. Ich war unendlich begeistert von der Idee, Webhosting mal anders zu machen und der Art, wie Uberspace mich als Nerd direkt angesprochen hat. Also klickte ich mir einen Account, schaute mich um, probierte aus und nahm kurzerhand Kontakt mit Jonas auf, um meine Begeisterung abzuladen und dem gerade gestarteten Projekt „Uberspace“ viel Erfolg zu wünschen.

Und so kam es, dass wir von unverschlüsselten Direct Messages bei Twitter zu natürlich mit GPG verschlüsselten Mails schwenkten, bis wir schlussendlich regelmäßig via Jabber (natürlich mit OTR) in Kontakt standen. Man tauschte sich aus und ich bekam immer mal wieder kurze Erfolgsmeldungen, dass Uberspace wohl einen Nerv getroffen haben musste: Die User wurden mehr, nahezu jeder war begeistert, endlich eine anständige Shell zu bekommen, um nicht mehr in kruden Webhosting-Panels herumklicken zu müssen. Ich war seinerzeit noch in der Ausbildung zum Fachinformatiker für Systemintegration und so gerne ich auch für meinen damaligen Ausbilder gearbeitet habe: Irgendwann einmal für Uberspace zu arbeiten, habe ich mir durchaus vorstellen können.

Die vergangene Oktober-Woche habe ich auf die Einladung von Jonas hin in Mainz verbracht – meine erste Woche als Mitarbeiter von uberspace.de. Und es war toll: Die Arbeitszeiten decken sich mit denen meiner inneren Uhr – es gibt nämlich keine. Als ohnehin ständig erreichbarer Nerd kümmere ich mich seit dem 15. Oktober zu allen erdenklichen Uhrzeiten (gut, vielleicht nicht gerade früh morgens) um die Support-Anfragen unserer Uberspace-User.

Verbrachte ich den ersten Tag nach größtenteils damit, mir die Infrastruktur und die Scripte zur Verwaltung der Uberspace-Accounts erklären zu lassen, registrierte ich nach einigen Stunden die ersten Domains und nahm mir auch gleich die ersten Anfragen von Uberspace-Usern vor. Den Tag darauf leistete uns Boni Gesellschaft, es gab leckere Brötchen und so saßen wir zu dritt an unseren Notebooks und sorgten für zügige Antworten im Ticketsystem.

Immer wieder wurden mir immer neue Tipps gegeben, Features vom Request Tracker gezeigt und kleinere Kniffe im Domainrobot verraten, die die doch hin und wieder nervigen Einarten von Domainregistrierungen erheblich vereinfachen.

Den Freitag machten wir uns verhältnismäßig früh gegen 12 auf den Weg in Richtung Frankfurt, um einen Blick auf die beiden Rechenzentrums-Standorte zu werfen. Auch wenn ich aus NRW kommend nur bei größeren Umbauten im Rechenzentrum gebraucht werde, sollte man doch zumindest alles mal gesehen haben. Und als Technik-Interessierter ist so ein Einblick in einem bis dato fremde Rechenzentren ohnehin immer ein Neugierde-Auslöser.

Zuerst stand die Databurg auf dem Plan, in der wir als Plusline-Kunde einige Racks angemietet haben. Einen Kaffee und eine Strassenbahnverbindung später fanden wir uns dann bei rh-tec ein, deren Rechenzentrum wir in letzter Zeit vermehrt mit neuen Uberspace-Hosts ausstatten. Die saubere Verkabelung fiel mir bei sämtlichen Racks an beiden Standorten direkt ins Auge, obwohl wir von Boni vorgewarnt wurden, dass er damit noch nicht zu 100% zufrieden ist. Der Eindruck von beiden Standorten war mehr als gut und ich bereue es doch ein wenig, so weit weg zu wohnen, dass es sich für kleinere Arbeiten nicht lohnen wird, extra mit dem ICE nach Frankfurt zu pendeln. Gegen 16 Uhr endete die erste Uberspace-Woche allerdings auch schon und etwa 150 Minuten später fand ich mich bereits am Düsseldorfer Hauptbahnhof wieder, um das Wochenende anklingen zu lassen.

Als Nerd, der sich größtenteils aus dem Backofen und in Imbissbuden ernährt, war retrospektiv übrigens auch das zumeist selbstgekochte Essen der vergangenen Woche mehr als erwähnenswert.

Ich freu mich drauf, bei Uberspace zu arbeiten. Und auf alles, was noch kommen mag.

Die Annalen der Bash-Geschichte

02. Oktober 2012 von Christopher Hirschmann

Heute mal wieder eine Episode aus den „adventures in modern computing“.

Wer die Bash (oder eine andere gängige Shell, hier geht es aber spezifisch um die Bash) kennt, wird höchstwahrscheinlich ihre äußerst nützliche History-Funktion kennen. Die Bash loggt die Befehle die man auf ihr eingibt und hält sie in einem Puffer bereit, auf den man verschiedentlich zugreifen kann um Befehle erneut auszuführen, anzupassen oder sich einfach ins Gedächtnis zu rufen. In erster Linie ist dies wohl als Arbeitserleichterung gedacht, es hat aber auch Anklänge eines regelrechten Logs, allerdings mit der Einschränkung, daß in der History normalerweise nur die 500 letzten Befehlszeilen vorgehalten werden (mit ein paar Tricks drumherum, z.B. kann man das mehrfache Speichern wiederholter Befehle ganz oder teilweise vermeiden). 500 wird heute vielen als ein vergleichsweise kleiner Wert erscheinen und das sehen offenbar die meisten Betriebssystem-Schmiede genauso und liefern ihre Software mit höheren Voreinstellungen aus (manchmal nur 1000 Zeilen, manchmal aber auch 10000 oder 40000 oder noch mehr). Speicher ist schließlich billig, oder?

Nun, wer eine SSD sein Eigen nennt sieht das vermutlich etwas anders, aber vor allem gibt es noch einen anderen Speicher der nicht sooo billig ist: RAM. Normalerweise liest die Bash nämlich den gesamten Inhalt ihrer History-Datei ein wenn sie startet. Das verlangsamt den Start der Bash und erhöht ihren RAM-Bedarf. Wer also die Bash History auch als Log nutzen möchte und daher die Variable $HISTSIZE auf einen sehr hohen Wert setzt, zahlt dafür mit verringerter Performance. Das fällt auf aktuellen Computern vermutlich gar nicht mal so sehr auf, zumindest bis die Datei so allmählich über ihre ersten Megabytes hinaus gewachsen ist. Auch für diesen Fall ist in der Bash bereits vorgesorgt: es gibt neben der Variablen $HISTSIZE noch die Variable $HISTFILESIZE, erstere definiert wie viele Zeilen der Datei beim Starten der Bash in den Puffer gelesen werden, letztere ist nützlich, um die tatsächliche Größe der Datei (in Zeilen, nicht in Byte) abweichend zu definieren. Eine Möglichkeit ist also, $HISTSIZE vergleichsweise klein zu wählen und $HISTFILESIZE extrem groß zu wählen. So spart man RAM und auch etwas Zeit beim Starten der Bash, kann aber sehr viele Befehle in der History halten. Die sind dann zwar nicht direkt über Funktionen der Bash zugänglich, aber es gibt ja noch die Möglichkeit die Datei zu durchsuchen, z.B. mit dem Programm grep.

Nun würde ich aber gerne ein ewiges Log haben. Auf Kundensystemen ist das zwar kein Ersatz für eine gute Dokumentation des Setups, aber es ist eine gute Ergänzung dazu. Auf meinen eigenen Computer will ich das sogar noch viel mehr, weil ich da auch häufiger mal Dinge ausprobiere, für die ich gar keinen alltäglichen Nutzen habe, an die ich mich aber gerne „erinnern“ können möchte, selbst wenn es nur Spielereien sind wie etwa „Twitter auf der Kommandozeile nutzen“ oder „Nachgucken was für ein Wochentag ein bekanntes historisches Datum hatte“ oder dergleichen. Hier habe ich dann unweigerlich das Problem, daß die History auf mehr Zeilen anwachsen könnte als ich in $HISTFILESIZE erlaubt habe. Zwar kann ich den Wert dort sehr hoch setzen, sogar lächerlich hoch, aber trotzdem ist es eine Grenze und es ist weiterhin nötig, daß die Bash sich anschaut wie groß die Datei überhaupt ist, was mir ja völlig egal wäre. Zumal ich stark annehme, daß ich niemals mehr Zeilen in der History-Datei erlauben könnte als die Bash als größte Zahl unterstützt. Die Bash selbst mag zwar nicht typisiert sein, aber trotzdem wird sie nicht unendlich lange Zahlen verarbeiten können.

Ich hab ein bißchen rumprobiert, konnte aber keine wirklich gute Lösung finden. Laut Dokumentation bewirkt eine $HISTFILESIZE von 0, daß gar keine History-Datei unterhalten wird. Ich würde da am liebsten „unendlich“ reinschreiben, aber das geht nunmal nicht. Die Dokumentation der Bash sagt aber noch, daß das Fehlen der Variable $HISTFILESIZE dazu führen würde, daß die Datei beliebig groß sein darf. Also änderte ich meine .bashrc:

export HISTSIZE=10000
#export HISTFILESIZE=1000000000000000000
unset HISTFILESIZE

Das hatte aber nicht den gewünschten Effekt. Die Bash setzte den Wert von $HISTFILESIZE beharrlich auf 10.000, also den gleichen Wert wie $HISTSIZE. Nur warum?

Ich überlegte bereits, mir eine Logrotate-Config für die History-Datei zu erstellen (das mache ich vielleicht trotzdem noch), aber es ließ mich nicht in Ruhe, daß das nicht mit „Bordmitteln“ der Bash funktionieren wollte, denn eigentlich sollte das was ich mir wünschte durchaus gehen. Nach längerem vergeblichen Suchen (ich kann gar nicht oft genug erwähnen, wie sehr ich Foren hasse), hab ich dann auf einer Mailingliste den entscheidenden Hinweis gefunden: Die Bash liest zuerst ihre Configs ein und dann die History-Datei und wenn zu diesem Zeitpunt die Variable $HISTFILESIZE nicht gesetzt ist, wird sie auf den gleichen Wert gesetzt wie $HISTSIZE. Das ist natürlich vollkommen bescheuert, denn dann kann ich ja de facto diese Variable nicht nicht setzen, jedenfalls nicht in einer Config der Bash. Ich müßte also nach jedem Start einer Bash manuell ein „unset HISTFILESIZE“ auslösen, damit meine History nicht auf 10.000 Zeilen zusammengekürzt wird. Unpraktisch.

Wie soll man also die $HISTFILESIZE nicht setzen können, wenn ihr Fehlen gleichzeitig dazu führt, daß sie automatisch gesetzt wird? Ich erinnerte mich düster, daß die Bash durchaus einen Unterschied macht zwischen gar nicht gesetzten Variablen und gesetzten Variablen denen kein Wert zugewiesen wurde, also „NIL“ sind, wie man so schön sagt. Vielleicht meinte die Dokumentation genau das?

Sie meinte es. Dieser Teil meiner .bashrc sorgt für eine History-Datei ohne Größenbegrenzung, von der dann die letzten 10.000 Zeilen in den History-Puffer geladen werden:

# read this number of lines into history buffer on startup
# carefull with this, it will increase bash memory footprint and load time
export HISTSIZE=10000
# HISTFILESIZE is set *after* bash reads the history file
# (which is done after reading any configs like .bashrc)
# if it is unset at this point it is set to the same value as HISTSIZE
# therefore we must set it to NIL, in which case it isn't "unset",
# but doesn't have a value either, go figure
#unset HISTFILESIZE
export HISTFILESIZE=""

Wie George Takei sagen würde: Oooh my!

Enterprise, my ass

10. August 2012 von Jonas Pasche

Es war nur eine vergleichsweise unscheinbare Supportanfrage. Über das Webmail-System könnten gerade keine Mails mehr verschickt werden – es gäbe einen Authentifizierungsfehler. Merkwürdig ist das vor allem aus einem Grund: Für SMTP AUTH werden die gleichen Zugangsdaten verwendet wie für IMAP, und die stimmen – sonst hätte sich der User gar nicht erst am Webmail-System einloggen können. Die Möglichkeit, für SMTP AUTH davon abweichende Daten einzustellen, gibt es überhaupt nicht. Wie kann es also sein, dass Zugangsdaten für IMAP funktionieren, aber für SMTP AUTH nicht mehr?

Praktischerweise hat Roundcube die Option $rcmail_config['smtp_debug'], mit der man zu Debugzwecken die gesamte SMTP-Session loggen kann. Das habe ich gemacht. So sah’s aus:

[09-Aug-2012 12:16:47 +0200]: Recv: 220 helium.uberspace.de ESMTP
[09-Aug-2012 12:16:47 +0200]: Send: EHLO webmail.taurus.uberspace.de
[09-Aug-2012 12:16:47 +0200]: Recv: 250-helium.uberspace.de
[09-Aug-2012 12:16:47 +0200]: Recv: 250-STARTTLS
[09-Aug-2012 12:16:47 +0200]: Recv: 250-PIPELINING
[09-Aug-2012 12:16:47 +0200]: Recv: 250-8BITMIME
[09-Aug-2012 12:16:47 +0200]: Recv: 250-SIZE 0
[09-Aug-2012 12:16:47 +0200]: Recv: 250 AUTH LOGIN PLAIN
[09-Aug-2012 12:16:47 +0200]: Send: AUTH LOGIN
[09-Aug-2012 12:16:47 +0200]: Recv: 334 VXNlcm5hbWU6
[09-Aug-2012 12:16:47 +0200]: Send: [...]
[09-Aug-2012 12:16:47 +0200]: Recv: 334 UGFzc3dvcmQ6
[09-Aug-2012 12:16:47 +0200]: Send: [...]
[09-Aug-2012 12:16:52 +0200]: Recv: 535 authentication failed (#5.7.1)
[09-Aug-2012 12:16:52 +0200]: Send: RSET
[09-Aug-2012 12:16:52 +0200]: Recv: 250 flushed
[09-Aug-2012 12:16:52 +0200]: Send: QUIT
[09-Aug-2012 12:16:52 +0200]: Recv: 221 helium.uberspace.de

Nun, das ist interessant. Am EHLO ist nämlich gut zu erkennen, dass wir uns hier auf taurus befinden. Der SMTP-Server meldet sich allerdings mit dem Namen helium, was zwar auch einer unserer Server ist (nämlich der, auf dem Uberspace.de selbst läuft), aber eben ein anderer. Wie zur..? Schnell die config/main.inc.php kontrolliert, aber dort steht ganz klar:

$rcmail_config['smtp_server'] = 'localhost';

Wie kann also der Host taurus, wenn er localhost kontaktiert, auf einem ganz anderen Host rauskommen?! Selbstverständlich ist in der /etc/hosts durchaus fest 127.0.0.1 localhost eingetragen; es kann also eigentlich nicht mal ein DNS-Problem sein.

Ich habe mich dann mit strace an den PHP-Prozess geklemmt, erneut versucht eine Mail zu versenden und zu beobachten, was passiert. Was klar erkennbar war: Es wurde eine DNS-Auflösung von localhost.uberspace.de gemacht, die dann 82.98.87.96 zurücklieferte – die IP von helium.

Nachdem ich ein bisschen Debugging in die PHP-Klassen Net_SMTP und Net_Socket reingehackt hatte (die Roundcube für den Mailversand verwendet), war klar, dass hier nicht plötzlich etwas anderes passiert als sonst. Das wäre auch unlogisch gewesen, aber seien wir ehrlich, das ganze Problem ist schließlich unlogisch. Also habe ich erstmal versucht, das Problem zu isolieren, mit folgendem Testscript:

[webmail@taurus ~]$ cat test.php
<?php echo gethostbyname("localhost"); ?>

Ist klar, oder? Gut, dann führen wir das mal aus:

[webmail@taurus ~]$ php test.php 
82.98.87.96

What! The! Fuck! Wie kann denn bitte das sein? Es geht doch nicht von heute auf morgen in einem unangetasteten PHP die gethostbyname-Funktion plötzlich kaputtgehen …

Eine überraschend kurze Recherche nach „php gethostbyname localhost“ brachte mich auf einen Forenbeitrag mit dem vielsagenden Titel kernel upgraded, now php_network_getaddresses can’t resolve localhost, der auch schnell deutlich machte, dass es weniger der Kernel war, sondern:

I had a similar problem yesterday on Centos 5. It turns out a sudo updated corrupted the selinux context of /etc/nsswitch.conf which made all localhost lookups fail. See if it works with selinux set to permissive. If so, try restorecon /etc/nsswitch.conf and reset selinux to enforcing. Good luck.

Nun setzen wir allerdings SELinux bei uns gar nicht ein, insofern konnte es kein Problem des security contexts sein – auch wenn das durchaus bei anderen auch ein Problem war, zu dem Red Hat kurz darauf einen Patch herausgegeben hat. Und dieses bereits gepatchte sudo-RPM ist auch das, was wir hier einsetzen – also das, in dem jener Bug eigentlich schon gefixt war. Aber ein sudo-Update haben wir durchaus auch gemacht. Gerade eben nämlich. Und jetzt geht localhost nicht mehr.

Der Bugreport wies aber indirekt auf ein anderes Problem hin: Das SELinux-Problem bestand nämlich darin, dass die /etc/nsswitch.conf plötzlich auf rpm_script_tmp_t gelabelt war. Das macht man ja nun nicht einfach so zum Spaß. Vielmehr roch es danach, dass das sudo-Update irgendwie an der Datei rumfummelt, und zwar, in dem es eine temporäre Datei erstellt (die dann korrekt das Label rpm_script_tmp_t erhält) und jene dann mittels mv auf die eigentliche Datei drüberbewegt wird. Und da die /etc/nsswitch.conf kein Bestandteil des sudo-RPMs ist, kann es eigentlich nur eine Stelle geben, mit der sudo an der Datei rummachen kann:

[root@taurus ~]# rpm -q --scripts sudo
postinstall scriptlet (using /bin/sh):
/bin/chmod 0440 /etc/sudoers || :

#
# rhbz#841070: remove the old line, the new one won't be matched
#              by the old regexp and won't be deleted on upgrade
#
if grep -q '^sudoers: files ldap$' "/etc/nsswitch.conf"; then
   NSSWITCH_TMPFILE=$(mktemp)
   grep -v '^sudoers: files ldap$' "/etc/nsswitch.conf" > "$NSSWITCH_TMPFILE" && \
   mv -f "$NSSWITCH_TMPFILE" "/etc/nsswitch.conf"
   restorecon "/etc/nsswitch.conf"
fi

if ! grep -q '^[[:space:]]*sudoers:' "/etc/nsswitch.conf"; then
   # No "sudoers:" line in nsswitch.conf, add a default one
   echo "sudoers:  files ldap" >> "/etc/nsswitch.conf"
   restorecon "/etc/nsswitch.conf"
fi
[...]

Wir sehen hier also, dass dann, wenn in der /etc/nsswitch.conf die Zeile sudoers: files ldap steht, jene entfernt wird und im zweiten Codeblock durch sudoers: files ldap ersetzt wird. Ja: Die einzige Änderung beläuft sich darauf, dass hier nun zwei Leerzeichen drinstehen, und die Historie dieser Änderung ist wiederum eine ganz eigene Horrorgeschichte.

Was hier gut erkennbar ist: Es wird eine temporäre Datei mit mktemp angelegt, um den bisherigen Inhalt der /etc/nsswitch.conf abzüglich der zu entfernenden Zeile reinzuschreiben, die neue Zeile zu ergänzen und den Inhalt zurückzuschreiben. mktemp also. In dessen man page klar zu lesen ist:

If mktemp can successfully generate a unique filename, the file (or directory) is created with file permissions such that it is only readable and writable by its owner (unless the -u flag is given) and the filename is printed to standard output.

Und genau so – ohne irgendein chmod – wird die Datei dann anschließend auf die /etc/nsswitch.conf umbenannt: „only readable and writable by its owner“, und der wäre dann in diesem Fall root. Und das bringt dann diesen „lustigen“ Effekt mit sich (hier mal an einem Test-Host verifiziert und nicht mehr auf taurus):

[root@lyra ~]# telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 lyra.uberspace.de ESMTP

vs.

[webmail@lyra ~]$ telnet localhost 25
Trying 82.98.87.96...
Connected to localhost.
Escape character is '^]'.
220 helium.selfhost.de ESMTP

Wenn User nämlich die /etc/nsswitch.conf nicht mehr lesen können, in der die Zeile hosts: files dns steht und gethostbyname dazu anweist, erst in der /etc/hosts zu schauen, bevor es das DNS bemüht, dann spielt schon mal keine Rolle mehr, was in der /etc/hosts so drinsteht. Und wenn der aufzulösende Hostname keinen Punkt beinhaltet, verhält sich gethostbyname wie folgt:

If name doesn’t end in a dot and the environment variable HOSTALIASES is set, the alias file pointed to by HOSTALIASES will first be searched for name (see hostname(7) for the file format). The current domain and its parents are searched unless name ends in a dot.

Das bedeutet: gethostbyname versucht gar nicht erst, localhost via DNS aufzulösen (denn unser DNS-Resolver würde dafür direkt 127.0.0.1 liefern), sondern hängt direkt den eigenen Domainnamen an – in diesem Fall uberspace.de, so dass also faktisch via DNS nach localhost.uberspace.de gesucht wurde, was wegen eines Wildcard-Records im DNS dann auf die IP 82.98.87.96 auflöste.

Beim CentOS-Projekt gibt es einen Bug-Report dazu: Post-install script for sudo sets /etc/nsswitch.conf to mode 600. Er wurde auch upstream gemeldet, also im Red-Hat-Bugtracker, aber das Problem ist immer noch offen: Wer also heute unter CentOS 5 sein sudo updated, kann auch morgen noch localhost nicht mehr oder falsch auflösen. Vor diesem Hintergrund muss man fast froh über das wenig intuitive Verhalten von MySQL sein, das den Hostnamen localhost als implizite Aufforderung ansieht, sich doch bitte über einen Unix-Socket mit dem Server zu verbinden (möchte man eine TCP-Verbindung, muss man explizit 127.0.0.1 angeben), so dass alle Verbindungen zum MySQL-Server auf localhost von diesem Problem unbeeinträchtigt waren.

Hand aufs Herz, Bugs dieses Kalibers hätte ich nicht in einer Distribution erwartet, die ihren Fokus explizit nicht darauf hat, immer die neuesten Versionen von allem einzusetzen, sondern die bestehenden Versionen so sensibel wie möglich durch Backporting von Bugfixes zu pflegen, damit nur ja keine unvorhergesehenen Probleme auftreten – zumal es ja nicht mal ein Bug in sudo war, sondern sudo lediglich der Transporteur für einen Haufen abenteuerliches und unvorsichtiges Shellscripting war. Ich kann nur hoffen, dass das eine ärgerliche Ausnahme bleibt – und vor allem schnell gefixt wird.

Update 16.08.2012: Wunderbar, in aktualisierten sudo-RPMs findet sich nun im %postinstall:

#
# rhbz#846631: fix permissions on nsswitch.conf
#
if [ "$(stat -c '%a' "/etc/nsswitch.conf")" = "600" ]; then
	chmod 0644 "/etc/nsswitch.conf" || :
fi

Es sei denn, es ist die letzte Zeile

09. Juni 2012 von Jonas Pasche

Mit SMTP verhält es sich so: Ein Client sendet ein Kommando; der Server antwortet mit einem dreistelligen Statuscode, gefolgt von einem Leerzeichen und Text. Beispielsweise so:

HELO mainz.jonaspasche.com
250 crux.uberspace.de

Eine Antwort kann aber auch mehrzeilig ausfallen – in diesem Fall wird zwischen Statuscode und Text ein Minus statt eines Leerzeichens angegeben, um zu signalisieren, dass noch eine weitere Zeile kommt, und erst die letzte hat dann ein Leerzeichen. Beispielsweise so:

EHLO mainz.jonaspasche.com
250-crux.uberspace.de
250-PIPELINING
250-8BITMIME
250-AUTH LOGIN PLAIN
250 STARTTLS

Einige Mailserver nutzen dieses Feature, um gleich zur Begrüßung einen kleinen Roman loszuwerden:

220-mtain-me06.r1000.mx.aol.com ESMTP Internet Inbound
220-AOL and its affiliated companies do not
220-authorize the use of its proprietary computers and computer
220-networks to accept, transmit, or distribute unsolicited bulk
220-e-mail sent from the internet.
220-Effective immediately:
220-AOL may no longer accept connections from IP addresses
220 which no do not have reverse-DNS (PTR records) assigned.

So oder so: Nicht übermäßig kompliziert zu verstehen, das mit dem Minus und dem Leerzeichen.

Nun bekamen wir kürzlich eine Supportanfrage eines Ubernauten, der mehrere Uberspaces bei uns hat, dabei mindestens einen auf einem CentOS-5-Host und einen auf einem CentOS-6-Host. Unser netqmail-Setup unterscheidet sich hier ein wenig: Auf den CentOS-5-Hosts setzen wir einen TLS-Patch für netqmail ein; auf den CentOS-6-Hosts hingegen verwenden wir Spamdyke als SMTP-Frontend, das unter anderem TLS für ein in dieser Hinsicht ungepatchtes netqmail bereitstellt.

Der fragliche User berichtete nun, dass er mit seinem Android-Smartphone auf dem CentOS-5-Host problemlos SMTP AUTH mit TLS nutzen könne; auf dem CentOS-6-Host jedoch würde Android antworten, dass TLS erforderlich aber nicht von Server unterstützt sei. Dabei ging es um unseren Host crux, dessen EHLO-Rückgabe oben bereits zitiert wurde und der – ganz entgegen der Fehlermeldung – sehr wohl TLS-Support annonciert. Was zum..?!

Selbst ein Mitschneiden der Verbindung mittels tcpdump brachte nicht viel: Es war der Connect zu sehen, das EHLO, die Antwort des Servers, der klar STARTTLS annoncierte – und dann verabschiedete sich Android und jammerte, TLS würde nicht gehen.

Es brauchte einiges an Recherche, bis ich dann auf diesen fantastischen Bug-Report gestoßen bin: Email app doesn’t recognize STARTTLS as last line of EHLO response. Und Tatsache: Der entsprechende Code prüft auf TLS-Support, in dem er schaut, ob in der EHLO-Antwort die Zeichenkette „-STARTTLS“ enthalten ist. Ja, genau: Mit Minus. Im Klartext: Es erkennt den TLS-Support nur dann, wenn jener nicht in der letzten Zeile annonciert wird. Mir erschien das eigentlich schon fast zu blöde, um ernsthaft wahr zu sein, aber ich habe dann testweise Spamdyke gepatcht, damit der TLS-Support nun wie folgt annonciert wird:

EHLO mainz.jonaspasche.com
250-crux.uberspace.de
250-PIPELINING
250-8BITMIME
250-AUTH LOGIN PLAIN
250-STARTTLS
250 X-NOTHING

Tja, was soll ich sagen: Der Android-Mailclient funktioniert damit einwandfrei. Ist das zu fassen?

Nun ist der Bug-Report vom März 2009. Drei Tage später schreib jemand mit einer google.com-Adresse: „This issue is assigned to an engineer for further evaluation“ – supi. Dann passierte erstmal wochenlang nichts. Im Juli – immer noch 2009 – wurde ein Patch eingereicht, der im Prinzip die Zeile

if (result.contains("-STARTTLS")) {

durch diese Zeile ersetzt:

if (result.contains("-STARTTLS") || result.contains(" STARTTLS")) {

Es dauerte bis zum April 2010, bis der Patch in den produktiven Code übernommen wurde – was nichts daran ändert, dass sich die Kommentare im Bugreport auch weiterhin wie folgt lesen:

  • „Any resoulution on this issue yet?“ (August 2010)
  • „What should non-expert users stuck on Android 2.1 do to get around this? Any suggestions?“ (März 2011)
  • „I would’ve hoped Google would’ve fixed their email app by now.“ (Juni 2011)
  • „I guess google have fixed it, in a later release, but my phone will never be upgraded to 2.2 by the manufacturer or network.“ (Juni 2011)
  • „I can’t believe this is still not working *!#$!*“ (September 2011)
  • „Still not working in Android 4.0 Emulator.“ (November 2011)

Zwei Dinge stechen hier besonders hervor: Zum einen ein deutlicher Hinweis darauf, was für eine Schande es ist, dass kaum ein Hersteller von Android-Smartphones ernsthaft eine vernünftige Update-Politik betreibt und somit nur ein Bruchteil der Android-User in den Genuss von Bugfixes kommt.

Zum anderen ein Hinweis darauf, dass sich dieser Bug offenbar auch in aktuellen Versionen immer noch zeigt – und das nicht nur direkt beim Google-Projekt, sondern auch beim CyanogenMod-Projekt, das mit TLS does not work if STARTTLS is last line in server response einen inhaltlich identischen Bug-Report aufweist, allerdings aus dem November 2011, wo der Bug doch eigentlich schon längst gefixt hätte sein müssen. Der Bug-Reporter schickt auch gleich den – einzeiligen – Patch mit, der nötig ist, um diesen trivialen Bug zu fixen. Statt eines Dankeschöns wird er angeranzt, man habe Patches über Gerrit (ein von Google entwickeltes Review-System) einzureichen, woraufhin der Bug-Reporter trocken reagiert: „don’t have a development system… so then forget it.“ – vielen Dank auch, super gelaufen.

Wie’s aussieht, wird uns dieses Problem also noch eine Weile erhalten bleiben. Wir werden uns dann mal daran machen, unsere Spamdyke-Installationen zu patchen:

--- spamdyke-4.3.1/spamdyke/spamdyke.c	2012-01-19 16:58:29.000000000 +0100
+++ spamdyke-4.3.1-patched/spamdyke/spamdyke.c	2012-06-03 21:23:50.985562352 +0200
@@ -2436,8 +2436,11 @@
 
                     /* Add a "250 STARTTLS" line. */
                     output_writeln(current_settings, LOG_ACTION_FILTER_FROM, STDOUT_FD, SMTP_EHLO_SUCCESS, STRLEN(SMTP_EHLO_SUCCESS));
-                    output_writeln(current_settings, LOG_ACTION_FILTER_FROM, STDOUT_FD, SMTP_STR_DONE, STRLEN(SMTP_STR_DONE));
+                    output_writeln(current_settings, LOG_ACTION_FILTER_FROM, STDOUT_FD, SMTP_STR_CONTINUATION, STRLEN(SMTP_STR_CONTINUATION));
                     output_writeln(current_settings, LOG_ACTION_FILTER_FROM, STDOUT_FD, SMTP_EHLO_TLS_INSERT, STRLEN(SMTP_EHLO_TLS_INSERT));
+                    output_writeln(current_settings, LOG_ACTION_FILTER_FROM, STDOUT_FD, SMTP_EHLO_SUCCESS, STRLEN(SMTP_EHLO_SUCCESS));
+                    output_writeln(current_settings, LOG_ACTION_FILTER_FROM, STDOUT_FD, SMTP_STR_DONE, STRLEN(SMTP_STR_DONE));
+                    output_writeln(current_settings, LOG_ACTION_FILTER_FROM, STDOUT_FD, SMTP_EHLO_NOTHING_INSERT, STRLEN(SMTP_EHLO_NOTHING_INSERT));
                     }
                   else if ((filter_return & FILTER_MASK_TLS) == FILTER_FLAG_TLS_REMOVE)
                     filter_return ^= FILTER_FLAG_TLS_REMOVE;

Ernsthaft bei Spamdyke für künftige Releases einreichen können wir den Patch wohl kaum – das wäre ja schon irgendwie etwas arg lächerlich, um solche dämlichen Android-Bugs in Fremdsoftware herumworkarounden zu müssen. Natürlich ist das ein Bug, der in Android selbst gefixt werden muss. Aber so ist das eben nun mal, wenn man sich ein Gerät zulegt, dessen Plattform so geschlossen ist, wie es nur irgend geht – wenn ich dann letztlich bei der Update-Politik dann doch wieder von der Güte meines Smartphone-Herstellers völlig abhängig bin, spielt eigentlich keine Rolle mehr, dass da ein Linux drunter läuft; mit freier Software hat das nichts mehr zu tun.

(Auch) darum machen wir Uberspace.de

29. April 2012 von Jonas Pasche

Manchmal sind wir fast etwas überrascht davon, dass offenbar als Besonderheit Nr. 1 von Uberspace.de ausgerechnet das freie Preismodell gesehen wird, das wir eigentlich eher als Nebenschauplatz gesehen hatten – und das vornehmlich aus der Bequemlichkeit heraus entstanden ist, nicht ständig über „warum kostet dieses und jenes so und so viel, und woanders kostet das aber so und so viel“ diskutieren zu müssen. Eigentlich lag unser Hauptanliegen vielmehr darin, mal etwas angstfreier damit umzugehen, Usern eine „echte Shell“ an die Hand zu geben, statt eines Webinterfaces, das einem bei dem, was man so machen will, eher hinderlich im Weg steht – und so auch ein bisschen mehr den „Spaß am Gerät“ zu wecken und zu zeigen, wie vielfältig die Möglichkeiten mit Linux so sind.

Um so schöner, wenn dann in einer Supportmail auch mal so etwas zu lesen ist (vielen Dank an den Autor, dass wir ihn zitieren dürfen):

Jetzt mal im Ernst: Ich erinnere mich noch daran, dass ich dir vor einem Jahr geschrieben habe, dass das Tolle an Uberspace das Erfassen der Zusammenhänge ist. Das macht es auch weiterhin so spannend, euer Kunde zu sein, während man sich irgendwann an Confixx sattgesehen hat. Nun bin ich zwar immer noch kein Profi geworden (das habe ich jetzt zwei-, nein, dreimal eindrucksvoll bewiesen), dafür habt ihr mir die Berührungsängste, die ich gegenüber Linux hegte, genommen, auch im Desktopbereich. Allein dafür bin ich froh, dass es euch gibt. Danke.

Schreibmaschine mit memory_limit

26. April 2012 von Jonas Pasche

Normalerweise sitze ich nicht um 1:20 Uhr noch am Rechner, um PHP-Probleme zu debuggen. Insbesondere nicht, wenn’s keine bezahlte Auftragsarbeit ist, sondern nacktes „Ich! Will! Das! Verstehen!“. Aber genau sowas hatte ich heute. Nachdem ich vor einigen Tagen schon mal ergebnislos an dem folgenden Problem herumgedoktert hatte, habe ich mich heute nochmal darin verbissen.

Ein User betreibt einen Magento-Shop auf unserer Hosting-Plattform Uberspace.de. Dazu gehören auch einige Hintergrundjobs, und zwar unter anderem dieser hier, der ganz offenkundig am memory_limit scheitert:

[demosite@krypton ~]$ php -c ~/cli /home/demosite/html/www.demosite.tld/shell/indexer.php status 
PHP Fatal error:  Allowed memory size of 262144 bytes exhausted (tried to allocate 232 bytes) in /var/www/virtual/demosite/www.demosite.tld/app/code/core/Mage/Index/Model/Mysql4/Process.php on line 110

Moment mal – ein memory_limit von gerade mal 256K? Das kann ja eigentlich nicht sein. In der php.ini steht nämlich 512M, und das wertet PHP an sich auch korrekt aus:

[demosite@krypton ~]$ php -c ~/cli -i | grep memory_limit
memory_limit => 512M => 512M

Nun gibt’s aber ja auch die Möglichkeit, in PHP-Scripts zur Laufzeit mit ini_set() einzelne Einstellungen dynamisch zu ändern, also auch das memory_limit. Schauen wir also mal, wo das möglicherweise noch so passiert:

[demosite@krypton ~]$ grep -r memory_limit /var/www/virtual/demosite/www.demosite.tld | grep -v cache
/var/www/virtual/demosite/www.demosite.tld/lib/Varien/.svn/text-base/Pear.php.svn-base:        @ini_set('memory_limit', '256M');
/var/www/virtual/demosite/www.demosite.tld/lib/Varien/Pear.php:        @ini_set('memory_limit', '256M');
/var/www/virtual/demosite/www.demosite.tld/lib/Zend/Memory/.svn/text-base/Manager.php.svn-base:     * Default value is 2/3 of memory_limit php.ini variable
/var/www/virtual/demosite/www.demosite.tld/lib/Zend/Memory/.svn/text-base/Manager.php.svn-base:        $memoryLimitStr = trim(ini_get('memory_limit'));
/var/www/virtual/demosite/www.demosite.tld/lib/Zend/Memory/Manager.php:     * Default value is 2/3 of memory_limit php.ini variable
/var/www/virtual/demosite/www.demosite.tld/lib/Zend/Memory/Manager.php:        $memoryLimitStr = trim(ini_get('memory_limit'));
/var/www/virtual/demosite/www.demosite.tld/magiczoomplus/module/magictoolbox/core/.svn/text-base/magictoolbox.params.class.php.svn-base:@ini_set('memory_limit', '512M');
/var/www/virtual/demosite/www.demosite.tld/magiczoomplus/module/magictoolbox/core/magictoolbox.params.class.php:@ini_set('memory_limit', '512M');
/var/www/virtual/demosite/www.demosite.tld/.svn/text-base/payment_check.php.svn-base:ini_set('memory_limit', '1024M');
/var/www/virtual/demosite/www.demosite.tld/.svn/text-base/.htaccess.svn-base:  php_value memory_limit 128M
/var/www/virtual/demosite/www.demosite.tld/.htaccess:  php_value memory_limit 128M

Okay, kommt einige Male vor, durchaus auch mit wechselnden Werten, aber nirgendwo etwas mit 256K. Was also ist hier los?

XDebug muss her. Flugs kompiliert und die xdebug.so via zend_extension in die php.ini eingebunden und xdebug.auto_trace=1 gesetzt. Dann nochmal ausgeführt und geschaut, und siehe da – dreimal wird ini_set() aufgerufen:

[demosite@krypton ~]$ grep ini_set traces/trace.3557638816.xt 
    0.1443    8707248         -> ini_set() /var/www/virtual/demosite/www.demosite.tld/shell/abstract.php:117
    0.1443    8707384         -> ini_set() /var/www/virtual/demosite/www.demosite.tld/shell/abstract.php:117
    0.1444    8706736         -> ini_set() /var/www/virtual/demosite/www.demosite.tld/shell/abstract.php:123

Also mal geschaut, was an dieser Stelle so für Code steht – und das hat mich wirklich überrascht:

    /**
     * Parse .htaccess file and apply php settings to shell script
     *
     */
    protected function _applyPhpVariables()
    {
        $htaccess = $this->_getRootPath() . '.htaccess';
        if (file_exists($htaccess)) {
            // parse htaccess file
            $data = file_get_contents($htaccess);
            $matches = array();
            preg_match_all('#^\s+?php_value\s+([a-z_]+)\s+(.+)$#siUm', $data, $matches, PREG_SET_ORDER);
            if ($matches) {
                foreach ($matches as $match) {
                    @ini_set($match[1], $match[2]);
               	}
            }
            preg_match_all('#^\s+?php_flag\s+([a-z_]+)\s+(.+)$#siUm', $data, $matches, PREG_SET_ORDER);
            if ($matches) {
                foreach ($matches as $match) {
                    @ini_set($match[1], $match[2]);
                }
            }
        }
    }

Es kommt hier also Code zum Tragen, der bei der Ausführung eines PHP-Scripts auf der Kommandozeile eine .htaccess-Datei auswertet, die in diesem Kontext nun eigentlich überhaupt keine Rolle spielt – handelt es sich dabei doch um eine webserver-spezifische Konfigurationsdatei. Also flugs dort nochmal reingeschaut:

[demosite@krypton ~]$ grep memory_limit /var/www/virtual/demosite/www.demosite.tld/.htaccess
  php_value memory_limit 128M

Hm. Da steht aber auch nichts von 256K. Wo zum Kuckuck kommt das her? Also auf die Schnelle – es ist spät – schnell ein wenig Debugging-Code an die entsprechende Stelle gesetzt, um einfach mal auszugeben, was genau dort geschrieben wird (jaja, das ist nicht elegant, aber man muss auch mal pragmatisch sein dürfen):

               	foreach ($matches as $match) {
                    echo "setting '" . $match[1] . "' to '" . $match[2] . "'\n";
                    @ini_set($match[1], $match[2]);
                }

Die Ausgabe hat dann doch reichlich überrascht:

[demosite@krypton ~]$ php -c ~/cli /home/demosite/html/www.demosite.tld/shell/indexer.php status 
'etting 'memory_limit' to '128M
'etting 'max_execution_time' to '18000
PHP Fatal error:  Allowed memory size of 262144 bytes exhausted (tried to allocate 92 bytes) in /var/www/virtual/demosite/www.demosite.tld/app/code/core/Mage/Index/Model/Mysql4/Process.php on line 110

Auch dem ungeübten Betrachter wird auffallen, dass die Ausgabe nicht mit setting, sondern mit 'etting beginnt, und dafür nicht mit '128M' endet, sondern mit '128M. Was zum..? Es wird doch nicht..?

Ein kleiner Exkurs: Unix-Systeme benutzen andere Zeilenumbrüche als Windows-Systeme. Während unter Unix Zeilenumbrüche mit einem einzelnen line feed ("\n") separiert werden, werden unter Windows Zeilenumbrüche mit einem carriage return, line feed ("\r\n") separiert: Carriage return, der gute alte Wagenrücklauf wie bei der Schreibmaschine – am Hebel ziehen, um den Druckkopf wieder am Zeilenanfang zu positionieren; Druckwalze eine Zeile vorrücken lassen.

Und genau so sah das hier aus: Das zweite ' von '128M' ist nach vorne gerutscht; ein carriage return hat stattgefunden. Und das wiederum heißt, dass … Moment, kurz nachgucken …

[demosite@krypton ~]$ file /var/www/virtual/demosite/www.demosite.tld/.htaccess
/var/www/virtual/demosite/www.demosite.tld/.htaccess: ASCII English text, with CRLF line terminators

… richtig: Die Inhalte der .htaccess-Datei sind mit Windows-Zeilenumbrüchen gespeichert. Werden jene nun auf einem Unix-System eingelesen, was die Zeilen am „\n“ auftrennt, dann hat memory_limit hier faktisch nicht den Wert „128M„, sondern den Wert „128M\r“ – Wagenrücklauf inklusive. Und genau das ist es, was PHP nun so gar nicht schmeckt. Verdammte Axt!

Also los:

[demosite@krypton ~]$ dos2unix /var/www/virtual/demosite/www.demosite.tld/.htaccess
dos2unix: converting file /var/www/virtual/demosite/www.demosite.tld/.htaccess to UNIX format ...

Getestet:

[demosite@krypton ~]$ php -c ~/cli /home/demosite/html/www.demosite.tld/shell/indexer.php status 
setting 'memory_limit' to '128M'
setting 'max_execution_time' to '18000'
Product Attributes:            Pending
Product Prices:                Pending
Catalog Url Rewrites:          Pending
Product Flat Data:             Pending
Category Flat Data:            Pending
Category Products:             Pending
Catalog Search Index:          Running
Stock status:                  Pending

Problem gelöst. Script läuft. Nur noch die Debug-Zeile wieder rausnehmen. So, erledigt.

Und jetzt ist wirklich Zeit für’s Bett.


Impressum