الزامات کدنویسی ایمن از نظر حافظه، شیوه نوشتن نرمافزارهای سیستمی در گوگل، مایکروسافت و کرنل لینوکس را تغییر میدهد

در فوریه ۲۰۲۴، دفتر مدیر ملی سایبری کاخ سفید گزارشی با عنوانی ساده و استدلالی صریح منتشر کرد: حدود ۷۰ درصد از آسیبپذیریهایی که مایکروسافت و گوگل هر سال رفع میکنند، ریشه در باگهای مربوط به ایمنی حافظه دارند — سرریز بافر (buffer overflow)، استفاده پس از آزادسازی حافظه (use-after-free)، و خوانش خارج از محدوده — یعنی دقیقاً همان دسته از خطاهایی که زبانهای C و C++ نوشتنشان را آسان و شناساییشان را دشوار میکنند. دو سال بعد، این استدلال دیگر صرفاً یک نکته گفتاری نیست، بلکه به بخشی از نحوه برنامهریزی بزرگترین شرکتهای نرمافزاری برای پروژههای جدید تبدیل شده است.
گزارشهای شفافیت خود گوگل درباره اندروید، واضحترین شاهد است که این موضوع صرفاً حرف نیست. در سال ۲۰۱۹، باگهای ایمنی حافظه حدود ۷۶ درصد از آسیبپذیریهای جدی اندروید را تشکیل میدادند. طبق آخرین گزارشها، این سهم به اوایل دهه بیست درصد کاهش یافته است — نه به این دلیل که کدهای قدیمی C و C++ بازنویسی شدهاند، بلکه به این دلیل که سیاست تغییر کرده است: کدهای جدیدی که از سال ۲۰۱۹ به اندروید اضافه شدهاند، بهطور پیشفرض با زبانهای ایمن از نظر حافظه — بیشتر Rust و Kotlin — نوشته میشوند و نرخ آسیبپذیری در این کدهای جدید تقریباً به صفر رسیده است. نتیجهگیری گوگل، که بهصراحت در وبلاگ امنیتی خودش بیان شده، این است: ریسک یک پایگاه کد متناسب با میزان کد جدید ناامنی است که همچنان مینویسید، نه با اندازه کل پایگاه کد.
بازنویسیها واقعاً کجا در جریان است
کرنل لینوکس مهمترین نمونه آزمایشی است، زیرا باگهای کرنل پرارزشترین هدف در این صنعتاند — یک نقص ایمنی حافظه در یک درایور میتواند به فرار از sandbox یا نفوذ کامل به سیستم تبدیل شود. پشتیبانی از Rust در اواخر ۲۰۲۲ به شاخه اصلی کرنل اضافه شد و تا سال ۲۰۲۶ دیگر یک لایه آزمایشی نیست. اکنون درایورهای تولیدی Rust برای کنترلرهای ذخیرهسازی NVMe، برای مکانیزم Binder IPC اندروید — یکی از پرحملهترین سطوح کرنل در هر گوشی اندرویدی — و برای GPUهای اپل سیلیکون از طریق درایور AGX پروژه Asahi Linux وجود دارد. هیچیک از اینها بهطور کامل جایگزین کدهای C موجود نشدهاند؛ بلکه زیرسیستمهای جدید یا بازنویسیهای اجزای خاصی هستند که به دلیل قرار داشتن مستقیم در مسیر ورودیهای نامطمئن انتخاب شدهاند.
مایکروسافت در پیامرسانی عمومی ساکتتر بوده، اما به همان اندازه در داخل شرکت فعال است. مارک روسینویچ، مدیر فناوری Azure، بهطور علنی گفته است که پروژههای سیستمی جدید در مایکروسافت بهطور پیشفرض با Rust نوشته میشوند، مگر اینکه دلیل مشخصی برای خلاف آن وجود داشته باشد؛ و بخشهایی از پشته رندر متن و فونت ویندوز — که از نظر تاریخی منبعی غنی برای باگهای اجرای کد از راه دور بوده، چون فونتها ورودیهای نامطمئن و قابلکنترل توسط مهاجماند که عمیقاً درون کرنل پردازش میشوند — با نام DWriteCore با Rust بازنویسی شدهاند. تجزیه فونت یک مثال کلاسیک برای اهمیت این موضوع است: دقیقاً همان نوع کدی است که بایتهای ارسالی مهاجم را میگیرد و روی آنها محاسبات اشارهگر (pointer arithmetic) انجام میدهد.
Chrome رویکردی محدودتر اما همچنان قابلتوجه را در پیش گرفته است. بهجای یک بازنویسی کامل، گوگل Rust را به تجزیهگرهای پرریسک خاصی مانند کدکهای تصویر، JSON، و بخشی از مدیریت پروتکلهای شبکه وارد کرده، در حالی که بخش اصلی C++ کرومیوم را حفظ کرده و با تکنیکهایی مانند MiraclePtr آن را تقویت کرده است — تکنیکی که باگهای use-after-free را به کرشهای ایمن تبدیل میکند، نه به فساد حافظه قابلسوءاستفاده. این مسیر میانی واقعگرایانه برای یک پایگاه کد سیمیلیون خطی است: تجزیهگرهای جدید با Rust، کدهای قدیمی تقویتشده بهجای بازنویسی.
چرا C و C++ ناپدید نمیشوند
هیچکدام از اینها به معنای پایان C و C++ نیست. اقتصاد یک بازنویسی کامل منطقی نیست: دههها کد آزمودهشده و تنظیمشده از نظر کارایی، استخرهای عظیم استخدامی از مهندسان C/C++، و زنجیرهابزارهایی که حول این زبانها برای همهچیز از فرمور تعبیهشده تا موتورهای بازی ساخته شدهاند. آنچه تغییر کرده، محدودتر و پایدارتر از یک جنگ زبانی است — این یک سیاست درباره کجا کد جدید نوشته میشود است که بهطور انتخابی برای اجزایی اعمال میشود که مستقیماً با ورودیهای نامطمئن روبرو هستند: تجزیهگرها، درایورها، پشتههای شبکه، و هر چیزی که با یک جریان بایت از خارج مرز اعتماد در تماس است.
ریسک جدیدی که این ایجاد میکند، در نقاط اتصال است. هر جزء Rust که درون یک پایگاه کد بزرگتر C/C++ جای میگیرد، به یک مرز FFI (رابط فراخوانی خارجی) نیاز دارد، و مرزهای FFI دقیقاً جایی هستند که تضمینهای ایمنی Rust دیگر اعمال نمیشوند — یک بلوک unsafe در سمت Rust، یا یک فراخوانکننده C که یک اشارهگر نادرست ارسال میکند، دقیقاً همان دسته باگی را که بازنویسی قرار بود حذف کند، دوباره وارد میکند. تیمهای امنیتی در هر دو شرکت گوگل و مایکروسافت، بررسی مرز FFI را مشکل سخت بعدی دانستهاند، نه مشکلی حلشده.
اگر کرنل تولید نمیکنید، این موضوع چه معنایی دارد
بیشتر تیمهای مهندسی، یک سیستمعامل یا مرورگر نگهداری نمیکنند، اما درس زیربنایی مستقیماً قابل تعمیم است:
- کد جدیدی که ورودیهای نامطمئن را تجزیه میکند، جایی است که این سیاست باید ابتدا اعمال شود. اگر در سال ۲۰۲۶ یک تجزیهگر، deserializer، یا پردازشگر پروتکل جدید مینویسید، انتخاب پیشفرض Rust، Go، یا یک زبان دیگر ایمن از نظر حافظه، فروختن این ایده به یک بازبین امنیتی بسیار آسانتر از پنج سال پیش است — ابزارها، اکوسیستم کتابخانهها، و استخر استخدام همگی بالغتر شدهاند.
- کدهای قدیمی C/C++ که نمیتوانید بازنویسی کنید، باید پوشش sanitizer و fuzzing بگیرند، نه یک پروژه بازنویسی. AddressSanitizer، MemorySanitizer، و fuzzing مستمر (libFuzzer، OSS-Fuzz) همان دسته باگهایی را میگیرند که یک بازنویسی حذف میکرد، با کسری از هزینه مهندسی، و همین امروز روی کدی که قصد لمس آن را ندارید کار میکنند.
- اگر نرمافزار به دولت فدرال آمریکا میفروشید، وضعیت تعهدنامه Secure by Design سازمان CISA را بررسی کنید. بیش از ۳۰۰ شرکت آن را امضا کردهاند، و این تعهدنامه شامل تعهد به انتشار یک نقشهراه ایمنی حافظه است — نه رعایت کامل از روز اول، بلکه یک برنامه عمومی با یک بازه زمانی. اگر شرکت شما امضاکننده است و این نقشهراه را منتشر نکرده، این خلافی است که یک حسابرس یا تیم امنیتی مشتری دیر یا زود درباره آن خواهد پرسید.
- هر مرز FFI جدید را بهعنوان یک مستند امنیتی قابلبازبینی در نظر بگیرید، نه یک جزئیات اجرایی. اگر یک ماژول Rust و یک ماژول C با هم در ارتباطاند، این مرز شایسته همان دقتی است که یک تجزیهگر جدید دریافت میکند، چون جایی است که تضمینهای ایمنی واقعاً پایان مییابند.