Skip to content
research

Rusts 11-jähriger Speicherfehler versteckte sich in sicherem Code

Ein winziger, obskurer Grenzfall im Typsystem blieb über ein Jahrzehnt lang unentdeckt. Seine Entdeckung erinnert daran, dass selbst die stärksten Sicherheitsgarantien davon abhängen, dass der Compiler-Code jedes Detail korrekt verarbeitet.

Aki Tanaka
Rusts 11-jähriger Speicherfehler versteckte sich in sicherem Code

Das winzige Safe-Code-Muster, das ein großes Versprechen brach

Rusts grundlegendes Versprechen – dass der Borrow Checker Use-after-free verhindert – stieß kürzlich auf eine bedeutende Herausforderung. Issue #164250, das letzte Woche eröffnet wurde, beschreibt einen Speichersicherheitsfehler, der in vollständig sicherem Rust-Code reproduzierbar ist – ein Szenario, das zuvor als unmöglich galt. Diese Unsoundness blieb 11 Jahre lang unentdeckt, seit Rust 1.4.0 im Jahr 2015.

Der Exploit basiert auf einem spezifischen Muster: Ein generischer Struct, wie struct Q<T: 'static>(Option<T>);, deklariert eine 'static-Lifetime-Anforderung für seinen Typparameter T. Entscheidend ist, dass dieser Struct auch eine benutzerdefinierte Drop-Funktion implementiert, die dieser 'static-Bindung implizit vertraut und davon ausgeht, dass die enthaltenen Daten für die gesamte Dauer des Programms bestehen bleiben.

Der überraschende Schritt erfolgt bei der Zuweisung. Entwickler instanziieren den Struct leer, zum Beispiel let mut q = Q(None);. Dann weisen sie eine Referenz auf eine kurzlebige lokale Variable, wie &l, seinem kovarianten Feld zu: q.0 = Some(&l);. Hier versäumt es der Compiler, die 'static-Lifetime-Verpflichtung des äußeren Typs für die zugewiesene Referenz durchzusetzen.

Dieses Versäumnis ermöglicht es der Drop-Implementierung, eine Referenz zu erhalten, von der sie glaubt, sie sei 'static, die aber tatsächlich auf bereits freigegebenen Speicher zeigt, sobald die lokale Variable den Gültigkeitsbereich verlässt. Dieses Szenario, das durch AddressSanitizer und Miri verifiziert wurde, führt zu stack-use-after-scope oder dangling reference use-after-free und verletzt damit direkt die grundlegenden Speichersicherheitsgarantien von Rust.

Wie ein Lifetime-Versprechen zu einer Dangling Reference wurde

Der Kern des Fehlers liegt darin, wie der Compiler von Rust Lifetime-Versprechen bei bestimmten Zuweisungen handhabt. Ein Struct kann deklarieren, dass jede Referenz, die er enthält, für die gesamte Dauer des Programms existieren muss, indem er die 'static lifetime bound verwendet. Eine benutzerdefinierte Drop-Implementierung für diesen Struct vertraut dann implizit diesem Versprechen und geht davon aus, dass die gespeicherte Referenz gültig bleibt.

Probleme entstehen, wenn eine Referenz auf eine lokale Variable – eine mit begrenztem Gültigkeitsbereich – einem Feld innerhalb eines solchen Structs zugewiesen wird. Der Compiler führt eine Typüberprüfung der Zuweisung durch, versäumt es jedoch entscheidend, die gesamte Lifetime-Anforderung des Structs erneut gegen die neu zugewiesene Referenz zu validieren. Dies erzeugt eine Diskrepanz: Der Destruktor des Structs glaubt, er halte eine 'static-Referenz, aber die zugrunde liegenden Daten sind eine lokale Variable, die kurz vor dem Ablauf steht.

Wenn der Struct gedroppt wird, führt seine Drop-Implementierung Operationen auf einer Referenz aus, die nun auf freigegebenen Speicher zeigt. Ein gezielt erstelltes Drop kann diese dangling reference speichern oder versuchen, sie zu verwenden, was zu use-after-free oder stack-use-after-scope Speicherfehlern führt. Zum Beispiel könnte das Drop die ungültige Referenz an einen separaten Thread für einen späteren Zugriff übergeben.

Obwohl das Beispielmuster nicht direkt unsafe-Blöcke beinhaltet, demonstriert die resultierende Speicherbeschädigung eine Lücke in der Compiler-Prüfung. Tools wie Miri und AddressSanitizer (ASan) können diese ungültigen Speicherzugriffe erkennen, Probleme wie stack-use-after-scope oder dangling reference use-after-free markieren und so die Ausnutzbarkeit von Issue #164250 bestätigen.

Elf Jahre alt – und schwer zufällig zu treffen

Ein Mitwirkender am Rust-Compiler konnte den Memory-Safety-Bug in Issue #164250 bis zu Rust 1.4 zurückverfolgen, das im Oktober 2015 veröffentlicht wurde. Damit ist der Fehler etwa 11 Jahre alt – eine erstaunliche Langlebigkeit für eine Sicherheitslücke in einer Sprache, deren Kernversprechen die Memory Safety ist. Die Entdeckung veranlasste den Reporter zu der Frage: "Wie um alles in der Welt konnte das bisher unentdeckt bleiben?"

Trotz seines Alters und der Auswirkungen auf die grundlegenden Garantien von Rust wird das Problem als I-unsound und P-high eingestuft, nicht als P-critical. Diese Klassifizierung spiegelt die konstruierte Natur des Exploit-Musters wider und nicht eine geringere Einschätzung der Korrektheit (Soundness). Eine P-critical-Bewertung ist normalerweise Fehlern vorbehalten, die in gewöhnlichem Code leicht versehentlich ausgelöst werden können.

Eine versehentliche Ausnutzung dieses Fehlers gilt in gewöhnlichem Anwendungscode als äußerst unwahrscheinlich. Die Ausnutzung des Bugs erfordert eine gezielte Kombination von Faktoren:

  • Ein generischer Typ mit einer 'static-Lifetime-Bindung
  • Eine permissive Feldzuweisung, die die Lifetime-Validierung umgeht
  • Eine benutzerdefinierte Drop-Implementierung, die darauf ausgelegt ist, die Diskrepanz auszunutzen, oft durch das Verschieben einer hängenden Referenz (dangling reference) auf eine globale Variable oder einen anderen Thread.

Das Mitglied des Rust Types Teams, lcnr, merkte an, dass die 11-jährige Schlummerzeit des Bugs darauf hindeutet, dass er nicht leicht auszulösen ist. Dieses spezifische Muster bricht zwar eine Kerninvariante, erfordert aber eine bösartige oder höchst ungewöhnliche Drop-Implementierung, um ein Use-after-free auszulösen. Weitere technische Details finden Sie im GitHub-Issue: struct's lifetime requirements can be bypassed via covariant field assignment · Issue #164250 · rust-lang/rust.

Gefällt Ihnen der Artikel? Erhalten Sie jeden Morgen einen wie diesen per E-Mail.

eine E-Mail pro Tag · Abmeldung mit zwei Klicks · kein Tracking durch Dritte

Der Fix – und die Lektion hinter der Schlagzeile

Ein Lösungsvorschlag für Issue #164250 wurde schnell in PR #164277 eingereicht. Der Fix umfasst die Verbesserung der Well-Formedness-Validierung des Compilers für MIR local types, insbesondere um sicherzustellen, dass Zuweisungen und implizites Subtyping keine entscheidenden Lifetime-Bound-Prüfungen umgehen. Dies adressiert direkt die fehlende Validierung, die es ermöglichte, das 'static-Lifetime-Versprechen zu brechen.

Dieser Vorfall ist zwar alarmierend, unterstreicht aber eine umfassendere Wahrheit: Memory Safety beruht, selbst in einer Sprache wie Rust, grundlegend auf vom Compiler erzwungenen Garantien. Compiler sind komplexe Softwaresysteme, und wie jeder komplexe Code können sie subtile Grenzfälle und unvorhergesehene Interaktionen enthalten. Strenge Tests, kontinuierliche Code-Reviews und laufende Soundness-Arbeiten sind unverzichtbar.

Rusts Kernversprechen der Memory Safety in sicherem Code bleibt robust, aber dieser 11 Jahre alte Bug dient als deutliche Erinnerung. Er verdeutlicht, warum Compiler-Soundness bei der Einlösung dieses Versprechens genauso wichtig ist wie das Sprachdesign. Obwohl es sich um einen echten Verstoß gegen die Safe-Code-Garantie von Rust handelt, deutet dies nicht darauf hin, dass Rust generell unsicher ist; vielmehr unterstreicht es die Notwendigkeit einer wachsamen Compiler-Entwicklung.

Häufig gestellte Fragen

Was war der Rust Memory-Safety-Bug?

Issue #164250 beschreibt, wie eine Feldzuweisung eine Lifetime-Anforderung an ein Struct umgehen konnte, wodurch sicherer Code unter Verwendung eines speziell präparierten Destruktors eine hängende Referenz (dangling reference) erstellen konnte.

Könnte gewöhnlicher Rust-Code den Bug versehentlich auslösen?

Das demonstrierte Muster erfordert eine ungewöhnliche Kombination, insbesondere eine Drop-Implementierung, die bewusst einer Referenz mit einer fälschlicherweise langen Lifetime vertraut und diese verwendet.

Bedeutet der Bug, dass Rust nicht mehr Memory-Safe ist?

Nein. Es handelt sich um einen schwerwiegenden Fehler in der Compiler-Soundness, der jedoch die breiteren Vorteile der Memory Safety von Rust nicht entwertet und nicht bedeutet, dass typische Rust-Programme gefährdet sind.

Wie wurde vorgeschlagen, den Rust-Bug zu beheben?

Der vorgeschlagene Fix prüft, ob MIR local types ihre Well-Formedness-Anforderungen erfüllen, und verhindert so, dass Zuweisungen Lifetime-Beschränkungen umgehen.

Found this useful? Share it.

For builders

Want Stork to write one of these about your product?

Send us a URL. We use the product, form a view, and publish what we actually think — in 8 languages, labeled Sponsored, with no copy approval on your side. That last part is what makes it worth quoting.

See how it works$199 · AI tools & software only

Für Builder

Diese Seite arbeitet gerade für das Tool von jemand anderem.

KI-Agenten lesen sie. Käufer landen darauf. Sie antwortet in acht Sprachen und über MCP. Dein Tool kann so eine haben — in 24 Stunden live.