Skip to content
research

Un bug de mémoire vieux de 11 ans dans Rust se cachait dans du code « safe »

Un cas limite minuscule et obscur du système de types est resté caché pendant plus d'une décennie. Sa découverte nous rappelle que même les garanties de sécurité les plus robustes dépendent de la précision absolue du code du compilateur.

Aki Tanaka
Un bug de mémoire vieux de 11 ans dans Rust se cachait dans du code « safe »

Le minuscule modèle de code « safe » qui a brisé une promesse majeure

La promesse fondamentale de Rust — selon laquelle son borrow checker empêche les use-after-free — a récemment rencontré un défi significatif. L'Issue #164250, ouverte la semaine dernière, détaille un bug de sécurité mémoire reproductible dans du code Rust entièrement « safe », un scénario auparavant considéré comme impossible. Cette faille a persisté sans être détectée pendant 11 ans, depuis Rust 1.4.0 en 2015.

L'exploit repose sur un modèle spécifique : une structure générique, telle que struct Q<T: 'static>(Option<T>);, déclare une exigence de durée de vie 'static pour son paramètre de type T. Crucialement, cette structure implémente également une fonction Drop personnalisée qui fait implicitement confiance à cette contrainte 'static, en supposant que les données contenues persisteront pendant toute la durée du programme.

L'étape surprenante se produit lors de l'assignation. Les développeurs instancient la structure vide, par exemple let mut q = Q(None);. Ensuite, ils assignent une référence à une variable locale à courte durée de vie, comme &l, dans son champ covariant : q.0 = Some(&l);. Ici, le compilateur échoue à appliquer l'obligation de durée de vie 'static du type externe sur la référence assignée.

Cet oubli permet à l'implémentation de Drop de recevoir une référence qu'elle croit être 'static, mais qui pointe en réalité vers une mémoire désallouée une fois que la variable locale sort de sa portée. Ce scénario, vérifié par AddressSanitizer et Miri, entraîne un stack-use-after-scope ou un dangling reference use-after-free, violant directement les garanties fondamentales de sécurité mémoire de Rust.

Comment une promesse de durée de vie s'est transformée en référence pendante

Le cœur de la défaillance réside dans la manière dont le compilateur Rust gère les promesses de durée de vie lors d'assignations spécifiques. Une structure peut déclarer que toute référence qu'elle contient doit vivre pendant toute la durée du programme, en utilisant la contrainte de durée de vie 'static. Une implémentation Drop personnalisée pour cette structure fait alors implicitement confiance à cette promesse, en supposant que sa référence stockée restera valide.

Les problèmes surviennent lorsqu'une référence à une variable locale — dotée d'une portée limitée — est assignée à un champ au sein d'une telle structure. Le compilateur effectue une vérification de type sur l'assignation mais, de manière cruciale, omet de re-valider l'exigence globale de durée de vie de la structure par rapport à la référence nouvellement assignée. Cela crée une déconnexion : le destructeur de la structure croit qu'il détient une référence 'static, mais les données sous-jacentes sont une variable locale sur le point d'expirer.

Lorsque la structure est supprimée (dropped), son implémentation Drop s'exécute, opérant sur une référence qui pointe désormais vers une mémoire désallouée. Un Drop délibérément conçu peut alors stocker cette référence pendante (dangling reference) ou tenter de l'utiliser, entraînant des erreurs de mémoire de type use-after-free ou stack-use-after-scope. Par exemple, le Drop pourrait transmettre la référence invalide à un thread séparé pour un accès ultérieur.

Bien que le modèle d'exemple n'implique pas directement de blocs unsafe, la corruption mémoire qui en résulte démontre une faille dans les vérifications du compilateur. Des outils comme Miri et AddressSanitizer (ASan) peuvent détecter ces accès mémoire invalides, signalant des problèmes tels que stack-use-after-scope ou des références pendantes use-after-free, confirmant ainsi l'exploitabilité de l'Issue #164250.

Onze ans d'existence — et difficile à déclencher par accident

Un contributeur au compilateur Rust a retracé le bug de sécurité mémoire de l'Issue #164250 jusqu'à Rust 1.4, sorti en octobre 2015. Cela rend la faille vieille d'environ 11 ans, une longévité étonnante pour une vulnérabilité dans un langage dont la promesse fondamentale est la sécurité mémoire. La découverte a conduit le rapporteur à s'exclamer : "Comment diable cela n'a-t-il pas été découvert plus tôt ?"

Malgré son ancienneté et son impact sur les garanties fondamentales de Rust, le problème est classé comme I-unsound et P-high, et non P-critical. Cette classification reflète la nature complexe du modèle d'exploitation, et non une vision diminuée de la fiabilité. Une classification P-critical est généralement réservée aux bugs facilement déclenchés accidentellement dans le code courant.

L'exposition accidentelle à cette faille est considérée comme hautement improbable dans le code d'application ordinaire. L'exploitation du bug nécessite une combinaison délibérée de facteurs :

  • Un type générique avec une contrainte de durée de vie 'static
  • Une assignation de champ permissive qui contourne la validation de la durée de vie
  • Une implémentation personnalisée de Drop conçue pour exploiter l'inadéquation, souvent en déplaçant une référence pendante vers une variable globale ou un autre thread.

Le membre de l'équipe Rust Types, lcnr, a noté que la dormance de 11 ans du bug suggère qu'il n'est pas facilement rencontré. Ce modèle spécifique, bien qu'il brise un invariant fondamental, exige une implémentation Drop malveillante ou très inhabituelle pour déclencher un use-after-free. Pour plus de détails techniques, consultez l'issue GitHub : struct's lifetime requirements can be bypassed via covariant field assignment · Issue #164250 · rust-lang/rust.

Cet article vous plaît ? Recevez-en un comme celui-ci chaque matin.

un e-mail par jour · désinscription en deux clics · aucun traqueur tiers

Le correctif — et la leçon derrière le titre

Le remède proposé pour l'Issue #164250 est arrivé rapidement dans la PR #164277. Le correctif implique d'améliorer la validation de la conformité des MIR local types du compilateur, en s'assurant spécifiquement que les assignations et le sous-typage implicite ne contournent pas les vérifications cruciales des contraintes de durée de vie. Cela traite directement la validation manquante qui permettait de briser la promesse de durée de vie 'static.

Cet incident, bien qu'alarmant, souligne une vérité plus large : la sécurité mémoire, même dans un langage comme Rust, repose fondamentalement sur des garanties appliquées par le compilateur. Les compilateurs sont des systèmes logiciels complexes et, comme tout code complexe, ils peuvent abriter des cas limites subtils et des interactions imprévues. Des tests rigoureux, une revue de code continue et un travail constant sur la soundness sont indispensables.

La promesse fondamentale de Rust en matière de sécurité mémoire dans le code sûr reste robuste, mais ce bug vieux de 11 ans sert de rappel brutal. Il souligne pourquoi la fiabilité du compilateur est aussi critique que la conception du langage pour tenir cette promesse. Bien qu'il s'agisse d'une véritable violation de la garantie de code sûr de Rust, cela ne suggère pas que Rust est globalement non sécurisé ; cela renforce plutôt la nécessité d'un développement de compilateur vigilant.

Questions fréquemment posées

Quel était le bug de sécurité mémoire de Rust ?

L'Issue #164250 décrit comment une assignation de champ pouvait contourner une exigence de durée de vie sur une structure, permettant à du code sûr de créer une référence pendante sous un destructeur spécialement conçu.

Le code Rust ordinaire pourrait-il déclencher le bug accidentellement ?

Le modèle démontré nécessite une combinaison inhabituelle, en particulier une implémentation de Drop qui fait délibérément confiance et utilise une référence avec une durée de vie faussement longue.

Le bug signifie-t-il que Rust n'est plus sécurisé en mémoire ?

Non. Il s'agit d'une faille sérieuse de fiabilité du compilateur, mais elle n'invalide pas les avantages plus larges de Rust en matière de sécurité mémoire et ne signifie pas que les programmes Rust typiques sont exposés.

Comment le bug de Rust a-t-il été proposé d'être corrigé ?

Le correctif proposé vérifie que les MIR local types satisfont leurs exigences de conformité, empêchant les assignations de contourner les contraintes de durée de vie.

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

Pour les builders

Cette page travaille pour l’outil de quelqu’un d’autre.

Les agents IA la lisent. Des acheteurs y arrivent. Elle répond en huit langues et via MCP. Votre outil peut avoir la sienne — en ligne en 24 heures.