Rust؛ کشف bug‌های memory safety در std و core

چرا مهم است

اگر با Rust کار می‌کنی، این گزارش مرز تضمین‌های آن را روشن می‌کند: safe بودن کد تو به بخش‌های زیرین هم وابسته است. نمونه‌های پژوهشگران نشان می‌دهند خطا در همین بخش‌ها می‌تواند از کد کاملاً safe فعال شود.

قبل از خواندن، این‌ها را بدان۴ مفهوم
soundness

ویژگی‌ای که تضمین می‌کند استفاده از یک API در کد safe،؜ حتی با رفتار غیرمنتظره فراخواننده، به undefined behavior منجر نمی‌شود.

در حال نگارش: soundness

panic safety

حفظ شرایط لازم برای ایمنی کد حتی وقتی panic اجرای عادی یک عملیات را نیمه‌کاره متوقف می‌کند.

در حال نگارش: panic safety

undefined behavior

وضعیتی که در آن قواعد زبان دیگر تضمینی درباره رفتار برنامه نمی‌دهند؛ مانند دسترسی خارج از محدوده حافظه.

در حال نگارش: undefined behavior

invariant

شرطی درباره وضعیت داده یا برنامه که کد برای درست و ایمن کارکردن به برقرار بودن آن تکیه می‌کند.

در حال نگارش: invariant

Microsoft FORGE Lab چند مشکل memory safety در standard library زبان Rust،؜ شامل std و core،؜ شناسایی کرده است. در بعضی موارد، کدی که کاملاً با safe Rust نوشته شده نیز می‌تواند undefined behavior ایجاد کند. بخشی از این bug‌ها با کمک Hyenas،؜ نسخه پژوهشی سامانه شناسایی آسیب‌پذیری مبتنی بر AI با نام codename MDASH،؜ پیدا شده‌اند. پژوهشگران کد را بررسی کرده، bug‌ها را بازتولید کرده و برای گزارش و رفع آن‌ها با نگه‌دارندگان Rust همکاری کرده‌اند.

تمرکز این گزارش روی soundness در بخش‌های unsafe از standard library است، نه bug‌های compiler. بسیاری از امکانات پایه، از جمله Vec و String،؜ پشت API‌های safe خود از کد unsafe استفاده می‌کنند. مسئولیت این کد آن است که هیچ استفاده‌ای از API در کد safe نتواند به undefined behavior برسد؛ حتی اگر رفتار کد فراخواننده با انتظار نویسنده متفاوت باشد.

پژوهشگران سه الگوی تکرارشونده را مطرح می‌کنند: اعتماد به اینکه کد panic نمی‌کند، اعتماد به درست‌بودن پیاده‌سازی‌های ارائه‌شده توسط کاربر، و اعتماد به سیستم زیرین. برای نشان‌دادن مشکل دوم، تابعی را مثال می‌زنند که یک بار با as_ref طول یک slice را بررسی می‌کند و بار دیگر برای دسترسی بدون بررسی محدوده، همان متد را فراخوانی می‌کند. یک پیاده‌سازی safe می‌تواند بار اول slice بلند و بار دوم slice خالی برگرداند؛ بنابراین بررسی اول، دسترسی دوم را ایمن نمی‌کند.

نمونه مفصل گزارش، یک مشکل panic safety در MapWindows::clone است. API با نام Iterator::map_windows که nightly است، پنجره‌های هم‌پوشان از عناصر می‌سازد. هنگام clone،؜ یک buffer پیش از مقداردهی عناصر ساخته می‌شد، در حالی که destructor آن آماده اجرا بود. اگر فراخوانی T::clone متعلق به کاربر panic می‌کرد، فرایند unwinding باعث می‌شد destructor با مقادیر مقداردهی‌نشده کار کند. نمونه بازتولید پژوهشگران فقط از کد safe استفاده می‌کند و به crash می‌رسد.

اصلاح این مورد، buffer جدید را تا پایان مقداردهی داخل ManuallyDrop نگه می‌دارد. در نتیجه، هنگام unwinding،؜ buffer نیمه‌ساخته به‌جای اجرای destructor رها می‌شود. درس عمومی گزارش این است که فراخوانی کد خارج از کنترل، از clone و مقایسه گرفته تا متدهای iterator،؜ ممکن است خروج زودهنگام ایجاد کند. کد unsafe باید پیش از شکستن invariant کارهای مستعد panic را انجام دهد، یا وضعیتی بسازد که خروج زودهنگام از آن ایمن باشد.

نکته کلیدی

وقتی کد unsafe می‌نویسی، فرض نکن فراخوانی کد دیگران همیشه تا انتها اجرا می‌شود. برای حفظ ،؜ پیش از تغییر وضعیت داخلی کارهای مستعد panic را انجام بده، یا خروج زودهنگام را هم ایمن نگه دار.

خواندن مقاله اصلی (در زبانه‌ی جدید باز می‌شود)
برچسب‌ها (۳)