Update auf PHP 7.1.2 bereitet Probleme mit älteren Kerneln

Posted on Sunday, 19 February 2017 in Server

Nach dem Update auf PHP 7.1.2 kann es bei älteren Kernel-Versionen zu Problemen kommen, wenn diese versuchen, über die PHP-eigenen Funktionen Zufallswerte von /dev/urandom zu lesen. Das Problem liegt im Systemaufruf getrandom(), der mit dem Linux-Kernel in Version 3.17 eingeführt wurde, statt direkt aus der Datei /dev/urandom lesen zu müssen. In PHP selbst wird während des Kompilierens festgelegt, ob der Syscall zur Verfügung steht und entsprechend verwendet wird oder direkt von /dev/urandom gelesen wird:

#elif defined(__linux__) && defined(SYS_getrandom)

Dieser Bug wurde in PHP 7.1.2 eingeführt. Wird nun das so kompilierte Paket auf einem System installiert, dessen Kernel älter ist und den Syscall nicht kennt, wird beim Lesen von Zufallswerten ein Fehler geworfen, der nicht behandelt wird. PHP gibt in diesem Fall "Could not gather sufficient random data" aus. Aufgefallen ist es mir vor ein paar Tagen, nachdem ich nicht mehr auf mein Dokuwiki und meine Nextcloud zugreifen konnte und lediglich ein Internal Server Error 500 angezeigt wurde.

Fehlersuche

Das eigentliche Problem habe ich bereits vorweg genommen. Um es allerdings auszumachen, hatte ich zuerst bei Dokuwiki und danach bei Nextcloud versucht anhand der Log-Einträge herauszufinden, wo das Problem auftritt. Und habe direkt überprüft, ob es Probleme beim Lesen von /dev/urandom gibt oder keine ausreichende Entropie vorhanden ist. Anfängliche einfache Tests, indem ich php mit der Option -r aufgerufen habe, um PHP-Code ohne umständliche Tags auszuführen, konnten vorerst nicht helfen.

php -r 'is_readable("/dev/urandom");'

Beim Lesen gibt es schon mal keine Probleme.

cat /proc/sys/kernel/random/entropy_avail

Entropie scheint auch ausreichend vorhanden zu sein. Um sicher zu gehen, dass eine weitere Fehlersuche nicht nutzlos ist und das Problem ganz woanders liegt.

Nach einigen Versuchen und dem Durchsuchen des PHP-Codes bin ich letztlich bei random_compat gelandet, einem PHP-Paket, das von vielen anderen Projekten wie auch ownCloud, Nextcloud und Dokuwiki verwendet wird, und unter anderem die Funktionen random_bytes() und random_int() zur Verfügung stellt, die mit PHP 7.0 eingeführt wurden. Dabei habe ich mir die Funktion random_bytes() (Dokumentation) genauer angesehen, welche auch von random_int() aufgerufen wird und den vermeintlichen Fehler zu produzieren scheint, wenn die Anzahl der geforderten Bytes nicht mit der von RandomCompat_strlen() bestimmten Länge übereinstimmt. Also habe ich mir die Funktionen dort genauer angeschaut, die auf das mcrypt PHP-Modul zurückgreifen (Code). Erst einmal prüfen, ob ich zufällige Werte erhalte:

php -r 'echo @mcrypt_create_iv(20, MCRYPT_DEV_URANDOM);'

Scheint zu funktionieren, ich erhalte seltsame Symbole. Und was ist mit der Länge?

php -r 'echo strlen(@mcrypt_create_iv(30, MCRYPT_DEV_URANDOM));'

Rückgabewert ist 30, alles in Ordnung. Dennoch erzeugt der Aufruf von

php -r 'echo random_int(0,10);'

reproduzierbar den Fehler. Somit scheint das Problem im mcrypt-Modul zu liegen und damit in PHP selbst. Die gelesene Anzahl an Zufallswerten stimmt nicht mit der geforderten Größe überein.

Aus Zeitgründen konnte ich mich dem erst gestern wieder genauer widmen. Dabei hat eine Suche wesentlich mehr Treffer geliefert, unter anderem bin ich im Github Repository von PHP auf einen mittlerweile geschlossenen Pull Request gestoßen, der genau dieses Problem thematisiert und den eingangs erwähnten Bug behebt.

Fix

Mittlerweile ist das Problem behoben, indem der Fehler "ENOSYS", wenn der Syscall nicht zur Verfügung steht, behandelt wird und als Fallback direkt von /dev/urandom gelesen wird. Im nächsten Release von PHP, welches vermutlich die Versionsnummer 7.1.3 tragen wird, sollte das Problem somit nicht mehr auftreten.

Downgrade

Bis dahin kann man sich behelfen, indem ein Downgrade der betroffenen Pakete durchgeführt wird. Idealerweise hat man den Cache von pacman noch nicht geleert, sodass die vorherige PHP-Version noch als Paket vorhanden ist. Ansonsten kann man sie über das Archiv von Arch Linux besorgen. Eine Auflistung, welche PHP-Pakete noch im Cache liegen, erhält man mit

ls /var/cache/pacman/pkg/php*

In meinem Fall liegen noch einige Versionen dort herum, inklusive der vorherigen Version 7.1.1, welche ich so direkt installieren kann:

sudo pacman -U /var/cache/pacman/pkg/php-7.1.1-1-armv7h.pkg.tar.xz
sudo pacman -U /var/cache/pacman/pkg/php-fpm-7.1.1-1-armv7h.pkg.tar.xz

Den geänderten Dienste noch neu laden und den PHP-Server neu starten

sudo systemctl daemon-reload
sudo systemctl restart php-fpm

und die zuvor auftretenden Server-Fehler 500 sollten verschwinden. Jetzt sollte am besten bei Upgrades PHP ausgeschlossen werden (mit pacman -Syu --ignore php --ignore php-fpm), bis die Version 7.1.3 zur Verfügung steht.

Schlechte Idee

Wie an den Paketen oben zu erkennen ist, lasse ich den Server auf einem kleinen ARM-Rechner laufen. In meinem Fall ist es ein Odroid U3. Das Problem dabei ist, dass der offiziell dafür verfügbare Kernel recht alt ist (linux-odroid-u2-3.8.13.30-5) --- zu alt, um den getrandom() Syscall zu kennen. Die vermeintlich gute Idee, die ich spontan hatte, war, einfach den ARMv7 multi-platform Kernel linux-armv7-4.9.10-1 zu installieren. Schlechte Idee, nach dem Neustart war mein Server nicht mehr erreichbar. Doch dazu später mehr...