11 year old unsoundness bugs found in Rust borrow checker
🇬🇧 English
A Hacker News report describes a soundness bug in Rust in which a struct's lifetime requirements can be bypassed through covariant field assignment. The reporter shares a small program that uses a wrapper type, Q<T>, holding an Option<T> with a Drop implementation. The Drop impl moves the contained value into a static Mutex<Vec<Box<dyn Any + Send>>>, which should be impossible for a borrowed value that does not live for 'static.
In the example, a local String is assigned by reference into the wrapper inside an inner scope, so the borrow should end when that scope closes. Because the wrapper's field is covariant, the compiler accepts the assignment, and the reference can then be stored beyond the lifetime of the local. The program compiles and prints unrelated memory contents instead of being rejected.
When run under AddressSanitizer, the program crashes with a stack-use-after-scope error, confirming that the dangling reference is actually dereferenced. The issue is reproducible on current nightly builds and on stable releases back to at least 1.85.0, according to the report. Links to Compiler Explorer examples with and without sanitizers accompany the post.
The finding points to a soundness hole in how variance interacts with Drop-based storage, and it is the kind of issue that Rust maintainers treat as a high priority because safe code should never produce undefined behavior.
🇸🇦 العربية
تجاوز عمر بنية Rust عبر الإسناد المتغاير
يصف تقرير على Hacker News خللاً في سلامة (soundness) لغة Rust يمكن من خلاله تجاوز متطلبات عمر البنية عبر إسناد الحقول المتغاير. يشارك المُبلِّغ برنامجاً صغيراً يستخدم نوع غلاف، هو Q<T>، يحتوي على Option<T> مع تنفيذ Drop. ينقل تنفيذ Drop القيمة المحتواة إلى Mutex<Vec<Box<dyn Any + Send>>> ثابت، وهو ما كان يجب أن يكون مستحيلاً بالنسبة لقيمة مستعارة لا تعيش لمدة 'static.
في المثال، يُسنَد String محلي بالمرجع إلى الغلاف داخل نطاق داخلي، لذا كان ينبغي أن ينتهي الاستعارة عند إغلاق ذلك النطاق. ولأن حقل الغلاف متغاير، يقبل المترجم الإسناد، ويمكن بعدها تخزين المرجع خارج عمر المتغير المحلي. يُترجَم البرنامج وينتج بدلاً من الرفض طباعة محتويات ذاكرة غير ذات صلة.
عند تشغيله تحت AddressSanitizer، ينهار البرنامج بخطأ stack-use-after-scope، ما يؤكد أن المرجع المتدلي يُلغى مرجعه فعلاً. وبحسب التقرير، يمكن إعادة إنتاج المشكلة في إصدارات nightly الحالية، وكذلك في الإصدارات المستقرة حتى 1.85.0 على الأقل. ويرفق المنشور روابط لأمثلة في Compiler Explorer مع المدقّقات وبدونها.
يشير هذا الاكتشاف إلى ثغرة في السلامة تتعلق بكيفية تفاعل التغاير مع التخزين القائم على Drop، وهو نوع من المشكلات التي يتعامل معها مطورو Rust بأولوية عالية، لأن الشيفرة الآمنة لا ينبغي أبداً أن تُنتج سلوكاً غير معرّف.
أسئلة شائعة: لماذا يُترجَم برنامج Rust هذا رغم وجود مرجع متدلٍّ؟
يسمح تغاير الحقل للمترجم بقبول إسناد مرجع أقصر عمراً، ثم يخزّن تنفيذ Drop ذلك المرجع في حاوية 'static، فيمتد استخدامه إلى ما بعد عمر المتغير المحلي. وهذه ثغرة في السلامة، وكان ينبغي للمترجم أن يرفض هذه الشيفرة.
لماذا يُترجَم برنامج Rust هذا رغم وجود مرجع متدلٍّ؟
يسمح تغاير الحقل للمترجم بقبول إسناد مرجع أقصر عمراً، ثم يخزّن تنفيذ Drop ذلك المرجع في حاوية 'static، فيمتد استخدامه إلى ما بعد عمر المتغير المحلي. وهذه ثغرة في السلامة، وكان ينبغي للمترجم أن يرفض هذه الشيفرة.
🇧🇩 বাংলা
কোভেরিয়েন্ট অ্যাসাইনমেন্টের মাধ্যমে Rust স্ট্রাক্টের লাইফটাইম বাইপাস
Hacker News-এর একটি প্রতিবেদনে Rust-এর একটি সাউন্ডনেস বাগের বর্ণনা দেওয়া হয়েছে, যেখানে কোভেরিয়েন্ট ফিল্ড অ্যাসাইনমেন্টের মাধ্যমে একটি স্ট্রাক্টের লাইফটাইম শর্ত এড়িয়ে যাওয়া সম্ভব। প্রতিবেদক একটি ছোট প্রোগ্রাম শেয়ার করেছেন, যেখানে Q<T> নামের একটি র্যাপার টাইপ ব্যবহৃত হয়েছে, যাতে একটি Option<T> রয়েছে এবং একটি Drop ইমপ্লিমেন্টেশন আছে। Drop ইমপ্লিমেন্টেশন অভ্যন্তরীণ মানটিকে একটি স্ট্যাটিক Mutex<Vec<Box<dyn Any + Send>>>-এ সরিয়ে নেয়, যা এমন একটি ধার করা মানের জন্য অসম্ভব হওয়া উচিত যা 'static হিসেবে বেঁচে থাকে না।
উদাহরণে, একটি অভ্যন্তরীণ স্কোপের ভেতরে একটি স্থানীয় String-কে রেফারেন্স হিসেবে র্যাপারে অ্যাসাইন করা হয়েছে, তাই সেই স্কোপ বন্ধ হলে ধারটি শেষ হওয়ার কথা। র্যাপারের ফিল্ডটি কোভেরিয়েন্ট হওয়ায় কম্পাইলার অ্যাসাইনমেন্টটি গ্রহণ করে, এবং তারপর রেফারেন্সটি স্থানীয় ভেরিয়েবলের জীবনকালের বাইরেও সংরক্ষণ করা যায়। প্রোগ্রামটি প্রত্যাখ্যাত না হয়ে কম্পাইল হয় এবং সম্পর্কহীন মেমরির বিষয়বস্তু প্রিন্ট করে।
AddressSanitizer-এর অধীনে চালালে প্রোগ্রামটি stack-use-after-scope ত্রুটিসহ ক্র্যাশ করে, যা নিশ্চিত করে যে ঝুলন্ত রেফারেন্সটি প্রকৃতপক্ষে ডিরেফারেন্স করা হচ্ছে। প্রতিবেদন অনুযায়ী, বর্তমান nightly বিল্ডে এবং অন্তত 1.85.0 পর্যন্ত স্থিতিশীল রিলিজে সমস্যাটি পুনরুৎপাদনযোগ্য। পোস্টের সঙ্গে স্যানিটাইজার সহ ও ছাড়া Compiler Explorer উদাহরণের লিংক রয়েছে।
এই আবিষ্কার দেখায় যে Drop-ভিত্তিক সংরক্ষণের সঙ্গে ভেরিয়েন্স কীভাবে কাজ করে সেখানে একটি সাউন্ডনেস ফাঁক রয়েছে। Rust-এর রক্ষণাবেক্ষণকারীরা এমন সমস্যাকে উচ্চ অগ্রাধিকার দেন, কারণ নিরাপদ কোড কখনোই অনির্ধারিত আচরণ তৈরি করা উচিত নয়।
ঝুলন্ত রেফারেন্স থাকা সত্ত্বেও এই Rust প্রোগ্রামটি কেন কম্পাইল হয়?
ফিল্ডের কোভেরিয়েন্সের কারণে কম্পাইলার অপেক্ষাকৃত স্বল্প-জীবনের রেফারেন্স অ্যাসাইন করা গ্রহণ করে, এবং তারপর Drop ইমপ্লিমেন্টেশন সেই রেফারেন্সটিকে একটি 'static কন্টেইনারে সংরক্ষণ করে, ফলে স্থানীয় ভেরিয়েবলের জীবনকালের পরেও এর ব্যবহার চলতে থাকে। এটি একটি সাউন্ডনেস বাগ, তাই কম্পাইলারের এই কোড প্রত্যাখ্যান করা উচিত ছিল।
🇩🇪 Deutsch
Umgehung der Lebensdauer einer Rust-Struktur durch kovariante Zuweisung
Ein Bericht auf Hacker News beschreibt einen Soundness-Fehler in Rust, durch den sich die Lebensdauer-Anforderungen einer Struktur über eine kovariante Feldzuweisung umgehen lassen. Der Verfasser teilt ein kleines Programm, das einen Wrapper-Typ Q<T> verwendet. Dieser enthält einen Option<T> und eine Drop-Implementierung. Die Drop-Implementierung verschiebt den enthaltenen Wert in ein statisches Mutex<Vec<Box<dyn Any + Send>>>, was für einen geliehenen Wert, der nicht für 'static lebt, eigentlich unmöglich sein sollte.
Im Beispiel wird ein lokaler String per Referenz in einem inneren Gültigkeitsbereich dem Wrapper zugewiesen. Die Leihe sollte daher enden, wenn dieser Bereich geschlossen wird. Da das Feld des Wrappers kovariant ist, akzeptiert der Compiler die Zuweisung, und die Referenz kann anschließend über die Lebensdauer der lokalen Variable hinaus gespeichert werden. Das Programm kompiliert und gibt unzusammenhängende Speicherinhalte aus, anstatt abgelehnt zu werden.
Unter AddressSanitizer stürzt das Programm mit einem stack-use-after-scope-Fehler ab. Das bestätigt, dass die hängende Referenz tatsächlich dereferenziert wird. Laut Bericht lässt sich das Problem in aktuellen Nightly-Builds sowie in stabilen Versionen bis mindestens 1.85.0 reproduzieren. Der Beitrag enthält Links zu Compiler-Explorer-Beispielen mit und ohne Sanitizer.
Der Fund deutet auf eine Soundness-Lücke hin, die entsteht, wenn Varianz mit Drop-basierter Speicherung zusammenwirkt. Solche Probleme stufen die Rust-Maintainer als hohe Priorität ein, denn sicherer Code darf niemals undefiniertes Verhalten hervorrufen.
Warum kompiliert dieses Rust-Programm trotz der hängenden Referenz?
Die Kovarianz des Feldes erlaubt dem Compiler, die Zuweisung einer kurzlebigeren Referenz zu akzeptieren. Die Drop-Implementierung speichert diese Referenz dann in einem 'static-Container und verlängert damit ihre Nutzung über die Lebensdauer der lokalen Variable hinaus. Dies ist ein Soundness-Fehler, der Compiler hätte den Code ablehnen müssen.
🇪🇸 Español
Evasión del tiempo de vida de una struct en Rust mediante asignación covariante
Un informe de Hacker News describe un error de solidez en Rust en el que los requisitos de tiempo de vida de una struct pueden eludirse mediante la asignación covariante de campos. El autor comparte un programa pequeño que usa un tipo envoltorio, Q<T>, que contiene un Option<T> con una implementación de Drop. La implementación de Drop mueve el valor contenido a un Mutex<Vec<Box<dyn Any + Send>>> estático, lo cual debería ser imposible para un valor prestado que no vive durante 'static.
En el ejemplo, un String local se asigna por referencia al envoltorio dentro de un ámbito interno, por lo que el préstamo debería terminar cuando se cierra ese ámbito. Como el campo del envoltorio es covariante, el compilador acepta la asignación, y la referencia puede entonces almacenarse más allá del tiempo de vida de la variable local. El programa compila e imprime contenido de memoria no relacionado en lugar de ser rechazado.
Al ejecutarlo bajo AddressSanitizer, el programa falla con un error stack-use-after-scope, lo que confirma que la referencia colgante se desreferencia efectivamente. Según el informe, el problema es reproducible en las compilaciones nightly actuales y en las versiones estables desde al menos la 1.85.0. La publicación incluye enlaces a ejemplos de Compiler Explorer con y sin sanitizadores.
El hallazgo apunta a un agujero de solidez en la forma en que la varianza interactúa con el almacenamiento basado en Drop, y es el tipo de problema que los mantenedores de Rust tratan como de alta prioridad, porque el código seguro nunca debería producir comportamiento indefinido.
Preguntas frecuentes: ¿Por qué este programa de Rust compila a pesar de la referencia colgante?
La covarianza del campo permite que el compilador acepte la asignación de una referencia de vida más corta, y la implementación de Drop almacena luego esa referencia en un contenedor 'static, extendiendo su uso más allá del tiempo de vida de la variable local. Se trata de un error de solidez; el compilador debería rechazar el código.
¿Por qué este programa de Rust compila a pesar de la referencia colgante?
La covarianza del campo permite que el compilador acepte la asignación de una referencia de vida más corta, y la implementación de Drop almacena luego esa referencia en un contenedor 'static, extendiendo su uso más allá del tiempo de vida de la variable local. Se trata de un error de solidez; el compilador debería rechazar el código.
🇫🇷 Français
Contournement de la durée de vie d'une struct Rust par affectation covariante
Un rapport publié sur Hacker News décrit une faille de solidité (soundness) dans Rust, qui permet de contourner les exigences de durée de vie d'une struct grâce à l'affectation covariante d'un champ. L'auteur partage un petit programme utilisant un type enveloppe, Q<T>, qui contient un Option<T> ainsi qu'une implémentation de Drop. Cette implémentation déplace la valeur contenue dans un Mutex<Vec<Box<dyn Any + Send>>> statique, ce qui devrait être impossible pour une valeur empruntée qui ne vit pas pendant 'static.
Dans l'exemple, une String locale est affectée par référence à l'enveloppe à l'intérieur d'un bloc interne, si bien que l'emprunt devrait prendre fin à la fermeture de ce bloc. Comme le champ de l'enveloppe est covariant, le compilateur accepte l'affectation, et la référence peut alors être stockée au-delà de la durée de vie de la variable locale. Le programme compile et affiche le contenu de mémoires sans rapport au lieu d'être rejeté.
Lorsqu'il est exécuté avec AddressSanitizer, le programme plante avec une erreur stack-use-after-scope, ce qui confirme que la référence pendante est bien déréférencée. Selon le rapport, le problème est reproductible sur les versions nightly actuelles ainsi que sur les versions stables jusqu'à au moins la 1.85.0. Le message fournit des liens vers des exemples Compiler Explorer avec et sans sanitizers.
Cette découverte met en évidence une faille de solidité dans la manière dont la variance interagit avec le stockage fondé sur Drop. Ce type de problème est traité comme prioritaire par les mainteneurs de Rust, car le code sûr ne devrait jamais produire de comportement indéfini.
FAQ : Pourquoi ce programme Rust compile-t-il malgré la référence pendante ?
La covariance du champ permet au compilateur d'accepter l'affectation d'une référence de durée de vie plus courte, puis l'implémentation de Drop stocke cette référence dans un conteneur 'static, prolongeant son utilisation au-delà de la durée de vie de la variable locale. Il s'agit d'une faille de solidité : le compilateur aurait dû rejeter ce code.
Pourquoi ce programme Rust compile-t-il malgré la référence pendante ?
La covariance du champ permet au compilateur d'accepter l'affectation d'une référence de durée de vie plus courte, puis l'implémentation de Drop stocke cette référence dans un conteneur 'static, prolongeant son utilisation au-delà de la durée de vie de la variable locale. Il s'agit d'une faille de solidité : le compilateur aurait dû rejeter ce code.
🇮🇳 हिन्दी
कोवेरिएंट असाइनमेंट के जरिए Rust स्ट्रक्t लाइफटाइम बाईपास
Hacker News पर एक रिपोर्ट में Rust में एक soundness बग का वर्णन किया गया है, जिसमें कोवेरिएंट फ़ील्ड असाइनमेंट के माध्यम से किसी struct की लाइफटाइम आवश्यकताओं को बायपास किया जा सकता है। रिपोर्ट करने वाले ने एक छोटा प्रोग्राम साझा किया है जो एक रैपर टाइप Q<T> का उपयोग करता है, जिसमें एक Drop इम्प्लीमेंटेशन वाला Option<T> होता है। Drop इम्प्लीमेंटेशन निहित मान को एक स्टैटिक Mutex<Vec<Box<dyn Any + Send>>> में ले जाता है, जो उस उधार लिए गए मान के लिए असंभव होना चाहिए जो 'static के लिए जीवित नहीं है।
उदाहरण में, एक स्थानीय String को एक आंतरिक स्कोप के भीतर रैपर में संदर्भ द्वारा असाइन किया गया है, इसलिए वह उधार उस स्कोप के बंद होने पर समाप्त हो जाना चाहिए। चूंकि रैपर का फ़ील्ड कोवेरिएंट है, कंपाइलर असाइनमेंट को स्वीकार कर लेता है, और फिर संदर्भ को स्थानीय चर के जीवनकाल से आगे संग्रहीत किया जा सकता है। प्रोग्राम कंपाइल हो जाता है और अस्वीकार होने के बजाय असंबंधित मेमोरी सामग्री प्रिंट करता है।
AddressSanitizer के तहत चलाने पर, प्रोग्राम stack-use-after-scope त्रुटि के साथ क्रैश हो जाता है, जो पुष्टि करता है कि लटकता हुआ संदर्भ वास्तव में डीरेफ़रेंस किया जाता है। रिपोर्ट के अनुसार, यह समस्या वर्तमान nightly बिल्ड्स में और कम से कम 1.85.0 तक की स्थिर रिलीज़ में पुन: उत्पन्न की जा सकती है। पोस्ट के साथ sanitizers के साथ और बिना sanitizers के Compiler Explorer उदाहरणों के लिंक भी दिए गए हैं।
यह खोज बताती है कि Drop-आधारित संग्रहण के साथ variance किस तरह परस्पर क्रिया करता है, इसमें एक soundness छेद है। यह ऐसी समस्या है जिसे Rust के maintainers उच्च प्राथमिकता मानते हैं, क्योंकि सुरक्षित कोड को कभी भी undefined behavior उत्पन्न नहीं करना चाहिए।
लटकते संदर्भ के बावजूद यह Rust प्रोग्राम कंपाइल क्यों हो जाता है?
फ़ील्ड की कोवेरिएंस कंपाइलर को छोटी लाइफटाइम वाले संदर्भ के असाइनमेंट को स्वीकार करने देती है, और फिर Drop इम्प्लीमेंटेशन उस संदर्भ को 'static कंटेनर में संग्रहीत कर देता है, जिससे उसका उपयोग स्थानीय चर के जीवनकाल से आगे बढ़ जाता है। यह एक soundness बग है, इसलिए कंपाइलर को इस कोड को अस्वीकार करना चाहिए।
🇮🇩 Bahasa Indonesia
Penghindaran Masa Hidup Struct Rust melalui Penugasan Kovarian
Sebuah laporan di Hacker News menjelaskan bug soundness di Rust, yakni syarat masa hidup (lifetime) sebuah struct dapat dilewati melalui penugasan field kovarian. Pelapor membagikan program kecil yang menggunakan tipe pembungkus, Q<T>, yang menyimpan Option<T> dengan implementasi Drop. Implementasi Drop memindahkan nilai yang dikandung ke dalam Mutex<Vec<Box<dyn Any + Send>>> statis, yang seharusnya mustahil untuk nilai yang dipinjam dan tidak hidup selama 'static.
Dalam contoh tersebut, sebuah String lokal ditugaskan secara referensi ke dalam pembungkus di dalam scope bagian dalam, sehingga peminjaman seharusnya berakhir ketika scope itu ditutup. Karena field pembungkus bersifat kovarian, compiler menerima penugasan tersebut, dan referensi kemudian dapat disimpan melampaui masa hidup variabel lokal. Program tersebut terkompilasi dan mencetak isi memori yang tidak relevan, alih-alih ditolak.
Ketika dijalankan di bawah AddressSanitizer, program mengalami crash dengan galat stack-use-after-scope, yang memastikan bahwa referensi menggantung benar-benar didereferensi. Menurut laporan tersebut, masalah ini dapat direproduksi pada build nightly terkini dan pada rilis stabil hingga setidaknya 1.85.0. Unggahan itu disertai tautan ke contoh Compiler Explorer dengan dan tanpa sanitizer.
Temuan ini menunjukkan adanya celah soundness dalam cara variance berinteraksi dengan penyimpanan berbasis Drop. Masalah semacam ini biasanya diprioritaskan tinggi oleh para maintainer Rust, karena kode aman seharusnya tidak pernah menghasilkan perilaku tidak terdefinisi.
Mengapa program Rust ini tetap terkompilasi meskipun ada referensi menggantung?
Kovarians field memungkinkan compiler menerima penugasan referensi dengan masa hidup yang lebih pendek. Implementasi Drop kemudian menyimpan referensi tersebut dalam wadah 'static, sehingga penggunaannya diperpanjang melampaui masa hidup variabel lokal. Ini adalah bug soundness; seharusnya compiler menolak kode tersebut.
🇯🇵 日本語
共変な代入による Rust 構造体のライフタイム回避
Hacker News の報告は、Rust における健全性(soundness)のバグを取り上げています。共変なフィールド代入によって、構造体のライフタイム要件を回避できるというものです。報告者は、Drop 実装を持つ Option<T> を内包するラッパー型 Q<T> を用いた小さなプログラムを共有しています。この Drop 実装は、格納された値を静的な Mutex<Vec<Box<dyn Any + Send>>> に移動させますが、'static で生存しない借用値に対してはこれは不可能なはずです。
この例では、ローカルの String が内側のスコープ内でラッパーに参照によって代入されています。そのため、借用はそのスコープが閉じると終了するはずです。ところが、ラッパーのフィールドが共変であるため、コンパイラはこの代入を受け入れ、参照はローカル変数の寿命を超えて保存されてしまいます。プログラムは拒否されることなくコンパイルされ、無関係なメモリの内容を出力します。
AddressSanitizer の下で実行すると、プログラムは stack-use-after-scope エラーでクラッシュします。これは、ダングリング参照が実際に参照外しされていることを裏付けるものです。報告によれば、この問題は現在の nightly ビルドや、少なくとも 1.85.0 までの安定版リリースで再現できるとのことです。投稿には、サニタイザーあり・なしの Compiler Explorer の例へのリンクが添えられています。
この発見は、変位(variance)が Drop ベースのストレージとどのように相互作用するかに健全性の穴があることを示しています。安全なコードが未定義動作を引き起こすことは決してあってはならないため、Rust のメンテナーはこの種の問題を高い優先度で扱います。
よくある質問:ダングリング参照があるのに、なぜこの Rust プログラムはコンパイルできるのですか?
フィールドの共変性により、コンパイラは寿命の短い参照の代入を受け入れます。その後、Drop 実装がその参照を 'static なコンテナに格納するため、ローカル変数の寿命を超えて使用されてしまいます。これは健全性のバグであり、本来コンパイラはこのコードを拒否すべきです。
ダングリング参照があるのに、なぜこの Rust プログラムはコンパイルできるのですか?
フィールドの共変性により、コンパイラは寿命の短い参照の代入を受け入れます。その後、Drop 実装がその参照を 'static なコンテナに格納するため、ローカル変数の寿命を超えて使用されてしまいます。これは健全性のバグであり、本来コンパイラはこのコードを拒否すべきです。
🇧🇷 Português
Contorno do tempo de vida de uma struct em Rust por atribuição covariante
Um relato no Hacker News descreve uma falha de solidez (soundness) em Rust, na qual os requisitos de tempo de vida de uma struct podem ser contornados por meio da atribuição covariante de campos. O autor compartilha um pequeno programa que usa um tipo invólucro, Q<T>, contendo um Option<T> com uma implementação de Drop. A implementação de Drop move o valor contido para um Mutex<Vec<Box<dyn Any + Send>>> estático, o que deveria ser impossível para um valor emprestado que não vive por 'static.
No exemplo, uma String local é atribuída por referência ao invólucro dentro de um escopo interno, de modo que o empréstimo deveria terminar quando esse escopo se fecha. Como o campo do invólucro é covariante, o compilador aceita a atribuição, e a referência pode então ser armazenada além do tempo de vida da variável local. O programa compila e imprime conteúdos de memória não relacionados, em vez de ser rejeitado.
Ao ser executado sob o AddressSanitizer, o programa falha com um erro stack-use-after-scope, o que confirma que a referência pendente é de fato desreferenciada. Segundo o relato, o problema é reproduzível nas compilações nightly atuais e em versões estáveis desde pelo menos a 1.85.0. A publicação inclui links para exemplos no Compiler Explorer com e sem sanitizadores.
A descoberta aponta para uma brecha de solidez na forma como a variância interage com o armazenamento baseado em Drop, o tipo de problema que os mantenedores de Rust tratam como de alta prioridade, pois código seguro nunca deve produzir comportamento indefinido.
Perguntas frequentes: por que este programa em Rust compila apesar da referência pendente?
A covariância do campo permite que o compilador aceite a atribuição de uma referência de vida mais curta, e a implementação de Drop então armazena essa referência em um contêiner 'static, estendendo seu uso além do tempo de vida da variável local. Trata-se de um bug de solidez; o compilador deveria rejeitar o código.
Por que este programa em Rust compila apesar da referência pendente?
A covariância do campo permite que o compilador aceite a atribuição de uma referência de vida mais curta, e a implementação de Drop então armazena essa referência em um contêiner 'static, estendendo seu uso além do tempo de vida da variável local. Trata-se de um bug de solidez; o compilador deveria rejeitar o código.
🇷🇺 Русский
Обход времени жизни структуры Rust через ковариантное присваивание
В отчёте на Hacker News описана ошибка корректности (soundness) в Rust, при которой требования к времени жизни структуры можно обойти с помощью ковариантного присваивания полей. Автор делится небольшой программой, в которой используется тип-обёртка Q<T>, содержащий Option<T> и реализацию Drop. Реализация Drop перемещает содержимое в статический Mutex<Vec<Box<dyn Any + Send>>>, что должно быть невозможно для заимствованного значения, не живущего в течение 'static.
В примере локальная String присваивается обёртке по ссылке внутри внутренней области видимости, поэтому заимствование должно закончиться при выходе из этой области. Поскольку поле обёртки ковариантно, компилятор принимает присваивание, и ссылка затем может храниться дольше времени жизни локальной переменной. Программа компилируется и выводит непричастное содержимое памяти вместо того, чтобы быть отклонённой.
При запуске под AddressSanitizer программа аварийно завершается с ошибкой stack-use-after-scope, что подтверждает фактическое разыменование висячей ссылки. По сообщению, проблема воспроизводится в текущих nightly-сборках и в стабильных выпусках начиная как минимум с 1.85.0. К публикации приложены ссылки на примеры в Compiler Explorer с санитайзерами и без них.
Находка указывает на брешь в корректности в том, как вариантность взаимодействует с хранением на основе Drop. Такие проблемы сопровождающие Rust считают приоритетными, поскольку безопасный код никогда не должен приводить к неопределённому поведению.
Часто задаваемый вопрос: почему эта программа на Rust компилируется, несмотря на висячую ссылку?
Ковариантность поля позволяет компилятору принять присваивание ссылки с более коротким временем жизни, а затем реализация Drop сохраняет эту ссылку в контейнере 'static, продлевая её использование за пределы времени жизни локальной переменной. Это ошибка корректности: компилятор должен был отклонить код.
Почему эта программа на Rust компилируется, несмотря на висячую ссылку?
Ковариантность поля позволяет компилятору принять присваивание ссылки с более коротким временем жизни, а затем реализация Drop сохраняет эту ссылку в контейнере 'static, продлевая её использование за пределы времени жизни локальной переменной. Это ошибка корректности: компилятор должен был отклонить код.
🇨🇳 简体中文
通过协变赋值绕过 Rust 结构体生命周期
Hacker News 上的一份报告描述了 Rust 中的一个健全性漏洞:通过协变字段赋值,结构体的生命周期要求可以被绕过。报告者分享了一个小程序,其中使用了一个包装类型 Q<T>,它持有一个 Option<T>,并实现了 Drop。Drop 实现会将所包含的值移入一个静态的 Mutex<Vec<Box<dyn Any + Send>>> 中,而对于不具有 'static 生命周期的借用值来说,这本应是不可能的。
在示例中,一个局部 String 在内部作用域中通过引用被赋值给该包装器,因此借用本应在该作用域结束时终止。由于包装器的字段是协变的,编译器接受了这一赋值,于是该引用可以被存储到局部变量的生命周期之外。该程序能够通过编译,并打印出无关的内存内容,而不是被编译器拒绝。
在 AddressSanitizer 下运行时,程序会因 stack-use-after-scope 错误而崩溃,这证实了悬垂引用确实被解引用了。据报告,该问题在当前的 nightly 版本以及至少可追溯到 1.85.0 的稳定版本中都能复现。帖子中附有带和不带 sanitizer 的 Compiler Explorer 示例链接。
这一发现表明,在变型与基于 Drop 的存储相互作用的方式上存在健全性漏洞。Rust 维护者通常将此类问题视为高优先级,因为安全代码永远不应产生未定义行为。
常见问题:为什么这个 Rust 程序尽管存在悬垂引用仍能通过编译?
由于字段的协变性,编译器接受了对生命周期较短的引用的赋值,随后 Drop 实现将该引用存入一个 'static 容器中,使其使用范围超出了局部变量的生命周期。这是一个健全性漏洞,编译器本应拒绝此代码。
为什么这个 Rust 程序尽管存在悬垂引用仍能通过编译?
由于字段的协变性,编译器接受了对生命周期较短的引用的赋值,随后 Drop 实现将该引用存入一个 'static 容器中,使其使用范围超出了局部变量的生命周期。这是一个健全性漏洞,编译器本应拒绝此代码。