PHP 8.5.5 veröffentlicht: Alle Bugs und was sie für deinen Code bedeuten
Sie befinden sich: Home > Webmaster News
Am 9. April 2026 ist PHP 8.5.5 erschienen. Auf den ersten Blick wirkt das Release wie eine routinemäßige Bugfix-Iteration ohne nennenswerte Überraschungen. Wer sich aber die Zeit nimmt, den Changelog sorgfältig durchzuarbeiten, stößt auf Korrekturen, die im Produktionsbetrieb erheblich sein können: vier Fixes unter Opcache, davon drei direkt im JIT-Compiler, darunter ein Fehler, der schlicht falsche Rechenergebnisse produzierte, gleich drei Use-After-Free-Bugs in JIT, Phar und DOM, sowie Stabilitätsprobleme in SPL, PCRE und OpenSSL.
PHP 8.5.5 ist ein reines Bugfix-Release ohne neue Sprachfeatures und ohne explizit ausgewiesene CVEs. Use-After-Free-Bugs und undefiniertes Verhalten können jedoch potenziell sicherheitsrelevant sein, auch wenn kein konkreter Exploit nachgewiesen ist. Wer PHP 8.5.x produktiv betreibt, sollte zügig aktualisieren.

In diesem Artikel nehme ich jeden einzelnen Punkt des offiziellen Changelogs auseinander. Ich erkläre, was genau der Fehler war, unter welchen Bedingungen er auftrat und was er in der Praxis bedeutet.
Warum PHP 8.5.5 kein gewöhnliches Bugfix-Release ist
Viele PHP-Releases enthalten vor allem kleinere Korrekturen, die den Alltag eines typischen Webentwicklers kaum berühren. PHP 8.5.5 ist da anders gelagert. Gleich mehrere Fixes betreffen Bereiche, die viele Produktivsysteme direkt berühren können, auch wenn nicht jeder Bug jede Anwendung trifft.
Der schwerwiegendste Fehler ist GH-20838: Der JIT-Compiler produzierte bei bestimmten arithmetischen Mustern schlicht falsche Ergebnisse. Das betrifft konkret den JIT-Modus opcache.jit=1255 bei spezifischen Additions- und Konversionskombinationen, nicht pauschal jede JIT-Konfiguration. Dazu kommen drei Use-After-Free-Bugs, die Abstürze oder im schlechtesten Fall Speicherkorruption verursachen können. Die PCRE-Reentrancy-Fixes sind wichtig für alle Anwendungen, die PCRE aus Erweiterungen heraus aufrufen. Und die OpenSSL-Korrektur für EC-Schlüssel ist relevant für jeden, der mit Elliptic-Curve-Kryptografie arbeitet und OpenSSL 3.6 einsetzt.
Kurz gesagt: Es gibt mehr als genug Gründe, dieses Update zügig einzuspielen. Alle Änderungen auf einen Blick gibt es im offiziellen PHP-Changelog auf php.net.
Core: Trait-Properties und Lazy Proxies
Zwei Core-Fixes betreffen Bereiche, die mit PHP 8.4 an Bedeutung gewonnen haben: einerseits die interne Verwaltung von Trait-Properties in Verbindung mit Reflection, andererseits das Lazy-Objects-Feature, das Ghost-Objekte und Virtual Proxies einführte.
GH-20672: Falsch dimensionierte property_info bei überschatteten Trait-Properties
Der erste Core-Fix betrifft eine interne Datenstruktur namens property_info, die Reflection-Informationen zu Klasseneigenschaften hält. Wenn eine Klasse einen Trait einbindet und die gleiche Property lokal neu deklariert, also das Shadowing nutzt, stimmte die Größe dieser internen Struktur nicht mehr. Die Anzahl der tatsächlichen Property-Metadaten und die berechnete Strukturgröße liefen auseinander.
Für normalen PHP-Code, der keine Reflection nutzt, ist dieser Fehler unsichtbar. Relevant wird er bei Frameworks, die intensiv über Reflection auf Property-Informationen zugreifen. Doctrine, Symfony PropertyInfo und ähnliche Tools können in solchen Edge-Cases fehlerhafte Typinformationen erhalten oder in undefiniertes Verhalten geraten. Mit PHP 8.4 wurden Property-Hooks und asymmetrische Sichtbarkeit eingeführt, was die Anzahl der möglichen Property-Metadaten erhöht hat und diesen Fehler wahrscheinlicher machte.
<?php
/*
GH-20672: "locally shadowed trait properties"
Der Begriff "locally shadowed" bezeichnet intern den Vorgang, bei dem
die Engine Trait-Properties in die property_info-Tabelle der nutzenden
Klasse aufnimmt. Dabei wurde die Strukturgroesse falsch berechnet.
Das PHP-Manual erlaubt eine Trait-Property nur dann in der Klasse neu
zu deklarieren, wenn Typ, Sichtbarkeit, Readonly-Status und Initialwert
identisch sind. Da die Regel den Spielraum stark einschränkt, zeigt
dieses Beispiel den einfachsten betroffenen Fall: die Klasse nutzt die
Trait-Property direkt, ohne sie neu zu deklarieren.
Für Reflection-Frameworks, die Property-Informationen aus
Trait-basierten Klassen auslesen, konnte die falsche Dimensionierung zu
inkonsistenten Typinformationen oder internen Engine-Fehlern führen.
*/
trait HasName {
public string $name = '';
}
class Entity {
use HasName;
}
$rp = new ReflectionProperty(Entity::class, 'name');
/*
In 8.5.4 konnte die interne property_info-Struktur bei der
Engine-seitigen Verarbeitung von Trait-Properties inkonsistent sein.
In 8.5.5 ist die Berechnung korrekt.
*/
echo $rp->getType(); /* string */
Kein Anpassungsbedarf im Anwendungscode. Die Änderung erhöht ausschließlich die Robustheit reflection-basierter Werkzeuge.
GH-20875, GH-20873, GH-20854: IN_GET-Guard für Lazy Proxies
PHP 8.4 führte Lazy Objects ein, also Ghost-Objekte und Virtual Proxies, die über ReflectionClass::newLazyGhost() und ReflectionClass::newLazyProxy() erzeugt werden. Diese Objekte initialisieren sich erst beim ersten Property-Zugriff. Genau in dieser Initialisierungslogik steckten drei verwandte Bugs.
Der sogenannte IN_GET-Guard ist eine interne Schutzmarke, die verhindert, dass __get() und die Lazy-Initialisierung sich gegenseitig rekursiv aufrufen. Bestimmte interne Codepfade, die bei Lazy Proxies durchlaufen wurden, setzten diesen Guard nicht. Das führte im Debug-Build zu harten Assertion-Fehlern. Im Release-Build war undefiniertes Verhalten möglich.
Konkret: GH-20875 betraf einen Assertion-Fehler in _get_zval_ptr_tmp, GH-20873 einen in _zendi_try_convert_scalar_to_number. GH-20854 beschreibt einen dritten verwandten Pfad.
<?php
class A {
public int $p = 1;
}
$rc = new ReflectionClass(A::class);
$obj = $rc->newLazyProxy(fn() => new A());
$rc->initializeLazyObject($obj);
/*
In 8.5.4 konnte dieser Zugriff im Debug-Build
eine Assertion-Verletzung ausloesen.
In 8.5.5 wird der IN_GET-Guard korrekt propagiert.
*/
var_dump($obj->p);
In 8.5.5 setzt get_property_ptr_ptr den Guard konsequent durch alle relevanten Lazy-Proxy-Pfade. Wer Lazy Objects nutzt, profitiert von deutlich mehr Stabilität.
Bz2: Trunkierung der Ausgabegröße
In der Bz2-Erweiterung wurde die Gesamtgröße der Ausgabe beim Dekomprimieren intern mit einem zu kleinen Integer-Typ verfolgt. Bei sehr großen Datenmengen von mehr als 2 Gigabyte lief dieser Typ über, die berechnete Gesamtgröße erschien kleiner als sie war.
Die Folge: Funktionen wie bzdecompress() oder stream-basierte Bz2-Filter konnten den Vorgang mit einer Fehlermeldung abbrechen, obwohl die Quelldaten vollständig korrekt waren. Ein falscher Alarm.
In 8.5.5 kommt ein ausreichend breiter Typ zum Einsatz, der die tatsächliche Datenmenge korrekt repräsentieren kann. Wer extrem große, Bz2-komprimierte Dateien mit PHP verarbeitet, erhält jetzt zuverlässige Ergebnisse. Für normale Anwendungsfälle mit Dateien weit unterhalb von 2 GB war dieser Fehler nie relevant.
DOM: HTMLDocument und xml:*-Attribute
Ein Fix in der neuen DOM-API betrifft die korrekte Behandlung von XML-Namespaces beim HTML-Parsing.
GH-21486: DomHTMLDocument beschädigt xml:space und xml:lang
Die neue DOM-API, die in PHP 8.4 eingeführt wurde, brachte unter anderem DomHTMLDocument mit einem überarbeiteten HTML-Parser. Dieser Parser hatte einen Normverstoß: Die spezialisierten XML-Attribute xml:space und xml:lang wurden nicht korrekt behandelt. Nach dem Parsen fehlte entweder der Namespace https://www.w3.org/XML/1998/namespace oder der Präfix xml: wurde abgetrennt.
Wer XHTML-Dokumente mit diesen Attributen über DomHTMLDocument einliest und anschließend weiterverarbeitet oder serialisiert, bekam nach dem Roundtrip kaputte Attribute zurück. Das ist besonders problematisch für Internationalisierungspipelines, die auf xml:lang angewiesen sind, sowie für Formatierungsprozesse, die xml:space="preserve" auswerten.
<?php
$html = '<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml" xml:lang="de" xml:space="preserve">
<body><p>Hallo</p></body>
</html>';
$doc = DomHTMLDocument::createFromString($html);
$root = $doc->documentElement;
/*
In 8.5.4 konnten xml:lang und xml:space nach dem Parsen
ohne Praefix oder mit falschem Namespace enden.
In 8.5.5 bleiben Namespace und Praefix korrekt erhalten.
*/
echo $root->getAttribute('xml:lang'); /* gibt "de" zurueck */
Kein Handlungsbedarf im Anwendungscode, solange du auf 8.5.5 aktualisierst.
FFI: Ressourcenleck in FFI::cdef()
FFI::cdef() lädt C-Header und verbindet sie mit einer nativen Bibliothek. Wenn dabei die Symbol-Auflösung scheiterte, also ein in der Header-Deklaration genanntes Symbol in der Bibliothek nicht gefunden werden konnte, warf PHP zwar eine Exception, gab aber intern allozierte Ressourcen nicht vollständig frei. Laut offiziellem Changelog betrifft der Fix explizit diesen "symbol resolution failure"-Pfad.
In lang laufenden Prozessen, die FFI-Bindings dynamisch aufbauen und dabei ab und zu Fehler abfangen, führte das zu schleichenden Ressourcenlecks.
<?php
for ($i = 0; $i < 1000; $i++) {
try {
/* Symbol nicht_vorhanden() existiert nicht in der Bibliothek */
$ffi = FFI::cdef(
"int nicht_vorhanden(int);",
"/usr/lib/libbeispiel.so"
);
} catch (FFIException $e) {
/*
In 8.5.4: Symbol-Resolution-Fehler leckte intern Ressourcen.
In 8.5.5: Der Fehlerpfad raeumt vor der Exception korrekt auf.
*/
}
}
Die Korrektur ist ausschließlich im Fehlerpfad aktiv. Reguläre, erfolgreiche cdef()-Aufrufe waren nie betroffen.
GD: phpinfo() zeigt libJPEG 10.0 korrekt an
Ein rein diagnostischer Fix in der GD-Erweiterung, der die Versionsanzeige in phpinfo() korrigiert.
GH-21431: Fehlende Erkennung von libJPEG 10.0
phpinfo() gibt im GD-Abschnitt die Version der verlinkten libJPEG aus. Mit libJPEG 10.0 änderte sich das Versionsschema, und PHP erkannte diese Version nicht mehr korrekt. Das Feld blieb entweder leer oder zeigte einen falschen Wert.
Der Fix aktualisiert die Versionserkennung, damit phpinfo() nun "libJPEG 10.0" korrekt ausgibt. Die eigentliche JPEG-Funktionalität, also das Laden und Speichern von JPEG-Bildern, war nicht betroffen. Es handelt sich um eine rein diagnostische Korrektur, die bei der Fehlerbehebung und der Überprüfung von Serverumgebungen hilft.
Opcache und JIT: Vier Fixes, die zählen
Die Opcache-Fixes in 8.5.5 verdienen besondere Aufmerksamkeit. Vier Bugs wurden behoben: drei davon stecken direkt im JIT-Compiler, einer betrifft das Preloading und den Datei-Cache und ist kein JIT-Bug. Zusammen sind es ein Rechenfehler, eine Endlosschleife, ein Konsistenzproblem beim Preloading und ein Use-After-Free im JIT. Wer PHP mit aktiviertem JIT betreibt oder Preloading nutzt, sollte allein wegen dieser Fixes aktualisieren.
GH-21052: Preload-Konstanten werden fälschlich in Datei-gecachte Skripte propagiert
Opcache-Preloading lädt beim Serverstart definierte Klassen, Funktionen und Konstanten vorab in den gemeinsamen Speicher. In einer bestimmten Kombination wurden Konstanten, die ausschließlich in Preload-Skripten definiert waren, fälschlich in disk-gecachte Skripte übernommen. Aus Sicht des Datei-Caches wirkten diese Konstanten schon als definiert, obwohl das jeweilige Skript ohne Preloading laufen können sollte.
Das konnte zu seltsamen Effekten führen, wenn dasselbe Skript einmal in einer Umgebung mit Preloading und einmal ohne ausgeführt wurde, etwa beim Wechsel zwischen verschiedenen SAPI-Kontexten oder bei CLI-Aufrufen neben FPM. In 8.5.5 wurde die Propagation von Konstanteninformationen für datei-gecachte Skripte korrigiert.
GH-20838: Der JIT-Compiler produziert falsche Rechenergebnisse
Dieser Bug ist der kritischste in diesem Release. Im JIT-Modus enthielt eine Optimierung für arithmetische Ausdrücke einen Fehler. Unter bestimmten Mustern bei Additions- und Subtraktionsketten oder bei Typkonversionen produzierte der JIT-kompilierte Code andere Zahlenwerte als der Interpreter.
Das Skript sah korrekt aus. Der Interpreter lieferte das richtige Ergebnis. Aber mit aktiviertem JIT waren die Ergebnisse falsch, ohne dass eine Fehlermeldung ausgegeben wurde.
<?php
/*
Dieses Beispiel zeigt das Prinzip des Problems.
In einem realen Szenario konnten bestimmte arithmetische
Ausdruecke mit JIT falsche Werte zurueckgeben.
*/
function berechne(int $a, int $b, int $c): int {
/* Bestimmte Kombinationen aus Addition, Subtraktion
und Typkonversion konnten im JIT falsche Werte liefern */
return ($a + $b) - $c;
}
/*
Mit opcache.jit=disable: korrektes Ergebnis
Mit opcache.jit=1255 (Tracing): moeglicherweise falsches Ergebnis in 8.5.4
Mit 8.5.5: beide Pfade liefern identische Ergebnisse
*/
echo berechne(100, 200, 150);
Besonders riskant ist dieser Fehler für Finanzberechnungen, statistische Auswertungen oder kryptografische Operationen. Wer JIT aktiviert hat und noch nicht auf 8.5.5 aktualisiert ist, sollte in Betracht ziehen, opcache.jit=0 temporär zu setzen, bis das Update eingespielt ist.
Kritikalität: Hoch. Falsche Rechenergebnisse ohne Fehlermeldung sind schwer zu debuggen und können zu Datenfehlern in der Produktion führen.
GH-21267: Endlosschleife im Tracing-JIT bei polymorphem Kontext
Im Tracing-JIT, also mit opcache.jit=tracing, konnte eine sehr spezifische Kombination von Bedingungen zu 100 Prozent CPU-Auslastung führen:
- Eine Property ohne festen Typ mit Default-Wert
- Eine
__get()-Magic-Methode in der Klasse
- Mehrere Subklassen, also ein polymorpher Kontext
- Instanzen, bei denen diese Property explizit per
unset() entfernt wurde
Wenn in einer häufig durchlaufenen Schleife auf diese Property zugegriffen wurde und der JIT einen Trace für den Opcode FETCH_OBJ_R aufbaute, lief dieser Trace bei IS_UNDEF-Properties in eine Endlosschleife. Statt auf den Interpreter zurückzufallen und dort __get() aufzurufen, blieb der JIT-Trace hängen.
<?php
class Base {
public $incrementing = true;
public function __get(string $name): mixed {
return null;
}
public function getIncrementing(): mixed {
return $this->incrementing;
}
}
class ChildA extends Base {}
class ChildB extends Base {}
class ChildC extends Base {
public $incrementing = false;
}
$a = new ChildA();
$b = new ChildB();
/* Explizites unset() erstellt die IS_UNDEF-Situation */
unset($a->incrementing);
$objects = [$a, $b, new ChildC()];
/*
In 8.5.4 mit opcache.jit=tracing: Diese Schleife konnte
in eine Endlosschleife mit 100 % CPU laufen.
In 8.5.5 bailt der Trace korrekt und faellt auf den Interpreter zurueck.
*/
foreach ($objects as $obj) {
echo $obj->getIncrementing() . "\n";
}
In 8.5.5 erkennt der JIT-Compiler die IS_UNDEF-Situation korrekt und führt in diesen Fällen einen sauberen Bail-out durch.
GH-21395: Use-After-Free im JIT
In bestimmten Fehler- oder Invalidierungspfaden des JIT-Compilers wurden Trace-Strukturen oder zugehörige Metadaten freigegeben, während noch Verweise auf sie existierten. Ein anschließender Zugriff auf diese bereits freigegebenen Strukturen ist ein klassischer Use-After-Free-Bug.
Die Auswirkungen reichen von einem unmittelbaren Absturz bis zu stiller Speicherkorruption oder ausnutzbaren Schwachstellen. In 8.5.5 wurde die Lebensdauerverwaltung der JIT-internen Datenstrukturen überarbeitet. Für normale Anwendungsentwickler ist das in erster Linie ein Stabilitäts- und Sicherheitsgewinn.
OpenSSL: EC-Schlüssel und BIO_printf-Fehlerbehandlung
Zwei Korrekturen betreffen die OpenSSL-Erweiterung. Der erste Fix adressiert ein Kompatibilitätsproblem mit OpenSSL 3.6 beim Erzeugen von EC-Schlüsseln, der zweite verbessert die Fehlerweiterleitung bei internen BIO-Operationen.
GH-21083: openssl_pkey_new() scheitert bei EC-Schlüsseln mit OpenSSL 3.6
openssl_pkey_new() erzeugt neue kryptografische Schlüssel. Bei der Verwendung von Elliptic-Curve-Schlüsseln ist die Schlüssellänge durch die gewählte Kurve vollständig vorgegeben. Der Parameter private_key_bits ist für EC-Schlüssel semantisch irrelevant, da die Kurve selbst die Bitlänge definiert.
Mit OpenSSL 3.6 begann PHP jedoch, diesen Parameter dennoch zu validieren. War er nicht gesetzt oder trug einen unerwarteten Wert, schlug openssl_pkey_new() mit einem Fehler fehl, selbst wenn die Kurve korrekt angegeben war.
<?php
$config = [
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => 'prime256v1',
/*
Auf Systemen mit OpenSSL 3.6 und PHP 8.5.4 schlug
dieser Aufruf fehl, weil private_key_bits fehlte.
In 8.5.5 wird die Validierung fuer EC-Schluessel uebersprungen.
*/
];
$key = openssl_pkey_new($config);
if ($key === false) {
echo "Fehler: " . openssl_error_string();
} else {
echo "Schluessel erfolgreich erzeugt";
}
In 8.5.5 überspringt PHP die private_key_bits-Validierung für EC-Schlüssel und verhält sich damit konsistent zu anderen OpenSSL-Tools wie openssl ecparam -genkey. Wer ECDSA- oder ECDH-Funktionalität in PHP nutzt und auf ein System mit OpenSSL 3.6 migriert ist, war von diesem Bug betroffen.
Fehlende Fehlerweiterleitung bei BIO_printf()
OpenSSL nutzt intern BIO-Objekte für Ein- und Ausgabe. Mehrere Stellen im PHP-OpenSSL-Code riefen BIO_printf() auf, ohne den Rückgabewert zu prüfen. Bei einem Fehler, den BIO_printf() mit -1 signalisiert, passierte nichts. Der Fehler wurde nicht an den PHP-Fehler- oder Exception-Mechanismus weitergeleitet.
Der offizielle Changelog nennt als Konsequenz, dass Fehler von BIO_printf() nicht an den PHP-Fehler- oder Exception-Mechanismus weitergeleitet wurden. Das bedeutet allgemein: Operationen, die intern auf BIO-Ausgaben angewiesen sind, konnten einen Fehler stillschweigend ignorieren. In 8.5.5 wird der Rückgabewert von BIO_printf() konsequent geprüft. Anwendungscode, der auf Fehlermeldungen aus OpenSSL angewiesen ist, erhält diese jetzt zuverlässiger.
PCNTL: Signalhandler-Installation auf AIX
Unter IBM AIX war der interne Speicher für die Anzahl der verwaltbaren Signale zu knapp dimensioniert. Die globale Variable num_signals, die die Anzahl registrierter Signale verwaltet, konnte Signale jenseits eines bestimmten Bereichs nicht korrekt aufnehmen. Das führte zu Problemen bei der Installation von Signalhandlern über pcntl_signal() auf diesem Betriebssystem.
In 8.5.5 wurde der Speicherbereich dieser Struktur vergrößert. Für alle anderen Plattformen ändert sich nichts. Der Fix betrifft ausschließlich AIX-Installationen.
PCRE: Reentrancy-Probleme in den Kern-Funktionen
Die vier zentralen internen PCRE-Wrapper-Funktionen von PHP waren nicht vollständig reentrant. Das betrifft php_pcre_match_impl, php_pcre_replace_impl, php_pcre_split_impl und php_pcre_grep_impl.
Reentrancy bezeichnet die Eigenschaft einer Funktion, sicher aufgerufen werden zu können, während sie bereits ausgeführt wird. Das ist relevant, wenn Erweiterungen über FFI oder C-Code diese internen PHP-Funktionen aufrufen, während eine andere Regex-Operation noch läuft. In solchen Szenarien konnten interne, nicht abgesicherte Zustandsvariablen inkonsistent werden.
In 8.5.5 wurden alle notwendigen Kontextdaten lokalisiert oder korrekt synchronisiert. Für normalen PHP-Anwendungscode, der ausschließlich die PHP-Regex-Funktionen wie preg_match(), preg_replace() etc. aufruft, ändert sich das sichtbare Verhalten nicht. Die Änderung ist eine Stabilitätshärtung für PECL-Erweiterungen und andere C-Ebenen-Integrationen.
Phar: Use-After-Free beim Iterieren über komprimierte Archive
Ein Use-After-Free in der Phar-Erweiterung, der beim gleichzeitigen Löschen und Iterieren von Einträgen in komprimierten Archiven auftreten konnte.
GH-21333: Gefährliche Kombination aus Iterator und unlink()
Phar-Archive können GZip- oder BZip2-komprimierte Einträge enthalten. Beim Iterieren über ein solches komprimiertes Archiv und gleichzeitigem Löschen des gerade besuchten Eintrags über unset() oder unlink() entstand ein Use-After-Free. Der Iterator hielt interne Zeiger auf Eintragsstrukturen, die durch das Löschen freigegeben wurden. Beim nächsten Iterationsschritt griff der Iterator auf diesen bereits freigegebenen Speicher zu.
<?php
$phar = new Phar('archive.phar');
foreach ($phar as $file) {
if ($file->isCompressed()) {
/*
In 8.5.4: unset() waehrend der Iteration eines komprimierten
Phars konnte einen Use-After-Free ausloesen.
In 8.5.5: Der Iterator invalidiert seine Zeiger korrekt.
*/
unset($phar[$file->getFilename()]);
}
}
In 8.5.5 wurde die Iterationslogik so angepasst, dass der Iterator seine internen Zeiger nach einem Löschen korrekt invalidiert. Relevant vor allem für Tools, die Phar-Archive programmatisch bereinigen oder transformieren.
SNMP: Undefiniertes Verhalten bei NULL-Argumenten
Ein einzelner Fix in der SNMP-Erweiterung, der sich aber in der Kategorie undefiniertes Verhalten einordnet und damit mehr ist als ein gewöhnlicher Schönheitsfehler.
GH-21336: SNMP::setSecurity() und die NULL-Falle
SNMP::setSecurity() konfiguriert SNMPv3-Sicherheitsparameter wie Authentifizierungsprotokoll, Privacy-Passphrase und Kontextinformationen. Mehrere der Parameter sind optional. Wenn diese optionalen Parameter explizit mit
NULL übergeben wurden, griff der interne C-Code direkt auf einen Zeiger zu, ohne vorher auf NULL zu prüfen. Der UndefinedBehaviorSanitizer meldete diese Stellen.
<?php
$session = new SNMP(SNMP::VERSION_3, '192.168.1.1', 'admin');
/*
In 8.5.4: NULL-Argumente konnten zu undefiniertem Verhalten fuehren.
In 8.5.5: NULL-Checks vorhanden, NULL wird wie ein leerer String behandelt.
*/
$session->setSecurity('authNoPriv', 'MD5', 'passphrase', null, null, null, null);
In 8.5.5 wurden die notwendigen NULL-Checks ergänzt. Fehlende optionale Parameter werden als leere Strings behandelt. Die Empfehlung bleibt, immer gültige Strings zu übergeben und NULL nur bewusst einzusetzen.
SOAP: Set-Cookie-Parser mit falschem Offset
Der SOAP-Client wertet Set-Cookie-Header in Server-Antworten aus, um Cookies für nachfolgende Anfragen zu verwalten. Im Attribut-Scanner des Cookie-Parsers war ein Offset falsch berechnet. Dadurch wurden Attribute, die nach bestimmten Mustern mit Semikolons oder Leerzeichen folgten, entweder übersprungen oder falsch eingelesen.
Die Folge war, dass Attribute, die nach bestimmten Mustern auf das erste Attribut folgten, nicht korrekt dem Cookie zugeordnet wurden. Welche Attribute konkret betroffen sein konnten, hängt von der jeweiligen Header-Formatierung des Servers ab. Der offizielle Changelog nennt als Ursache lediglich den falschen Offset beim Scan.
In 8.5.5 wurden die Offsets im Parser korrigiert. Alle Attribute eines Set-Cookie-Headers werden jetzt korrekt eingelesen. Wer den SOAP-Client mit Webservices nutzt, die über Set-Cookie-Header Sitzungsdaten verwalten, sollte diesen Fix im Blick behalten.
SPL: Fehlende Write-Lock-Validierung in SplHeap
Ein Fix in der SPL-Erweiterung, der eine fehlende Absicherung in SplHeap schließt und vor allem in Kombination mit Fibers relevant ist.
GH-21454: SplHeap und Fibers
SplHeap hatte eine frühere Sicherheitshärtung erhalten, die Konsistenz-Checks einführte, um konkurrierende Schreibzugriffe auf die interne Heap-Struktur zu erkennen. Diese Checks fehlten jedoch an zwei Stellen:
Erstens rief SplHeap::next() die interne Funktion spl_ptr_heap_delete_top() auf, ohne vorher den Write-Lock zu prüfen. Zweitens validierte spl_heap_it_move_forward() mit write=false, obwohl intern eine schreibende Operation stattfand.
In Kombination mit PHP-Fibers konnte das problematisch werden: Wenn innerhalb des benutzerdefinierten compare()-Callbacks eine Fiber suspendierte und danach next() oder eine Iteratorbewegung auf demselben Heap ausgeführt wurde, war ein Use-After-Free oder Heap-Korruption möglich.
<?php
class FiberHeap extends SplHeap {
protected function compare(mixed $a, mixed $b): int {
/*
In 8.5.4: Fiber::suspend() waehrend eines Heap-Vergleichs
konnte zu Use-After-Free fuehren, wenn danach next()
auf demselben Heap aufgerufen wurde.
*/
Fiber::suspend();
return $b <=> $a;
}
}
$heap = new FiberHeap();
$heap->insert(3);
$heap->insert(1);
$heap->insert(2);
foreach ($heap as $value) {
/* next() wird implizit aufgerufen */
echo $value . "
";
}
In 8.5.5 wurden die fehlenden Write-Lock-Validierungen in next() und im Iterator ergänzt. Für Anwendungen, die SplHeap mit einfachen Callbacks nutzen, ist keine Änderung nötig.
Standard-Bibliothek: Zwei Fixes im Detail
Zwei unabhängige Korrekturen in der Standard-Bibliothek: ein Stabilitätsfix in highlight_string() und eine verbesserte Bilderkennung für das AVIF-Format.
GH-20906: Assertion-Fehler in highlight_string() bei manipulierten Output-Buffern
highlight_string() verwendet intern Output-Buffering. Wenn der Output-Buffer-Zustand durch externe Manipulationen inkonsistent war, etwa durch verschachtelte Buffer-Manipulationen oder gezielt herbeigeführte Fehlerpfade, konnte eine Debug-Assertion fehlschlagen:
/* Assertion-Meldung in Debug-Builds:
zif_highlight_string: Assertion `zval_get_type(&(*(return_value))) == 6' failed */
In 8.5.5 wurde highlight_string() so gehärtet, dass der Rückgabewert stets korrekt initialisiert ist, bevor auf ihn geprüft wird. Für normalen Anwendungscode ohne Output-Buffer-Manipulationen ist diese Änderung unsichtbar. Der Bug wurde über Fuzz-Testing entdeckt.
GH-20627: getimagesize() erkennt mehr AVIF-Varianten
AVIF ist ein modernes Bildformat, das auf dem HEIF/ISOBMFF-Container aufbaut. Dieser Container erlaubt zahlreiche Varianten bei der Box-Struktur und den Metadaten. getimagesize() hatte AVIF-Unterstützung erhalten, konnte aber nur eine Teilmenge der AVIF-Varianten zuverlässig erkennen. Bestimmte AVIF-Dateien mit anderen Box-Layouts oder Metadaten-Kombinationen wurden nicht erkannt: getimagesize() gab false zurück oder lieferte unvollständige Daten ohne Breite und Höhe.
In 8.5.5 wurde die AVIF-Erkennung erweitert, sodass zusätzliche Box-Layouts korrekt verarbeitet werden. CMS-Systeme und Bildverarbeitungspipelines, die getimagesize() für das Einlesen von AVIF-Uploads nutzen, sind damit robuster.
Sysvshm: Speicherleck bei korrupten Shared-Memory-Daten
shm_get_var() liest eine Variable aus einem System-V-Shared-Memory-Segment. Wenn die gespeicherten Daten korrupt waren, schlug die Deserialisierung intern fehl. In diesem Fehlerpfad wurden Speicherallokationen nicht vollständig freigegeben. Pro fehlgeschlagenem Zugriff auf ein korruptes Segment entstand ein kleines Speicherleck.
In lang laufenden Prozessen, die auf Shared-Memory angewiesen sind und bei denen Korruption durch Race-Conditions oder fehlerhafte Schreibvorgänge auftreten kann, addierte sich dieses Leck über die Zeit.
In 8.5.5 wurden die fehlenden Freigaben im Fehlerpfad ergänzt. Die allgemeine Empfehlung bleibt, Fehler bei shm_get_var() immer zu behandeln und ein Segment nach erkannter Korruption neu zu initialisieren.
XSL: Zwei Bugs im Zusammenspiel von DOM und XSL
Beide Fixes betreffen die Schnittstelle zwischen der XSL-Erweiterung und der DOM-API. Wer XSLT-Transformationen mit der neuen Dom-API kombiniert, profitiert von beiden Korrekturen.
GH-21357: XSLTProcessor akzeptiert jetzt auch DomXMLDocument
PHP 8.4 brachte eine neue DOM-API im Dom-Namespace. Während der klassische DOMDocument problemlos mit XSLTProcessor::importStylesheet() funktionierte, schlug der gleiche Aufruf mit dem neuen DomXMLDocument fehl. Die Brücke zwischen der neuen DOM-Implementierung und der libxslt-Bibliothek war unvollständig.
<?php
/* Klassische DOM-API: funktioniert */
$dom = new DOMDocument();
$dom->load('/pfad/zum/stylesheet.xsl');
$processor = new XSLTProcessor();
var_dump($processor->importStylesheet($dom)); /* bool(true) */
/* Neue DOM-API: schlug in 8.5.4 fehl */
$xmlDoc = DomXMLDocument::createFromFile('/pfad/zum/stylesheet.xsl');
$processor2 = new XSLTProcessor();
/*
In 8.5.4: importStylesheet() mit DomXMLDocument lieferte false.
In 8.5.5: Die Konvertierung zwischen neuer DOM-API und libxslt ist vollstaendig.
*/
var_dump($processor2->importStylesheet($xmlDoc)); /* bool(true) */
Wer die neue DOM-API nutzt und XSLT-Transformationen durchführt, kann ab 8.5.5 beides kombinieren. Kein Workaround über die alte API mehr nötig.
GH-21496: Use-After-Free in dom_objects_free_storage
Ein weiterer, verwandter Bug betraf die Destruktorlogik von DOM-Objekten im Zusammenspiel mit XSLTProcessor. Wenn ein DOMComment-Knoten mit einem DOMDocument verknüpft und anschließend in den Destruktorpfad von XSLTProcessor geriet, wurden Objekte mehrfach oder in falscher Reihenfolge freigegeben. Der AddressSanitizer meldete das als Use-After-Free.
<?php
/*
HINWEIS: Dies ist der offizielle Bug-Reproducer aus GH-21496,
kein regulaerer Anwendungsfall. XSLTProcessor::importStylesheet()
erwartet laut PHP-Manual ein DOMDocument, DomDocument oder
SimpleXMLElement. DOMComment ist kein gueltiges Argument.
Das Beispiel zeigt, wie ein bestimmter interner Codepfad
den UAF ausloeste.
*/
$comment = new DOMComment("Stil-Kommentar");
$doc = new DOMDocument();
$doc->loadXML('<stylesheet xmlns="urn:xsl"></stylesheet>');
$doc->documentElement->appendChild($comment);
$proc = new XSLTProcessor();
/*
In 8.5.4: Dieser Aufruf ausloeste einen UAF in dom_objects_free_storage.
In 8.5.5: Referenzzaehlung und Freigabereihenfolge sind korrekt.
*/
$proc->importStylesheet($comment);
In 8.5.5 wurden Referenzzählung und das Freigabeschema in dom_objects_free_storage korrigiert. Das Crash-Risiko in diesem Destruktor-Pfad ist damit beseitigt.
Kritikalität der Fixes im Überblick
Die folgende Tabelle zeigt alle Bereiche mit einer eigenen Einschätzung der Auswirkungen. Der offizielle Release-Text enthält keine Severity-Einstufungen; die Wertungen hier spiegeln meine persönliche Beurteilung auf Basis der verlinkten Issues und Commits wider.
| Bereich |
Bug |
Kritikalität |
| Opcache/JIT |
GH-20838 (falsche Arithmetik) |
Hoch (Datenkorrektheit) |
| Opcache/JIT |
GH-21395 (UAF im JIT) |
Hoch (Stabilität, Sicherheit) |
| Opcache/JIT |
GH-21267 (Endlosschleife Tracing) |
Hoch (DoS möglich) |
| XSL |
GH-21496 (UAF dom_objects) |
Hoch (Crash, Sicherheit) |
| Phar |
GH-21333 (UAF bei Iteration) |
Hoch (Crash, Sicherheit) |
| SPL |
GH-21454 (SplHeap Write-Lock) |
Mittel bis Hoch (mit Fibers) |
| OpenSSL |
GH-21083 (EC-Schlüssel OpenSSL 3.6) |
Mittel (Kompatibilität) |
| SOAP |
Set-Cookie-Parsing |
Mittel (Cookie-Parsing-Korrektheit) |
| PCRE |
Reentrancy |
Mittel (Stabilität bei Erweiterungen) |
| DOM |
GH-21486 (xml:*-Attribute) |
Mittel (Korrektheit) |
| Core |
GH-20875/73/54 (Lazy Proxy) |
Niedrig bis Mittel |
| Core |
GH-20672 (property_info) |
Niedrig bis Mittel (Reflection-Frameworks) |
| OpenSSL |
BIO_printf-Fehlerweiterleitung |
Niedrig bis Mittel |
| FFI |
Ressourcenleck in cdef() |
Niedrig bis Mittel (Langläufer) |
| Opcache |
GH-21052 (Preload-Konstanten) |
Niedrig bis Mittel |
| Standard |
GH-20627 (AVIF-Erkennung) |
Niedrig bis Mittel |
| Sysvshm |
Speicherleck in shm_get_var() |
Niedrig (Langläufer) |
| Bz2 |
Trunkierung Ausgabegröße |
Niedrig (nur bei über 2 GB) |
| GD |
GH-21431 (phpinfo libJPEG) |
Niedrig (diagnostisch) |
| Standard |
GH-20906 (highlight_string) |
Niedrig |
| PCNTL |
AIX-Signalhandler |
Niedrig (AIX-spezifisch) |
Handlungsempfehlungen für die Praxis
Sofort aktualisieren, wenn JIT aktiv ist. GH-20838 ist ein echter Rechenfehler ohne Fehlermeldung. Solche Bugs sind schwer zu erkennen, weil das Programm normal läuft und lediglich falsche Daten produziert. Wer JIT auf Produktionssystemen aktiviert hat, sollte das Update so schnell wie möglich einspielen oder JIT temporär deaktivieren.
Smoke-Tests vor dem Rollout durchführen. Die JIT-Fixes verändern das Verhalten von JIT-kompiliertem Code. Ein kurzer Smoke-Test mit den wichtigsten Anwendungspfaden auf einer Staging-Umgebung gibt Sicherheit, bevor das Update in der Produktion landet.
Phar-Tools überprüfen. Wer eigene Werkzeuge betreibt, die Phar-Archive programmatisch modifizieren und dabei Einträge während der Iteration entfernen, sollte diesen Pfad explizit testen.
OpenSSL-Version prüfen. Wer auf einem System mit OpenSSL 3.6 EC-Schlüssel über openssl_pkey_new() erzeugt, war in PHP 8.5.4 von einem Fehler betroffen. Nach dem Update auf 8.5.5 sollte das wieder zuverlässig funktionieren.
Neue DOM-API mit XSL kombinieren. Falls du die neue Dom-API in Kombination mit XSLT genutzt und dabei Probleme gehabt hast, sind beide XSL-Bugs in 8.5.5 behoben.
Monitoring nach dem Update. Die Korrekturen im JIT, bei Phar und bei DOM/XSL können in Kombination mit vorhandenem Code Verhaltensänderungen auslösen, die vorher durch Crashes oder Fehler verdeckt wurden. Es lohnt sich, nach dem Update für einige Stunden die Fehler-Logs im Auge zu behalten.
Fazit
PHP 8.5.5 ist ein kompaktes Release, das keine neuen Funktionen mitbringt. Trotzdem gibt es konkrete Gründe, warum dieses Update auf keinem Produktivsystem warten sollte.
Vier Fixes im Opcache-Bereich, davon drei direkt im JIT-Compiler und darunter ein echter Rechenfehler, sind für sich allein schon Grund genug. Drei Use-After-Free-Bugs kommen dazu. Die PCRE-Reentrancy-Fixes machen den PHP-Kern robuster gegenüber komplexen Erweiterungsszenarien. Die OpenSSL-Korrektur für EC-Schlüssel ist wichtig für alle, die auf OpenSSL 3.6 migriert sind. Die Korrekturen in DOM und XSL schließen Lücken, die mit der neuen Dom-API entstanden sind, welche PHP 8.4 mitgebracht hat. Und der SOAP-Fix behebt einen Fehler in einer langjährig vorhandenen Erweiterung, der das korrekte Einlesen von Cookie-Headern beeinträchtigte.
Wer wartet, setzt Produktionssysteme unnötigerem Risiko aus. Das Update einzuspielen, kurz zu testen und dann zu beobachten dauert wenig Zeit und bringt echten Gewinn an Stabilität, Korrektheit und Sicherheit.
(Autor:
schubertmedia), Eingetragen am 11.04.2026