News · OpenAI führt ChatGPT-Suchabstürze auf einen defekten Azure-Host und eine 18 Jahre alte libunwind-Race-Condition zurück

May, 274 Min. Lesezeit
Platform

OpenAI führt ChatGPT-Suchabstürze auf einen defekten Azure-Host und eine 18 Jahre alte libunwind-Race-Condition zurück

Wie eine Analyse eines Jahres an Core-Dumps auf Populationsebene zwei voneinander unabhängige Bugs auseinanderklamüserte, die sich als ein einziger, unmöglicher Fehler getarnt hatten

Zwei Abstürze im selben Kostüm

Im Zentrum steht Rockset, das cloud-native Such- und Analysesystem, das OpenAI 2024 übernommen hat und das heute den stets aktuellen Index der Wissensdatenbank eines Workspace für die ChatGPT-Suche pflegt. Die Ausführungsschicht ist in C++ geschrieben, und einige Monate vor dem Beitrag begann sie, physikalisch unmöglich erscheinende Abstürze zu produzieren: Eine normale Funktion schien fertig zu laufen und kehrte dann zu einer ungültigen Adresse zurück — mal zu einem NULL-Rücksprungadressen-Slot, mal mit einem Stack-Pointer %rsp, der exakt 8 Bytes verschoben war.

Keines der beiden Symptome passt zu gewöhnlichem Anwendungscode. Ein irregulärer Schreibzugriff, der ausgerechnet eine gesicherte Rücksprungadresse trifft, ist extrem unwahrscheinlich, und eine Verschiebung von %rsp um 8 Bytes ohne Inline-Assembler, setcontext oder longjmp — von denen Rockset keines verwendet — ist noch seltsamer, weil kompilierter Code dieses Register nur im Prolog und Epilog einer Funktion berührt. Jede Hypothese, die das Team oder ChatGPT aufstellte, hatte gewichtige Gegenbeweise. Der Grund, wie sich herausstellte: Man blickte gleichzeitig auf zwei unabhängige Bugs — eine stille Hardwarekorruption auf einem einzelnen Azure-Host und eine 18 Jahre alte Race-Condition in GNU libunwind.

Arzt versus Epidemiologe

Der erste Instinkt des Teams war klinisch: einzelne Core-Dumps genau untersuchen, Hypothesen bilden, sie eine nach der anderen ausschließen. Die meisten Abstürze traten in einer einzigen, stark inline-optimierten Methode auf, DocumentTree::updateDocument, was eine überwältigende Zahl an Kandidatenfunktionen übrig ließ. Log-Abfragen produzierten sowohl falsch-positive als auch falsch-negative Ergebnisse, weil die Stack-Korruptionsbugs genau jene Stack-Traces beschädigten, die zur Klassifizierung genutzt wurden. Manuelle Prüfung skalierte nicht. Schlimmer noch: Das Team hatte eine Hardware-Ursache bereits ausgeschlossen, weil die Abstürze über mehrere Regionen und Hardware-Typen verteilt auftraten — eine Schlussfolgerung, die für eine der beiden Fehlerpopulationen genau verkehrt war.

Der Wendepunkt war ein Methodenwechsel. Statt einen einzelnen Patienten zu diagnostizieren, behandelte man die gesamte Flotte als Population. Man ließ ChatGPT ein Skript schreiben, das von jeder Core-Datei ein Präfix herunterlud, Register extrahierte, bekannte Falsch-Positive gegen die Logs abglich und jeden Absturz als Return-to-Null, Misaligned-Stack oder Sonstiges kennzeichnete. Als man es über sämtliche Produktions-Rockset-Core-Dumps des Vorjahres laufen ließ, traten die Korrelationen sofort zutage: Return-to-Null-Abstürze verteilten sich über viele Cluster ohne klaren Startzeitpunkt, während Misaligned-Stack-Abstürze aus einer einzigen Region stammten, ein klares Startdatum hatten und nie auf lange laufenden Knoten auftraten — die typische Signatur einer einzelnen physischen Maschine mit defekter Hardware.

Ein Zeitfenster von hundert Pikosekunden — und die Fermi-Abschätzung, die es glaubwürdig machte

Der defekte Host wurde auf die Sperrliste gesetzt, und seine Misaligned-Stack-Abstürze verschwanden, obwohl das Team die Register-Korruption in wochenlangen Stresstests nie reproduzieren konnte. Nach Entfernung dieses Clusters traten die verbleibenden Return-to-Null-Abstürze allesamt während der C++-Exception-Abwicklung auf. GNU libunwind erzeugt einen ucontext_t auf dem Stack und übergibt ihn an eine interne Routine, _Ux86_64_setcontext, deren letzte Instruktionen %rsp auf den neuen Stack-Boden verschieben, bevor eine spätere Instruktion den Ziel-Instruktionszeiger aus genau derselben Struktur ausliest. Sobald sich %rsp verschiebt, ist die Struktur nicht mehr durch die Red Zone geschützt — trifft in diesem Zeitfenster ein Signal ein, kann der Kernel seinen Signal-Frame über diesen Speicher legen und den wiederherzustellenden Instruktionszeiger auf null setzen.

Das verwundbare Zeitfenster umfasst nur eine einzige Instruktion — auf einer modernen Out-of-Order-CPU etwa hundert Pikosekunden — was viel zu knapp schien, um mehr als ein Dutzend Return-to-Null-Abstürze pro Tag zu erklären. Eine Fermi-Abschätzung schloss die Lücke: Ein 10⁻¹⁰-Sekunden-Fenster gegenüber einem SIGUSR2, das alle 10⁻² Sekunden CPU-Zeit eintrifft, ergibt für jeden Cleanup-Handler eine Chance von rund 10⁻⁸, das Rennen zu verlieren; ein Host, der wegen Ingest-Backpressure 10⁴ Exceptions pro Sekunde auslöst, versagt dann etwa alle paar Stunden — bei Flottengröße völlig ausreichend.

Rockset war in allen drei Faktoren, die die Absturzrate treiben, ungewöhnlich. Es wirft häufig Exceptions als Überlastschutz, es liefert über seine coarse_thread_cputime_clock ungewöhnlich oft SIGUSR2 aus, und früher im Jahr war ein timer_getoverrun-Aufruf hinzugekommen, der den Signal-Handler mehr Stack verbrauchen ließ — genug, um bis zum veralteten ucontext_t vorzudringen und ihn zu überschreiben. Vor dieser Änderung traten die Abstürze überhaupt nicht auf. Der 18 Jahre alte Bug war schon immer vorhanden; erst das Produkt aus Exception-Rate, Signal-Rate und Handler-Stack-Verbrauch hatte gerade die Schwelle überschritten, ab der er sichtbar wurde.

Instrumentierung als eigentliche Lösung

Die Sofortmaßnahme bestand darin, von GNU libunwind auf den Unwinder von libgcc zu wechseln — ein für sich genommen guter Tausch, da libgcc mehr Arbeit in die Reduzierung der Lock-Konkurrenz auf großen VMs investiert hat — und OpenAI stellte einen eigenständigen Reproduzierer samt Fix für GNU libunwind zur Verfügung. Für den Hardwarefehler verbesserte das Team den Fatal-Signal-Handler so, dass er den Registerzustand protokolliert, sodass ein erneutes Auftreten allein aus den Logs erkennbar ist, ohne Core-Dump — und die Control Plane wurde so umgestellt, dass VMs wiederverwendet statt neu erstellt werden, was die Erkennung defekter Knoten erleichtert.

Die eigentliche Lehre liegt nicht in den Details zu ABI, DWARF oder Exception-Mechanik, die die Untersuchung zutage brachte. Sie liegt darin, dass die Verwirrung nur deshalb bestand, weil zwei Phänomene zu einer einzigen Geschichte verschmolzen waren — und sie löste sich in dem Moment auf, als der Datensatz sauber und vollständig wurde. Das ist die Erkenntnis, die sich auf andere Infrastruktur-Teams übertragen lässt: Wirkt ein Bug unmöglich, fehlt oft nicht eine klügere Einzelfall-Diagnose, sondern schlicht vollständige Populationsdaten.

Bei Zuverlässigkeit geht es nicht nur darum, Bugs zu beheben, nachdem sie aufgetreten sind — es geht darum, die Daten, Workflows und Fähigkeiten aufzubauen, die scheinbar unlösbare Probleme diagnostizierbar und lösbar machen.Montana Labs

Find this story relevant to you?

Contact us to find a unique solution

Contact us

Brauchen Sie einen KI-Engineering-Partner, der auch liefern kann?

Wir helfen Unternehmen dabei, KI in Produkte und Prozesse zu integrieren, wertvolle Workflows zu automatisieren und die zugrunde liegenden Softwaresysteme zu modernisieren.

Get in touch

Weiterführende Beiträge

Weitere Analysen rund um Produktbereitstellung, operative KI und die Systemarbeit, die dafür sorgt, dass der Einsatz in der Praxis Bestand hat.

Jul, 134 Min. Lesezeit
Platform

Doppel automatisiert die Bekämpfung von Phishing mit einer fünfstufigen GPT-5- und RFT-Pipeline

Jul, 94 Min. Lesezeit
Platform

OpenAI veröffentlicht gpt-oss unter Apache 2.0 und bezeichnet es als Model Card, nicht als System Card

Jul, 94 min to read
Platform

OpenAI's GPT-5.2-Codex ties a coding model release to a graded cybersecurity rollout