چکیده – تکنیکهای آزمون فازی (Fuzz Testing) یا همان فازینگ به دلیل توانایی روزافزون خود در تولید موارد آزمون (Test Cases) منجر به ایجاد کرش (Crash) در برنامهها، بهطور گستردهای مورد استفاده قرار گرفتهاند. بااینحال، نقضهای ایمنی حافظه (Memory Safety Violations) میتوانند به خرابیهای خاموش (Silent Corruptions) و خطاها منجر شوند و یک فازر (Fuzzer) ممکن است تنها در صورت وجود سازوکارهای پاکسازی یا همان سنیتایز (Sanitization Machinery) قادر به شناسایی آنها باشد. در مورد نرمافزارهای با کد منبع بسته (Closed-Source Software)، ترکیب پاکسازی با فازینگ با موانع عملی همراه است که ما تلاش میکنیم با ارائهای مستقل از معماری (Architecture-Independent) به نام QASan برای تشخیص نقضهای حافظه Heap، بر آنها غلبه کنیم. در آزمایشهای ما، QASan در مقایسه با پاکسازهای مستقل (Standalone Sanitizers) عملکرد رقابتی ارائه میدهد و در عین حال، با اعمال میانگین کاهش سرعت متوسط 1.61 برابر بر فازر ++AFL، امکان آشکارسازی تعداد بیشتری از باگهای مرتبط با Heap را برای آن فراهم میکند.
۱. مقدمه (Introduction)
ایمنی حافظه (Memory Safety) یکی از مطلوبترین ویژگیها برای برنامههای نرمافزاری است؛ زیرا نقض این ویژگی میتواند به خطاهایی منجر شود که تحلیل آنها دشوار است، نتایج ناسازگار ایجاد میکند، باعث خرابیهای خاموش حافظه (Silent Memory Corruptions) میشود و در نهایت، آسیبپذیریهای امنیتی به وجود میآورد [1]، [2]. طراحان زبانهای برنامهنویسی و معماران زمان اجرا (Runtime Architects) طی سالها رویکردهای متفاوتی را برای دستیابی به این هدف ارائه کردهاند که از جمله آنها میتوان به اثباتکنندههای خودکار ایمنی (Automatic Safety Provers) [3]، جمعآوریکنندههای زباله محافظهکارانه (Conservative Garbage Collectors) [4]، سامانههای تبدیل (Transformation Systems) برای گویشهای ایمن (Safe Dialects) [5] و در نهایت، بررسیهای زمان اجرا (Runtime Checks) برای زبانهایی که در محیطهای مدیریتشده (Managed Environments) مانند Java اجرا میشوند [6] اشاره کرد.
بااینحال، زبانهای فاقد ایمنی حافظه (Memory-Unsafe Languages) مانند C و ++C برای بسیاری از وظایف، از جمله برنامهنویسی سیستمی (System Programming) و سناریوهای حساس به کارایی (Performance-Sensitive Scenarios)، همچنان ضروری هستند. ازاینرو، توسعه تکنیکها و ابزارهایی برای شناسایی نقضهای ایمنی (Safety Violations) در برنامهها، پیش از استفاده از آنها در محیط تولید (Production)، همچنان یک نیاز جدی و اساسی به شمار میرود.
از دیدگاه امنیتی، اکسپلویتهای ناشی از خرابی حافظه (Memory Corruption Exploits) به مرور زمان پیچیدهتر شدهاند؛ یک مهاجم میتواند از نقضهای ایمنی برای اجرای حملاتی مانند ربایش جریان کنترل (Control Flow Hijacking)، ارتقای سطح دسترسی (Privilege Escalation) و نشت اطلاعات (Information Leakage) استفاده کند [7]. اگرچه سازوکارهای دفاعی رایج در سامانهها، مانند تصادفیسازی چیدمان فضای آدرس (Address Space Layout Randomization)، کار را برای مهاجمان دشوارتر کرده است، اما ماهیت واکنشی (Reactive Nature) این سازوکارها باعث میشود که در حالت کلی نتوانند از چنین حملاتی جلوگیری کنند.
امروزه توسعهدهندگان نرمافزار میتوانند از راهکارهای چندوجهی (Multi-Faceted Solutions) برای آزمون برنامهها بهره ببرند؛ از یک سو، چارچوبهای در دسترس عمومی برای تکنیکهای ایستا (Static Techniques)، مانند اجرای نمادین (Symbolic Execution) [8]، [9]، تفسیر انتزاعی (Abstract Interpretation) [10] و بررسی مدل کراندار (Bounded Model Checking) [11]، و از سوی دیگر، برای رویکردهای پویا (Dynamic Approaches)، مانند فازینگ (Fuzzing) [12] و پاکسازی (Sanitization) [7] در دسترس هستند. بهویژه، طی چند سال اخیر، تکنیکهای آزمون فازی (Fuzz Testing) به دلیل توانایی آنها در تولید کارآمد موارد آزمون منجر به کرش (Crashing Test Cases) برای برنامهها، توجه بسیاری را به خود جلب کردهاند [13]. بااینحال، همه نقضهای ایمنی حافظه (Memory Safety Violations) به خرابی فوری (Immediate Crash) منجر نمیشوند [14]؛ برای مثال، دسترسی به بایتهای پدینگ (Padding Bytes) که با هدف همترازی (Alignment) در حافظه قرار داده شدهاند، میتواند از این دسته باشد.
امروزه، بهترین رویهها (Best Practices) اغلب فازینگ را با پاکسازی ترکیب میکنند [15]. ابزارهای پاکسازی (Sanitization Tools)، یا بهطور رایجتر، پاکسازها (Sanitizers)، میتوانند هنگام وقوع رفتار نادرست (Incorrect Behavior)، آن را مستقیماً برای دستههای مشخصی از نقضها مشاهده کنند. پاکسازها معمولاً با افزودن تغییراتی به نمایش برنامه (Program Representation) عمل میکنند تا سازوکارهای هشداردهندهای (Tripwires) را در برنامه قرار دهند که نقض سیاستهای از پیش تعریفشده را آشکار میکنند. ابزارگذاری (Instrumentation) کد منبع برای پاکسازی معمولاً سربار عملکردی (Performance Overhead) محدودی ایجاد میکند و توسعهدهندگان میتوانند آن را بهصورت طبیعی با تکنیکهای فازینگ ترکیب کنند. متأسفانه، این ترکیب در مورد کتابخانهها و برنامههای با کد منبع بسته (Closed-Source) امکانپذیر نیست؛ نرمافزارهایی که بخش قابلتوجهی از چشمانداز نرمافزاری امروزی را تشکیل میدهند [15].
ابزارهای پاکسازی (Sanitization Tools) موجود برای برنامههای باینری (Binary Programs) معمولاً بر چارچوبهای ترجمه پویای باینری (Dynamic Binary Translation Frameworks) متکی هستند [16]. دو عامل عملی، ترکیب این ابزارها با راهکارهای فازینگ باینری (Binary Fuzzing Solutions) را با مشکل مواجه میکنند: ماهیت مستقل آنها (Standalone Nature) و سربار بالای ناشی از فناوری ترجمه زیربنایی (Underlying Translation Technology).
در سالهای اخیر، پژوهشگران نمونههای اولیهای از راهکارهای ابزارگذاری ایستا (Static Instrumentation Solutions) ارائه کردهاند که با ایجاد تغییر در فایلهای باینری، کاوشگرها (Probes) را بهگونهای در آنها قرار میدهند که گویی این کاوشگرها در زمان کامپایل به برنامه افزوده شدهاند [15]. اگرچه سامانههای بازنویسی (Rewriting Systems) بهطور قابلتوجهی بهبود یافتهاند، اما همچنان کامل نیستند؛ زیرا به ویژگیهای ساختاری کد (Structural Properties of the Code) متکی هستند. برای مثال، در حال حاضر پلتفرمهای ۳۲ بیتی خارج از محدوده قابلیتهای این سامانهها قرار دارند، در حالی که این پلتفرمها همچنان در مواردی مانند دستگاههای اینترنت اشیا (IoT Devices) کاربرد دارند [7].
پیشنهاد ما. در این مقاله، با ارائه یک طراحی برای پاکسازی (Sanitization) که بهصورت طبیعی مکمل راهکارهای موجود فازینگ است، به شکاف عملی میان فازینگ باینری و پاکسازی میپردازیم. پیادهسازی QASan ما میتواند قابلیتهای ASan (Address Sanitizer [17]) را در تشخیص نقضهای حافظه Heap شبیهسازی کند؛ ASan بر اساس پژوهشهای اخیر [7]، با اختلاف زیادی پرکاربردترین پاکساز مورد استفاده امروزی است.
به منظور تسهیل یکپارچهسازی QASan با راهکارهای موجود فازینگ (Fuzzing)، این ابزار بر پایه چارچوب ترجمه QEMU (QEMU Translation Framework) توسعه یافته است. در طراحی این سامانه، فرادادههای مرتبط با حافظه (Memory-Related Metadata) را بهگونهای مدیریت میکنیم که با معماریهای ناهمگون (Heterogeneous Architectures) سازگار باشد، از سناریوهای بازاجرا روی باینری (Binary Rehosting) پشتیبانی کند و فشار حافظه (Memory Pressure) واردشده بر برنامه هدفِ تحت تحلیل را کاهش دهد.
این مقاله یک ارزیابی دومحوره (Two-Pronged Evaluation) را شرح میدهد. در بخش نخست، دقت QASan را در تشخیص نقضهای Heap با استفاده از یک مجموعه آزمون شناختهشده (Well-Known Test Suite) اندازهگیری میکنیم. سپس با ارزیابی یکپارچهسازی آن با فازر محبوب AFL++ [18]، کاربردپذیری راهکار پیشنهادی خود را بهصورت سرتاسری (End-to-End Utility) نشان میدهیم. در مجموعهای از برنامههای بهخوبی آزمودهشده (Well-Tested Programs)، QASan باگهای بدون کرش (Non-Crashing Bugs) را آشکار میکند که فازرهای استاندارد آنها را از دست میدهند. هنگامی که QASan در روند معمول اجرای یک فازر ادغام میشود، سربار عملکردی (Performance Overhead) معادل 1.26 تا 2.74 برابر برای طرح پاکسازی (Sanitization Scheme) خود اندازهگیری میکنیم؛ سرباری که میتواند در سناریوهای آزمون پویا (Dynamic Testing Scenarios) قابلتوجه و کاربردی باشد.
ما به منظور تسهیل پژوهشهای بیشتر در این حوزه، QASan را بهصورت متنباز (Open Source) در نشانی زیر در اختیار عموم قرار دادهایم: https://github.com/andreafioraldi/qasan
۲. پیشزمینه (Background)
در این بخش، عناصر اصلی سازوکار پاکسازها (Sanitizers) را شرح داده و تمرکز خود را بر نقضهای ایمنی Heap قرار میدهیم. سپس راهکارهای ابزارگذاری (Instrumentation) موجود را برای طراحی ابزارهای پاکسازی و فازینگ که بر روی کد باینری (Binary Code) عمل میکنند، معرفی میکنیم.
۲.۱ پاکسازی حافظه (Memory Sanitization)
یک پژوهش اخیر [7]، پاکسازها را بهعنوان ابزارهای پویای یافتن باگ (Dynamic Bug Finding Tools) معرفی میکند که نتایج دقیقی ارائه میدهند و این نتایج برای نمونه اجرای مشاهده شده (Observed Execution Instance) معتبر هستند. برخلاف سازوکارهای کاهش اثر اکسپلویت (Exploit Mitigations) که سیستمعاملها در محیط تولید (Production) به کار میگیرند، هدف پاکسازها یافتن خطاها پیش از انتشار نرمافزار است.
به همین دلیل، پاکسازها میتوانند منابع بیشتری را برای مکانیابی دقیق یک باگ در اختیار داشته باشند؛ بهجای اینکه صرفاً یک خطای کلی را در زمانی دیرتر شناسایی کنند. همچنین، وجود هشدارهای کاذب (False Alerts) تا زمانی که تعداد آنها قابلکنترل باشد، قابلپذیرش است [7]. پاکسازها برای دستههای مختلفی از باگها، از جمله نقضهای ایمنی حافظه (Memory Safety Violations)، خطاهای نوع (Type Errors) و رفتار تعریفنشده (Undefined Behavior)، در دسترس هستند.
وجه مشترک بسیاری از این راهکارها، ابزارگذاری کد برنامه و/یا پشته نرمافزاری پیرامون آن با توالیهای صریح تشخیص (Explicit Detection Sequences) است؛ توالیهایی که نقض یک سیاست (Policy) را آشکار میکنند؛ نقضی که در غیر این صورت ممکن است بدون شناسایی باقی بماند یا در ادامه باعث خرابیهایی شود که تشخیص علت آنها دشوار است، یا به نتایج نادرست منجر شود [1]. تمرکز این مقاله بر نقضهای ایمنی حافظه (Memory Safety Violations) رخ داده در نواحی Heap است؛ نقضهایی که همانطور که پیشتر اشاره کردیم، در نرمافزارهای دنیای واقعی بسیار رایج هستند و میتوانند زمینه را برای چندین نوع حمله بهرهبرداری (Exploitation Attacks) فراهم کنند.
یک نقض ایمنی زمانی رخ میدهد که یک اشارهگر (Pointer) به ناحیهای غیر از ناحیه متعلق به شیئی (Object) اشاره کند که اشارهگر در ابتدا برای آن تعریف شده است. این نقضها میتوانند مکانی (Spatial) یا زمانی (Temporal) باشند. نقضهای ایمنی مکانی هنگامی رخ میدهند که یک دسترسی، بهطور کامل یا جزئی، خارج از محدوده شیء موردنظر اشارهگر باشد. تخصیص (Allocation) این محدوده را مشخص میکند؛ بنابراین، هرگونه محاسبه بعدی روی اشارهگر باید آن را درون یکی از اشیای موردنظر نگه دارد.
دو رویکرد اصلی برای تشخیص نقضهای مکانی وجود دارد [1]. رویکردهای مبتنی بر شیء (Object-Based Approaches)، اشیای حافظه تخصیصیافته را ردیابی میکنند و بررسی میکنند که آیا ارجاعهای حافظه (Memory Dereferences) بهطور کامل درون یک شیء واحد قرار دارند یا خیر. مرزهای اشیای مجاور را میتوان یا از طریق عملیات جستوجوی محدوده (Range Lookup Operations) از یکدیگر متمایز کرد که البته ممکن است سربار بسیار زیادی ایجاد کنند [1]، یا با تغییر چیدمان اشیا (Object Layout) و اعمال سازوکارهای حفاظتی، که در این حالت با افزایش مصرف حافظه، عملکرد بهتری به دست میآید. برای مثال، برخی پاکسازها (Sanitizers) در اطراف اشیا نواحی از بایتها را بهعنوان Redzone قرار میدهند و این بایتها را در یک نمایش داخلی، که حافظه سایه (Shadow Memory) نامیده میشود، بهعنوان نامعتبر علامتگذاری میکنند؛ سپس از این نمایش برای اعتبارسنجی دسترسیها (Accesses) استفاده میکنند.
رویکردهای مبتنی بر اشارهگر (Pointer-Based Approaches) برای هر اشارهگر، مبدأ (Base) و محدوده (Bounds) آن را ردیابی میکنند و در مقایسه با راهکارهای مبتنی بر شیء (Object-Based Solutions) کاملتر هستند؛ زیرا برای مثال میتوانند ارجاعزدایی (Dereference) از اشارهگرهایی را نیز شناسایی کنند که به حافظه متعلق به شیئی غیر از شیء موردنظر اشاره میکنند. بااینحال، این رویکردها معمولاً با کدهای بدون ابزارگذاری (Uninstrumented Code) سازگار نیستند [7] و به دلیل نیاز به انتقال فراداده (Metadata) در جریان عملیات روی اشارهگرها، سربار بیشتری ایجاد میکنند.
نقضهای ایمنی زمانی (Temporal Safety Violations) زمانی رخ میدهند که یک ارجاع حافظه (Memory Dereference) از طریق اشارهگری انجام شود که دیگر معتبر نیست؛ یعنی شیئی که اشارهگر به آن اشاره میکند، دیگر همان شیئی نیست که هنگام ایجاد اشارهگر وجود داشته است [19]. اشارهگرهای سرگردان (Dangling Pointers) نمونهای رایج از نقضهای زمانی هستند، زیرا در سناریوهای استفاده پس از آزادسازی (Use-After-Free) هم به بروز باگ و هم به ایجاد آسیبپذیریهای امنیتی منجر میشوند [7].
راهکار پیشنهادی ما، پاکساز محبوب ASan را گسترش میدهد تا بتواند بر روی باینریها نیز کار کند و سناریوهای فازینگ را بهبود دهد. ASan برای تشخیص نقضهای مکانی از رویکردی مبتنی بر شیء استفاده میکند. این ابزار با استفاده از ابزارگذاری در زمان کامپایل (Compile-Time Instrumentation)، عملیات حافظه را برای اعتبارسنجی ثبت میکند و با قرار گرفتن میان عملیات تخصیص حافظه (Memory Allocations)، حافظه سایه (Shadow Memory) خود را بهروزرسانی کرده و در اطراف اشیا نواحی Redzone ایجاد میکند. همچنین، ASan با بهتعویق انداختن عملیات تخصیص مجدد حافظه (Memory Reallocation Operations)، از تشخیص صحیح، اما ناقصِ نقضهای زمانی نیز پشتیبانی میکند. در بخشهای 3.2 و 3.3، جزئیات بیشتری از سازوکار داخلی آن را ارائه خواهیم کرد.
۲.۲ ابزارگذاری برای تحلیل باینری (Instrumentation for Binary Analysis)
طراحی چارچوبهای ابزارگذاری (Instrumentation Frameworks) برای پشتیبانی از تحلیل برنامهها بر روی نرمافزارهایی که تنها بهصورت باینری در دسترس هستند، مسئلهای است که در جوامع پژوهشی زبانهای برنامهنویسی، سیستمها و امنیت، بهطور گسترده مورد مطالعه قرار گرفته است [16]. یکی از الزامات رایج در پاکسازها (Sanitizers)، که در سناریوی آزمون فازی (Fuzz Testing) موردنظر QASan نیز وجود دارد، پشتیبانی از درج کاوشگر (Probe Insertion) در برنامه است.
کاوشگرها کلاسهایی از دستورالعملها (Instructions) را که در موضوع مورد تحلیل نقش دارند، پایش و میانجیگری (Mediate) میکنند؛ برای مثال، هنگام تشخیص نقضهای حافظه (Memory Violations)، دسترسیهای حافظه (Memory Accesses) و هنگام ردیابی کاوش مسیر (Path Exploration)، انتقالهای جریان کنترل (Control Flow Transfers) را زیر نظر میگیرند.
به همین منظور، تکنیکهای بازنویسی باینری (Binary Rewriting Techniques) از اوایل دهه ۱۹۹۰ مورد مطالعه قرار گرفتهاند [20]. چارچوبهایی مانند DynInst [21] با استفاده از ترامپولینها (Trampolines)، ابزارگذاری ایستا (Static Instrumentation) را فراهم میکنند؛ به این صورت که دستورالعملهای موردنظر را بازنویسی میکنند تا کد تحلیل (Analysis Code) را فراخوانی کنند. اگرچه این حوزه پژوهشی همچنان در حال پیشرفت است [22]، پشتیبانی از باینریهای تجاریِ آماده عرضه (COTS Binaries) در دنیای واقعی، از لحاظ تاریخی به دلایل مختلفی دشوار و دستنیافتنی بوده است؛ از جمله دیساسمبلی دقیق (Accurate Disassembly) بدون اطلاعات نماد (Symbol Information)، ابهام در اهداف شاخههای غیرمستقیم (Indirect-Branch Targets)، استفاده از کتابخانههای اشتراکی (Shared Libraries) و تولید پویای کد (Dynamic Code Generation).
برخی پژوهشهای دیگر، بازاسمبل (Reassembly) را بررسی کردهاند [23] تا ارجاعهای کدنویسی شده به کد و داده (Hardcoded Code and Data References) در خروجی دیساسمبلرها را به برچسب (Label) تبدیل کنند؛ روشی که دستکاریهای بعدی کد و کامپایل مجدد را بهطور قابلتوجهی سادهتر میکند. اخیراً RetroWrite [15] از بازاسمبل برای افزودن کاوشگرهای پاکسازی (Sanitization Probes) به کد مستقل از موقعیت (Position-Independent Code) در معماری x64 استفاده میکند. اگرچه این رویکردها بسیار کارآمد هستند، اما عمومی نیستند؛ زیرا به ویژگیهای کد کامپایلشده و پلتفرم وابستهاند و تنها از تعداد محدودی از پیکربندیها پشتیبانی میکنند.
رویکرد متفاوت دیگر، ساخت تحلیلها بر پایه سامانههای ترجمه پویای باینری (Dynamic Binary Translation — DBT) است؛ سامانههایی که میتوانند هر دستورالعمل را درست پیش از اجرای برنامه پایش کنند و در صورت نیاز، آن را تغییر دهند [24]. راهکارهای DBT میتوانند تمام مواردی را که پیشتر برای تکنیکهای ایستا دشوار عنوان شد، پوشش دهند؛ هرچند این کار به بهای کاهش سرعت اجرا انجام میشود. همچنین، این سامانهها به یک برنامه غیرمهاجم (Non-Adversarial) همان آدرسها و دادههایی را ارائه میکنند که برنامه در اجرای بومی (Native Execution) خود مشاهده میکرد [16].
در میان چارچوبهای پرکاربرد، Pin [25] و DynamoRIO [26] با دستکاری نسخههای کپیشده از دستورالعملهای اصلی کار میکنند، در حالی که Valgrind [27] و QEMU [28] این دستورالعملها را به یک نمایش میانی قابل کامپایل (Compilable Intermediate Representation) ترجمه میکنند. Valgrind مبنای ابزار محبوب تشخیص خطاهای حافظه memcheck [27] است، در حالی که DynamoRIO زیربنای ابزار Dr. Memory [29] را تشکیل میدهد. با استثنای قابلتوجه WinAFL [30]، هیچیک از این دو چارچوب در حوزه فازینگ به طور گسترده محبوب نشدند.
در مقابل، QEMU به فناوری منتخب بخش بزرگی از پژوهشهای فازینگ باینری تبدیل شده است (برای مثال، [31]–[33]). برخی از دلایل محبوبیت آن عبارتاند از پشتیبانی از پلتفرمهای متعدد با یک طراحی یکپارچه، سادگی مؤلفه مولد کد کوچک (Tiny Code Generator) آن برای درج انواع مختلف ابزارگذاری (Instrumentation)، و بهبودهای عملکردی حاصلشده در حالت شبیهسازی کاربر یا شبیه سازی در سطح کاربر (User Emulation).
۳. QASan
در این بخش، طراحی پیشنهادی خود را ارائه میکنیم. ابتدا یک نمای کلی از آن ارائه میدهیم و سپس به بررسی اجزای اصلی QASan (شکل ۱) میپردازیم. در ادامه نیز نحوه یکپارچهسازی آن با فازرهای مبتنی بر QEMU، محدودیتهای آن و مسیرهای آتی پژوهش را بررسی میکنیم.
۳.۱ نمای کلی (Overview)
QASan از رویکرد مبتنی بر شیء (Object-Based Approach) در ASan پیروی میکند تا قابلیتهای پاکسازی (Sanitization) خود را بر دسترسیهای Heap انجامشده در برنامههای باینری اعمال کند. این ابزار بر پایه شبیهسازی در سطح کاربر (User Emulation) در QEMU ساخته شده است تا عملیات خواندن و نوشتن را در کد تحت تحلیل رهگیری کند؛ کدی که توسط بخشهای اجرایی (Executable Sections) یک برنامه و/یا کتابخانههای موردنظر نمایش داده میشود.
سپس QASan در فضای آدرس (Address Space) برنامه هدف، برای رویدادهای مرتبط با تخصیص و آزادسازی حافظه Heap، میانجیگری (Interposition) انجام میدهد. طراحی این سامانه از دو مؤلفه اصلی تشکیل شده است. مؤلفه نخست، یک افزونه QEMU است که یک حافظه سایه (Shadow Memory) برای Heap برنامه هدف را در فضای آدرس شبیهساز نگهداری میکند و مولد کد کوچک (Tiny Code Generator — TCG) را گسترش میدهد تا برای دسترسیهای حافظه، کاوشگر (Probe) درج کند.
هنگامی که برنامه یک دسترسی حافظه ابزارگذاریشده (Instrumented Memory Access) را اجرا میکند، کد تحلیل (Analysis Code) را که در کد کامپایلشده و شبیهسازیشده بهصورت درونخطی (Inlined) قرار گرفته است، اجرا میکنیم تا اعتبار عملیات را بررسی کنیم یا یک خطا را تشخیص دهیم. در نهایت، این مؤلفه یک پشته سایه (Shadow Stack) نیز نگهداری میکند تا اطلاعات زمینهای (Contextual Information) مربوط به محلهای تخصیص (Allocation Sites) را که بعداً در نقضهای حافظه (Memory Violations) دخیل میشوند، فراهم کند.
مؤلفه دوم، یک کتابخانه زمان اجرا (Runtime Library) است که هنگام آغاز اجرای برنامه هدف، از پیش بارگذاری (Preloaded) میشود. نقش اصلی این کتابخانه، رهگیری عملیات تخصیص حافظه Heap و قرار دادن اشیاء در میان نواحی Redzone برای تشخیص دسترسیهای پاریز (Underflow) و سرریز (Overflow) است. این کتابخانه همچنین برای عملیات آزادسازی (Free)، یک ناحیه قرنطینه (Quarantine) اعمال میکند تا نقضهای زمانی Heap (Temporal Heap Violations) را تشخیص دهد.
پس از آغاز اجرا، کتابخانه زمان اجرا از طریق Hypercall با افزونه QEMU تعامل میکند تا هنگام تخصیص یا آزادسازی حافظه Heap، نمایش سایه (Shadow Representation) را بهروزرسانی کند. همچنین، هنگامی که برنامه هدف برای دستکاری حافظه (Memory Manipulation) توابع متداول کتابخانهای (Commodity Library Functions) را فراخوانی میکند، از هایپروایزرها برای اعتبارسنجی سریعتر استفاده میکند.
۳.۲ افزونه QASan برای QEMU (QASan Extension for QEMU)
وظایف اصلی مؤلفه QEMU در QASan، ابزارگذاری دسترسیهای حافظه (Memory Accesses) و میزبانی نمایش حافظه سایه (Shadow Memory Representation) موردنیاز برای اعتبارسنجی این دسترسیها است.
دسترسیهای حافظه (Memory Accesses). مولد کد کوچک (Tiny Code Generator — TCG) یکی از عناصر محوری در QEMU است؛ زیرا دستورالعملهای معماری باینری هدف را به عملیات شبیهسازی شده TCG مشابه RISC تبدیل میکند و سپس آنها را به دستورالعملهایی کامپایل میکند که بهصورت بومی (Natively) بر روی معماری میزبانِ اجراکننده QEMU اجرا میشوند.
رهگیری دسترسیهای حافظه در سطح TCG نسبتاً ساده و مناسب است. QEMU مؤلفههای ناهمگون (Heterogeneous Primitives) متعلق به معماریهای مختلف را با استفاده از همان عملیات بارگذاری (Load) و ذخیرهسازی (Store) در TCG به نمایش درمیآورد. در نتیجه، کامل بودن ابزارگذاری (Instrumentation Completeness) به سادگی حاصل میشود؛ زیرا دستورالعملهای SIMD و الگوهای نامحدود مشابه rep (Unbounded rep-Like Patterns) نیز با تکرار همین عملیات ترجمه میشوند.
void tcgـgenـqemuـldـi64(TCGvـi64 val, TCGv addr,TCGArg idx, TCGMemOp memop) {
[...]
genـldstـi64 (INDEXـopـqemuـldـi64, val, addr , memop, idx) ;
switch (memop & MOـSIZE ) {
case MO_64: qasanـgenـload8 (addr , idx); break ;
case MO_32: qasanـgenـload4 (addr , idx); break ;
case MO_16: qasanـgenـload2 (addr , idx); break ;
case MO_8: qasanـgenـload1 (addr , idx); break ;
default: qasan gen_load8 (addr , idx );
}
}
گزیده بالا نحوه ابزارگذاری (Instrumentation) یک بارگذاری حافظه (Memory Load) از موتور TCG را نشان میدهد. پس از تولید دستورالعمل، یک ساختار switch اضافه میکنیم تا اندازه عملوند (Operand Size) را تعیین کرده و یار QASan (QASan Helper) متناسب با آن اندازه را فراخوانی کند. این یار بررسی میکند که آیا دستورالعمل به یک ماژول کد (Code Module) تعلق دارد که باید پاکسازی (Sanitization) شود یا خیر، و در صورت لزوم، آن را برای بررسی اعتبار (Validity Checking) ابزارگذاری میکند. مدیریت ذخیرهسازیهای حافظه (Memory Stores) نیز به همین شکل انجام میشود و برای رعایت اختصار از ارائه جزئیات آن صرفنظر شده است.
حافظه سایه (Shadow Memory). طراحی ASan برای تعیین معتبر بودن یک دسترسی خواندن یا نوشتن حافظه، به حافظه سایه متکی است. ASan بهجای آنکه حافظه را با یک نمایش سایه هماندازه با حافظه اصلی بازتاب دهد، تنها از کسری معادل 1/n از آن استفاده میکند. این طراحی بر این مشاهده استوار است که تخصیصهای Heap، نشانیهایی را برمیگردانند که با توان ۲ یعنی 2n بایت همتراز (Aligned) هستند؛ بهطور معمول نیز n ≥ 3 است. به طور مشخص، در تنظیم پیشفرض n = 3، ASan از ۱ بایت برای ذخیره اطلاعات مربوط به قابلدسترس بودن بایتهای یک ناحیه حافظه ۸ بایتی استفاده میکند: یا همه آنها قابلدسترساند، یا تنها k بایت نخست، که در آن k ∈ {1..7} است، قابلدسترساند، یا هیچیک قابلدسترس نیستند.
ASan حافظه سایه را در فضای آدرس برنامه قرار میدهد؛ فضایی که اندازه آن حداکثر max bytes است، و با رزرو کردن یک بخش به اندازه max/8 در یک offset مشخص، حافظه سایه را در آن قرار میدهد. هرگاه ابزارگذاری نیاز داشته باشد اعتبار یک دسترسی به نشانی addr را بررسی کند، میتواند با دسترسی به نشانی offset + (addr >> 3)، ورودی متناظر در حافظه سایه را بهصورت کارآمد بازیابی کند.
در QASan، تصمیم گرفتیم ناحیه حافظه سایه را درون شبیهساز (Emulator) تخصیص دهیم. این رویکرد دو مزیت دارد: نخست، فشار حافظه (Memory Pressure) واردشده بر برنامه تحت تحلیل را کاهش میدهد؛ مسئلهای که برای برنامههای حافظهبر (Memory-Intensive Applications) و معماریهای ۳۲ بیتی یک مشکل شناختهشده است [7]. دوم، میتوانیم برای معماریهایی با حالتهای مختلف آدرسدهی (Addressing Modes)، مانند x86 و MIPS، از یک طرح نمایهگذاری (Indexing Scheme) یکسان استفاده کنیم؛ زیرا در TCG با نشانیهایی کار میکنیم که برای پلتفرم میزبان نرمالسازی (Normalized) شدهاند.
در ذخیرهسازی و بررسی اطلاعات اعتبار (Validity Information)، از رویکرد ASan پیروی میکنیم. یک ورودی صفر نشاندهنده آن است که یک ناحیه ۸ بایتی کاملاً معتبر است، و هنگامی که تنها بخشی از آن معتبر باشد، یک عدد صحیح مثبت تعداد بایتهای معتبرِ ابتدایی را مشخص میکند. از آنجا که دسترسیهای ناایمنِ ناهمتراز (Unaligned Unsafe Accesses) بسیار نادر هستند [17]، برای عملیات بارگذاری و ذخیرهسازی ۸ بایتی، تنها بررسی میکنیم که آیا ورودی متناظر با نشانی موردنظر در حافظه سایه برابر صفر است یا خیر. برای اندازههای کوتاهتر، از یک Bitmask استفاده میکنیم؛ برای مثال، برای یک بارگذاری ۱ بایتی، بررسی زیر را انجام میدهیم:
uintptrـt h = (uintptrـt) addr;
int8ـt* shadowـaddr = (int8ـt*) (h >> 3) + SHADOWـOFFSET;
int8ـt k = *shadow addr;
return k != 0 && (intptrـt ) ((h & 7 ) + 1 ) > k;
اگر مقدار بازگشتی صفر باشد، دسترسی معتبر است؛ این وضعیت زمانی برقرار است که k برابر صفر بوده و یا از سه بیت آخر نشانی بزرگتر باشد [17].
وظایف تکمیلی (Additional Tasks). مؤلفه QEMU در QASan، دو قابلیت دیگر نیز ارائه میدهد که به کتابخانه زمان اجرا (Runtime) در برنامه هدف کمک میکنند. نخستین قابلیت، یک سازوکار فراخوانی هایپروایزر (Hypercall Mechanism) است که به کتابخانه زمان اجرا اجازه میدهد کنترل را به کد تحلیل (Analysis Code) در حال اجرا در شبیهساز منتقل کند. همانطور که در بخش بعد توضیح خواهیم داد، این سازوکار برای بهروزرسانی حافظه سایه (Shadow Memory) هنگام تخصیص یا آزادسازی حافظه توسط برنامه ضروری است.
مشابه بیشتر طرحهای ترجمه پویای باینری (Dynamic Binary Translation — DBT)، طراحی QEMU یک مرز میان نواحی قابلدسترسی برای هدف شبیهسازیشده و نواحی مورد استفاده شبیهساز و کد تحلیل ایجاد میکند. بااینحال، این مرز میتواند، برای مثال هنگام ترجمه فراخوانیهای سیستمی (System Calls)، شکسته شود؛ یعنی زمانی که شبیهساز چنین فراخوانیای را از طرف برنامه هدف اجرا میکند.
ازاینرو، یک شماره فراخوانی سیستمی ساختگی (Fictional System Call Number) معرفی میکنیم تا یک Hypercall برای QASan فعال شود. این سازوکار مستقل از معماری (Architecture-Agnostic) است و به کتابخانه زمان اجرا اجازه میدهد کنترل را واگذار کرده و دادهها را به کد تحلیل منتقل کند؛ برای مثال، اطلاعات مربوط به یک عملیات تخصیص حافظه.
سپس، گونهای کارآمدتر و وابسته به معماری (Architecture-Specific) طراحی میکنیم که کد تحلیل QASan را مستقیماً فعال میکند و با این کار، عملیات switch مربوط به شماره فراخوانی را در مدیریتکننده فراخوانیهای سیستمی QEMU (QEMU System Call Handler) دور میزند. بهطور مشخص، یک کد عملیاتی سفارشی (Custom Opcode) در مجموعه دستورالعملها معرفی میکنیم که ترجمهکننده TCG برای معماری مربوطه (TCG Lifter)، برای مثال در x86، آن را شناسایی کرده و بهصورت کارآمد ترجمه میکند.
قابلیت دوم، یک پشته سایه (Shadow Stack) برای ردیابی زمینه فراخوانی (Calling Context) تخصیصهای Heap است؛ یعنی توالی توابعی که در حال حاضر روی پشته فعال هستند [34]. این اطلاعات برای ردیابی منشأ بلوکهای Heap که بعدها در نقضهای حافظه (Memory Violations) دخیل میشوند، بسیار مهم است.
در حالی که ASan با بازکردن پشته (Stack Unwinding) هنگام وقوع یک رویداد تخصیص، زمینه فراخوانی را تعیین میکند، این راهکار در سناریوی ما قابل استفاده نیست؛ زیرا باینریها اغلب بدون اشارهگرهای قاب پشته (Stack Frame Pointers) مورد استفاده برای بازکردن پشته کامپایل میشوند. ازاینرو، ما دستورالعملهای call و ret را ردیابی میکنیم تا یک پشته سایه را نگهداری کنیم که پشته فراخوانی (Call Stack) برنامه را بازتاب میدهد.
۳.۳ زمان اجرای QASan برای اجرای برنامه هدف (QASan Runtime for Target Execution)
مؤلفه زمان اجرا (Runtime Component) در کنار برنامه هدف فعالیت میکند تا در عملیات دستکاری Heap میانجیگری (Interpose) کند؛ این کار با هدف به روزرسانی حافظه سایه (Shadow Memory) و اعمال سیاستهای قرنطینه (Quarantine Policies) برای تشخیص نقضهای زمانی (Temporal Violations) انجام میشود. همچنین، این مؤلفه در توابع متداول دستکاری حافظه (Commodity Memory Manipulation Functions) نیز میانجیگری میکند تا اعتبارسنجی (Validation) با سرعت و دقت بیشتری انجام شود. برای پیادهسازی زمان اجرا، از یک کتابخانه پیوند شونده پویا (Dynamically Linked Library) استفاده میکنیم که هنگام آغاز اجرای برنامه، آن را از پیش بارگذاری (Preload) میکنیم تا نمادهای توابع (Function Symbols) را هوک (Hook) کنیم.
تخصیص و آزادسازی حافظه (Memory Allocation and Release). مشابه ASan، توابعی مانند malloc، free، realloc، posix_memalign و سایر توابع مورد استفاده برای تخصیص Heap را هوک (Hook) کرده و با پیادهسازیهای تخصصی خود جایگزین میکنیم. میانجیگری (Interposition) برای انجام دو وظیفه ضروری است: نخست، به روز نگهداشتن حافظه سایه؛ و دوم، قرار دادن هر بافر در میان نواحی قرمز یا Redzone پیرامونی، تا هنگام ارجاعزدایی (Dereference) از اشارهگرها، خطاهای پاریز (Underflow) و سرریز (Overflow) شناسایی شوند.
برای یک شیء تازهتخصیصیافته، تنها یک فراخوانی هایپروایزر (Hypercall) صادر میکنیم تا نمایش حافظه سایه (Shadow Memory Representation) را بهروزرسانی کرده و نواحی قرمز (Redzone) پیرامون آن را بهعنوان غیرقابلدسترسی (Inaccessible) علامتگذاری کند. این Hypercall فرادادههایی مانند نشانی و اندازه شیء را مشخص میکند، در حالی که اندازه نواحی Redzone یک پارامتر سراسری قابلتنظیم توسط کاربر است.
یکی از تفاوتهای QASan با ASan این است که ASan فرادادههای تخصیص (Allocation Metadata)، شامل اندازه، شناسه نخ (Thread ID) و زمینه (Context)، را درون نواحی Redzone ذخیره میکند؛ بنابراین، حداقل اندازه این نواحی ۳۲ بایت است [17]. در QASan، این اطلاعات را (با بازیابی زمینه فراخوانی (Calling Context) از پشته سایه) درون شبیهساز و با استفاده از یک درخت بازهای (Interval Tree) نگهداری میکنیم.
این انتخاب به کاربر اجازه میدهد، در صورتی که عملکرد بهتر را ترجیح میدهد، نواحی Redzone کوچکتری انتخاب کند؛ البته با این هزینه که احتمال ایجاد منفی کاذب (False Negatives) افزایش مییابد. همچنین، از دیدگاه طراحی، به حداقل رساندن میزان مداخله (Intrusiveness) در حافظه برنامه هدف میتواند برای گسترش QASan به سمت پاکسازی کل سیستم (Whole-System Sanitization) مفید باشد.
در نهایت، QASan میتواند مانند ASan، نقضهای زمانی استفاده پس از آزادسازی (Use-After-Free) را با استفاده از یک راهبرد قرنطینه (Quarantine Strategy) تشخیص دهد؛ به این صورت که تخصیص مجدد فوری بافرهایی را که توسط برنامه آزاد شدهاند، به تأخیر میاندازد. ناحیه آزادشده را در حافظه سایه مسموم (Poison) میکنیم؛ بنابراین، اگر برنامه کاربردی یک اشارهگر سرگردان (Dangling Pointer) را که در ناحیه مربوط به یک شیء قبلاً آزادشده و هنوز تخصیص مجدد نشده قرار دارد، ارجاعزدایی (Dereference) کند، هوکهای (Hook) مربوط به دسترسیهای حافظه، این نقض را تشخیص خواهند داد.
توابع متداول (Commodity Functions). برنامهها معمولاً برای انجام وظایف متداول مرتبط با دستکاری بایتهای حافظه (Memory Byte Manipulation)، مانند memcpy، و همچنین دستکاری رشتهها (String Manipulation)، مانند strcmp و atoi، از توابع کتابخانه استاندارد استفاده میکنند. ASan برای تعدادی از این توابع در libc از میانجیها (Interceptors) استفاده میکند تا پیش از اجرای آنها، نواحی حافظه درگیر در عملیات را اعتبارسنجی (Validate) کند. در QASan نیز راهبرد مشابهی را دنبال میکنیم؛ همانطور که در گزیده کد زیر برای میانجیگری (Interposition) تابع atoi مشاهده میشود:
int atoi (const char *str) {
sizeـt len = ــlibqasanـstrlen (str) + 1;
QASAN LOAD(str ,len);
return ــlqـlibcـatoi (str);
}
سازوکار هوک کردن نمادها (Symbol Hooking) در زمان اجرا، فراخوانیهای برنامه به تابع استاندارد atoi را به نسخه تخصصی خودمان هدایت میکند. این نسخه، پیش از فراخوانی پیادهسازی اصلی libc، یک Hypercall با نام QASAN_LOAD را اجرا میکند تا بافر حافظه درگیر در عملیات را بهصورت یکجا و با کارایی بالا اعتبارسنجی کند. هنگام جستوجو برای یافتن باگهای ظریف، ممکن است کاربر بخواهد دسترسیهای حافظه انجامشده در سایر توابع کتابخانه را نیز پاکسازی کند. در ASan، این کار معمولاً مستلزم کامپایل و پیوند دادن یک نسخه پاکسازیشده از libc برای برنامه است. طراحی QASan امکان استفاده از یک libc استاندارد و بدون ابزارگذاری (Uninstrumented) را فراهم میکند و برای موارد گسترش بارگذاری (Load Widening) آن نیز سازوکارهای ویژهای در نظر میگیرد.
به منظور توضیح گسترش بارگذاری (Load Widening)، از یک بحث شناختهشده در فهرست پستی LLVM [35] استفاده میکنیم: آرایه char a[22] و عملیات a[16]+a[21] را در نظر بگیرید. کامپایلر LLVM ممکن است زمانی که آرایه با مرز ۱۶ بایت همتراز (Aligned) باشد و روی پشته قرار گرفته باشد، برای خواندن همزمان هر دو بایت، یک بارگذاری ۸ بایتی (8-Byte Load) از نشانی &a[16] تولید کند؛ بااینحال، این بارگذاری بهاندازه ۲ بایت خارج از محدوده (Out of Bounds) خواهد بود.
این بحث در نهایت به معرفی فرادادههای بهینهسازی (Optimization Metadata) منجر شد که ASan از آنها برای نادیده گرفتن چنین مواردی استفاده میکند؛ در حالی که رویکردهای صرفاً باینری (Binary-Only Approaches) مانند RetroWrite و QASan تنها میتوانند این موارد را بهعنوان مثبت کاذب (False Positive) تشخیص دهند [36]. بااینحال، هنگامی که چنین مواردی در کد کتابخانه استاندارد رخ دهند، هشدارهای کاذب (False Alerts) از این نوع میتوانند برای تحلیل مشکلساز باشند و حتی باعث توقف زودهنگام آن شوند.
ما نمونههای زیادی از گسترش بارگذاری (Load Widening) را در libc پیدا کردیم که ناشی از دستورالعملهای SIMD تعریفشده بهصورت اسمبل درونخطی (Inline Assembly) در کد منبع آن بودند[1]. در QASan تصمیم گرفتیم مجموعهای از توابع متداول دستکاری حافظه (Commodity Memory Manipulation Functions) را که بر اساس تجربه خود، موارد گسترش بارگذاری (Load Widening) را در پیادهسازی آنها مشاهده کرده بودیم (A)، به نسخههای معادل از نظر معنایی و ایمن از نظر حافظه (Memory-Safe) بازهدایت (Rewire) کنیم.
سپس با استفاده از وصلهگذاری فوری (Hot Patching)، فراخوانیهای داخلی به این توابع «مشکلساز» از سایر توابع کتابخانه libc را نیز رهگیری میکنیم. از آنجا که این فراخوانیها توسط هوک کردن نماد (Symbol Hooking) شناسایی نمیشوند، یک ترامپولین (Trampoline) در بلوک ورودی (Entry Block) توابع فراخوانی شده قرار میدهیم تا این فراخوانیها را به نسخههای ایمن خود هدایت کند.
۳.۴ بحث (Discussion)
یکپارچهسازی با فازر (Fuzzer Integration). طراحی QASan میتواند بهصورت طبیعی مکمل راهکارهای موجود فازینگ باشد که بر پایه شبیهسازی در سطح کاربر QEMU (QEMU User Emulation) عمل میکنند. ما افزونه QEMU خود را در نسخه 3.11 این شبیهساز پیادهسازی کردیم که حدود ۵۰۰ خط کد (LOC) برای مدیریت Hypercallها و نگهداری پشته سایه (Shadow Stack)، و حدود ۲۰۰ خط کد دیگر برای هوک کردن دسترسیهای حافظه و تولید کد اعتبارسنجی (Validation Code) در شبیهساز TCG به آن اضافه شده است.
انتقال وصلههای ما به چارچوب فازینگ AFL++ [18] تنها به تغییرات بسیار اندک و سطحی نیاز داشت. ما معتقدیم گسترش این راهکار به سایر فازرهای مبتنی بر QEMU نیز به همین اندازه ساده خواهد بود؛ زیرا افزونه ما در عملکرد عادی TCG که این فازرها آن را با ابزارگذاری (Instrumentation) گسترش میدهند، دخالتی ایجاد نمیکند و همچنین چیدمان کد (Code Layout) و انتقالهای آن را که مبنای فازینگ هدایتشده با پوشش (Coverage-Guided Fuzzing) هستند، تغییر نمیدهد [12]. در ادامه، اسکریپتهای راهاندازی مربوط به مهارگر آزمون (Testing Harness) تنها به یک تغییر نیاز دارند: باید یک متغیر محیطی (Environment Variable) تنظیم شود تا کتابخانه زمان اجرای ما را از پیش بارگذاری (Preload) کند.
نقاط قوت و محدودیتها (Strengths and Limitations). QASan قابلیت پاکسازی آمادهبهکار (Off-the-Shelf) حافظه Heap را برای فازینگ باینری فراهم میکند؛ قابلیتی که همانطور که بهصورت تجربی در بخش 4.2 بررسی خواهیم کرد، میتواند باگهایی را آشکار کند که در غیر این صورت احتمالاً از دست میرفتند. ما QASan را بر پایه QEMU ساختهایم، زیرا این شبیهساز از نظر عملی اهمیت بالایی دارد؛ بااینحال، قابلیتهای ابزارگذاری موردنیاز ما، یعنی هوک کردن (Hook) دسترسیهای حافظه و انتقال فراخوانیهای سیستمی (System Call Forwarding)، تقریباً در هر سامانه ترجمه پویای باینری (Dynamic Binary Translation — DBT) در دسترس هستند [16].
این طراحی با معماریهای مختلف سازگار است و راه را برای سناریوهای بازاجرا روی باینری (Binary Rehosting)، برای مثال در آزمون میانافزار (Firmware Testing)، باز میکند. در مقایسه با ASan، محدودیت اصلی ما ناتوانی در بررسی اعتبار دسترسیها برای متغیرهای سراسری (Global Variables)، اشیای پشته (Stack Objects) و فیلدهای درونشیء (Intra-Object Fields) است. این موارد از مشکلات شناختهشده رویکردهای باینری هستند [7]، [15]؛ زیرا چنین واحدهای دادهای در وهله نخست، به دلیل از دست رفتن اطلاعات ناشی از فرایند کامپایل، بهسختی قابل مکانیابی هستند. علاوه بر این، نمیتوان چیدمان آنها را با استفاده از نواحی Redzone تغییر داد، مگر اینکه تمامی ارجاعهای موجود به آنها در کد نیز اصلاح شوند.
قرار دادن هوکها برای اعتبارسنجی دسترسیهای حافظه ممکن است از نظر عملکرد بهینه نباشد. این مسئله در مورد دسترسیهایی رخ میدهد که شامل Heap نیستند یا کامپایلر میتواند آنها را بهینهسازی کند. در حالت نخست، ما در Lifterها (Lifter در QEMU، بخشی است که دستورالعملهای معماری باینری هدف را دریافت و آنها را به عملیات میانی TCG تبدیل میکند تا QEMU بتواند آنها را پردازش و در نهایت برای معماری میزبان اجرا کند) دستورالعملهای اختصاصی پشته، مانند pop، را در فهرست سفید (Whitelist) قرار میدهیم؛ در حالی که یک تحلیل ایستا (Static Analysis) میتواند دستورالعملهای بیشتری را که هدف دسترسی آنها بهصورت ایستا قابل تعیین است، حذف کند. حالت دوم عمدتاً در مورد کد باینری بهینهسازینشده (Unoptimized Binary Code) مطرح است؛ برای مثال، زمانی که کامپایلر یک خواندن حافظه ثابت نسبت به حلقه (Loop-Invariant Memory Read) را به خارج از حلقه منتقل نکرده باشد.
مسیرهای آینده (Future Directions). یکی از مسیرهای امیدوارکننده برای پژوهشهای آینده، گسترش پیادهسازی ما به سمت پاکسازی کل سیستم (Full-System Sanitization) است. فازینگ کد هسته (Kernel Code) یک حوزه فعال پژوهشی است [37]، [38]، اما پاکسازها (Sanitizers) بهطور سنتی برای کد هسته قابل استفاده نبودهاند [7]. دو پروژه از Google [39]، [40] اکنون میتوانند هستههای ۶۴ بیتی لینوکس را با استفاده از ابزارگذاری مبتنی بر کد منبع (Source Instrumentation) پاکسازی کنند؛ در حالی که ویندوز و معماریهای ۳۲ بیتی در حال حاضر خارج از محدوده این قابلیتها قرار دارند.
مسیر دیگری که میتوان بررسی کرد، یکپارچهسازی MSan [41] است؛ روشی برای پاکسازی خواندنهای حافظه مقداردهینشده (Uninitialized Memory Reads). در ابزارگذاری مبتنی بر کد منبع، ASan و MSan را نمیتوان همزمان استفاده کرد؛ زیرا هرکدام در دسترسیهایی که به نمایش سایه (Shadow Representation) دیگری انجام میشوند، اختلال ایجاد میکنند. در مقابل، نگهداری چنین نمایشهایی درون شبیهساز، همانند QASan، از بروز این تداخل جلوگیری خواهد کرد.
۴. ارزیابی (Evaluation)
در این بخش، یک ارزیابی دومحوره (Two-Pronged Evaluation) ارائه میکنیم. ابتدا توانایی QASan را در شناسایی نقضها (Violations) در مجموعهای از آزمونهای معیار (Benchmarks) که حقیقت مبنا (Ground Truth) آنها مشخص است، بررسی میکنیم. سپس کاربردپذیری آن را به صورت سرتاسری (End-to-End Utility) ارزیابی میکنیم و نشان میدهیم که QASan میتواند باگهایی را آشکار کند که در غیر این صورت توسط یک فازر پیشرفته و بهروز (State-of-the-Art Fuzzer) شناسایی نمیشوند. همچنین، سربار (Overhead) افزودهشده به فرایند فازینگ در اثر اعمال پاکسازی (Sanitization) را اندازهگیری میکنیم.
۴.۱ نقضهای حافظه (Memory Violations)
ما قابلیتهای پیادهسازی خود در یافتن نقضهای حافظه را با استفاده از مجموعه آزمون ++Juliet C/C نسخه 1.3 ارزیابی میکنیم. این مجموعه شامل تعداد زیادی مورد آزمون (Test Case) برای بیش از ۱۰۰ دسته آسیبپذیری است. از میان آنها، موارد مرتبط با نقضهای ایمنی حافظه (Memory Safety Violations) را انتخاب میکنیم، یعنی:
- CWE-121 – سرریز بافر مبتنی بر پشته (Stack-based Buffer Overflow)
- CWE-122 – سرریز بافر مبتنی بر Heap (Heap-based Buffer Overflow)
- CWE-124 – نوشتن در خارج از ابتدای بافر (Buffer Underwrite)
- CWE-126 – خواندن بیش از محدوده بافر (Buffer Over-read)
- CWE-127 – خواندن پیش از محدوده بافر (Buffer Under-read)
- CWE-415 – آزادسازی مضاعف (Double Free)
- CWE-416 – استفاده پس از آزادسازی (Use-After-Free)
- CWE-590 – آزادسازی حافظهای که روی Heap قرار ندارد (Free Memory not on the Heap)
موارد آزمونی را که به منابع خارجی وابسته هستند یا برای دریافت مقادیر خاص به اوراکلهای تصادفی (Random Oracles) متکی میباشند، کنار میگذاریم؛ زیرا این موارد ذاتاً به تحلیلهای ایستا (Static Analyses) نیاز دارند. جدول ۱ نتایج جمعآوری شده هنگام کامپایل آزمونها برای یک Linux x64 را گزارش میکند. دستهها را بهگونهای مرتب کردهایم که نقضهای گروه نخست ممکن است حافظه سراسری (Global Storage)، Heap یا پشته (Stack) را درگیر کنند، در حالی که نقضهای گروه دوم تنها به Heap مربوط هستند. نیمی از موارد آزمون در هر گروه، طبق طراحی، منفی واقعی (True Negative — TN) هستند؛ یعنی از نظر ایمنی حافظه مشکلی ندارند.
ما QASan را با ابزارگذاری مبتنی بر کد منبع (Source-Based Instrumentation) در ASan و یک رویکرد مبتنی بر ترجمه پویای باینری (Dynamic Binary Translation — DBT) با استفاده از ابزار محبوب Valgrind memcheck مقایسه میکنیم. توجه داشته باشید که هر دو ابزار، منابع خطای بیشتری را نسبت به QASan پوشش میدهند؛ برای مثال، ASan نقضهای ایمنی مکانی (Spatial Safety Violations) مربوط به پشته و حافظه سراسری را نیز پوشش میدهد (بخش ۳.۴)، و Valgrind نیز نشت حافظه (Memory Leaks) و خواندن حافظه مقداردهینشده (Uninitialized Memory Reads) را شناسایی میکند. برای ایجاد یک مقایسه منصفانه، شناسایی موارد اخیر را در Valgrind غیرفعال میکنیم، زیرا این موارد نقضهای مکانی و زمانی نیستند (بخش 2.1، [7])؛ در مورد ASan نیز از پیکربندی پیشفرض استفاده میکنیم. جالب آنکه هیچیک از این سه ابزار در آزمونهای ما مثبت کاذب (False Positive — FP) ایجاد نمیکنند؛ بنابراین، مجموع مثبتهای واقعی (True Positive — TP) و منفیهای کاذب (False Negative — FN) برابر با ۵۰٪ آزمونها خواهد بود، یا در برخی آزمونها که مهلت ۳ ثانیهای به پایان میرسد، کمتر از این مقدار خواهد بود.
در گروه فقطHeap (Heap-Only) که در جدول با حروف برجسته مشخص شده است، QASan به اندازه memcheck دقیق است و هر دو عملکرد بهتری از ASan دارند؛ زیرا کد کتابخانه استاندارد را که توسط ASan مدلسازی نشده است نیز بررسی میکنند. هنگامی که ابزارگذاری libc را غیرفعال میکنیم، درصد TP برای CWE-122 به 45.04٪ کاهش مییابد، زیرا QASan در حال حاضر مدلهای کمتری نسبت به ASan پیادهسازی کرده است؛ در حالی که این درصد برای ۷ دسته دیگر بدون تغییر باقی میماند.
در گروه ترکیبی مربوط به چهار ردیف نخست جدول، در سه مورد از آنها QASan و memcheck اساساً خطاهای یکسانی را شناسایی میکنند؛ یعنی خطاهایی که اشیای Heap را درگیر میکنند. در مورد CWE-121 که تنها شامل نقضهای مربوط به پشته است، memcheck سازوکارهای ویژهای برای شناسایی دسترسیهایی دارد که از انتهای بالایی پشته فراتر میروند و به کرش فوری (Immediate Crash) منجر نمیشوند. در حال حاضر، تصمیم گرفتهایم برای مدیریت چنین مواردی ابزارگذاری اضافی انجام ندهیم و در نتیجه، سربار بیشتری نیز در فرایند فازینگ ایجاد نکنیم. در مقابل، قابلیت ASan برای تغییر چیدمان پشته در سطح کد منبع، امکان دستیابی به درصدهای TP بسیار بالاتری را فراهم میکند.
جدول ۱. نتایج مجموعه آزمون Juliet. ستونهای TP (مثبت واقعی) و FN (مثبت کاذب) برحسب درصد (%) ارائه شدهاند. مقادیر TN و FP بهترتیب در تمام موارد برابر با ۵۰٪ و ۰٪ بودند:
جدول ۲. باگهای گزارش شده و توان عملیاتی (Throughput) برای ++AFL:
۴.۲ فازینگ پاکسازی شده (Sanitized Fuzzing)
همانطور که در بخش ۳.۴ پیشبینی شد، QASan را در ++AFL یکپارچه کردیم؛ ++AFL یک چارچوب فازینگ جعبهخاکستری (Greybox Fuzzing) است که تکنیکهای پیشرفتهای مانند تطابق ورودی با حالت (Input-to-State Correspondence) [42] و پوشش مقایسه تکبایتی (Single-Byte Compare Coverage) [43] را پیادهسازی میکند. ++AFL برای Backend مبتنی بر QEMU خود، از یک سازوکار Forking کارآمد استفاده میکند تا سربار ناشی از کامپایل مجدد ترجمه پویای باینری (DBT Recompilation) در اجراهای مختلف را کاهش دهد.
برای بررسی کاربردپذیری پیشنهاد خود بهصورت سرتاسری (End-to-End Utility)، زیرمجموعهای شامل ۸ برنامه از مجموعه آزمون فازرهای گوگل (Google Fuzzer Test Suite — FTS) [44] را که در ستون نخست جدول ۲ گزارش شدهاند، آزمایش میکنیم. این برنامهها دارای خرابیهای حافظه (Memory Corruptions) و سایر باگهای ظریف هستند و معمولاً در پژوهشهای این حوزه مورد استفاده قرار میگیرند. این برنامهها از نرمافزارهای دنیای واقعی مشتق شدهاند که توسط طرح OSS-Fuzz [45] آزموده شدهاند؛ بنابراین، انتظار نمیرود دارای باگهای سطحی (Shallow Bugs) باشند. اجراهای آزمایشی را بهمدت ۱۲ ساعت روی یک ماشین مجهز به پردازنده Intel i7-8565U و با فعالیت پسزمینه کم انجام دادیم و در مواردی که بذرهای (Seed) مجموعه FTS در دسترس بودند، از آنها استفاده کردیم[1].
تعداد باگهایی که هنگام فعال بودن QASan پیدا میشوند، بیشتر از تعداد باگهای یافتشده در پیکربندی استاندارد ++AFL است. بررسی کردیم که تمام باگهای اضافی تنها در صورت فعال بودن پاکسازی (Sanitization) آشکار میشوند؛ به استثنای یک خطای بخشبندی (Segmentation Fault) در json که ++AFL به دلیل آنتروپی فازینگ (Fuzzing Entropy) آن را از دست داده بود. در مورد pcre2 نیز باگهای متعددی پیدا کردیم که در مستندات مجموعه آزمون فازرها (Fuzzer Test Suite) فهرست نشده بودند. در مجموعِ تمام معیارها (Benchmarks)، QASan تعداد ۴ مورد سرریز خواندن Heap (Heap Read Overflow)، ۳ مورد سرریز نوشتن (Write Overflow)، ۴ مورد زیرمحدودهخوانی (Read Underflow) و ۱۱ مورد استفاده پس از آزادسازی (Use-After-Free) را شناسایی کرد (جزئیات در بخشB ارائه شده است).
در مورد سربار زمانی (Time Overhead)، باید در نظر داشت که اگرچه رویکردهای مبتنی بر ترجمه پویای باینری (Dynamic Binary Translation — DBT) در یک اجرای منفرد پرهزینه هستند، در یک فازر با طراحی مناسب، اشتراکگذاری حافظه نهان کد (Code-Cache Sharing) هنگام Fork کردن، بخش قابلتوجهی از سربار ترجمه را مستهلک میکند. بنابراین، تعداد اجراهایی را که ++AFL در هر ثانیه تکمیل میکند با یکدیگر مقایسه میکنیم: با فعال بودن QASan، سربارهایی در بازه 1.26 تا 2.74 برابر مشاهده کردیم که میانگین هندسی (Geometric Mean) آنها 1.61 برابر است.
۵. کارهای مرتبط (Related Work)
در این بخش، پژوهشهای مرتبطی را بررسی میکنیم که در بخش 2 پوشش داده نشدهاند. توسعهدهندگان برای تشخیص نقضها (Violations) در باینریها، بدون ابزارگذاری (Instrumentation) دسترسیهای حافظه، رویکردهای مختلفی از جمله تخصیصدهندههای حافظه سفارشی (Custom Allocators) را بررسی کردهاند. صفحات محافظ (Guard Pages) در پژوهشهایی مانند [47]–[49] به کار گرفته شدهاند؛ به این صورت که برای هر شیء (Object)، یک صفحه مجزا تخصیص داده میشود و شیء در انتهای آن صفحه قرار میگیرد و پس از آن نیز یک صفحه غیرقابلدسترسی (Inaccessible Page) قرار داده میشود تا سرریزها (Overflows) شناسایی شوند.
این راهکارها کامل نیستند و معمولاً ماهیتی احتمالی (Probabilistic) دارند، مصرف حافظه بالایی ایجاد میکنند، در عملیات خواندن قادر به شناسایی پاریزها (Underflows) نیستند و ممکن است قواعد همترازی (Alignment Rules) را نقض کنند. BaseSAFE [50] سامانهای تخصصی برای آزمون فازی میانافزارهای شبکههای سلولی (Cellular Basebands) است که یک تخصیصدهنده جایگزینشونده مستقیم (Drop-in Allocator) را با قناری (Canary)های Heap ترکیب میکند؛ این قناریها با استفاده از هوکهای سنگین (Heavyweight Hooks) موتور شبیهسازی Unicorn بررسی میشوند. اگرچه طراحی آن به شدت به یک پشته نرمافزاری سفارشی و بازاجرا (Rehosting) جزئیِ یک Dump حافظه وابسته است، BaseSAFE پیشرفتهای قابلتوجهی را در روشهای آزمون فازی برای تحلیلگرهای سطح پایین (Low-Level Parsers) در اهداف نهفته (Embedded Targets) به همراه داشته است.
HQEMU [51] با استفاده از نخهای اضافی (Additional Threads) برای بهینهسازی مجدد Traceهای TCG بر اساس اطلاعات Profiling برخط (Online Profiling Information) و کامپایلر سنگین LLVM، سربار QEMU را کاهش میدهد. DBILL [52] بعدها این رویکرد را گسترش داد تا سازوکارهای پاکسازی (Sanitization Machinery) را نیز در آن درج کند. اگرچه این رویکرد برای اجراهای طولانیمدت کارآمد است، به نظر میرسد برای فازینگ چندان مناسب نباشد: یک فازر تعداد بسیار زیادی اجرا را انجام میدهد که اغلب از حافظههای نهان کد (Code Caches) مشترک استفاده میکنند، و هستههای پردازنده موجود نیز میتوانند برای اجرای همزمان فرایندهای فازینگ مورد استفاده قرار گیرند.
Song و همکاران در [7] یک مرور جامع و بهروز از پژوهشهای حوزه پاکسازی (Sanitization) ارائه کردهاند. تعامل میان فازینگ و نقضهای حافظه (Memory Violations) در ParmeSan [53] بررسی شده است؛ این سامانه از Tripwireهای پاکسازی (Sanitization Tripwires) برای هدایت فازر و آشکارسازی زودتر نقضها استفاده میکند. این تعامل همچنین در UAFuzz [54] بررسی شده است؛ UAFuzz فازینگ جعبهخاکستری (Grey-Box Fuzzing) را با معیارهای مستقیم (Direct Metrics) برای جستوجوی باگهای استفاده پس از آزادسازی (Use-After-Free) گسترش میدهد.
۶. ملاحظات پایانی (Concluding Remarks)
QASan افزودهای به موقع به حوزه فازینگ (Fuzzing) ارائه میدهد که امکان آشکارسازی خطاهای ایمنی حافظه (Memory Safety Errors) در باینریها را فراهم میکند. اگرچه طراحی آن راه را برای مسیرهایی مانند پاکسازی کل سیستم (Whole-System Sanitization) باز میکند، اما پیادهسازی آن به اندازهای بالغ و پخته است که میتواند نرمافزارهای پیچیدهای مانند gcc و LibreOffice را نیز مدیریت کند و هنگام فراخوانی کد libc، هیچ مثبت کاذبی (False Positive) ایجاد نکند. ما QASan را بهصورت متنباز (Open Source) در اختیار عموم قرار دادهایم.
پاورقی
[1] ². برای libxml، بذر (Seed) را از پروژه fuzzdata [46] تهیه میکنیم؛ در مورد pcre نیز از یک بذر خالی استفاده کرده و یک دیکشنری (Dictionary) را در اختیار ++AFL قرار میدهیم.
منابع
[1] S. Nagarakatte, J. Zhao, M. M. Martin, and S. Zdancewic, “Softbound: Highly compatible and complete spatial memory safety for C,” in Proc. of the 30th ACM SIGPLAN Conference on Programming Language Design and Implementation, ser. PLDI ’09. ACM, 2009, pp. 245–258. [Online]. Available:https://doi.org/10.1145/1542476.1542504
[2] L. Szekeres, M. Payer, T. Wei, and D. Song, “Sok: Eternal war in memory,” in 2013 IEEE Symposium on Security and Privacy, 2013, pp. 48–62.
[3] J. Berdine, B. Cook, and S. Ishtiaq, “Slayer: Memory safety for systems-level code,” in Computer Aided Verification. Springer Berlin Heidelberg, 2011, pp. 178–183.
[4] H.-J. Boehm, “Space efficient conservative garbage collection,” in Proceedings of the ACM SIGPLAN 1993 Conference on Programming Language Design and Implementation, ser. PLDI ’93. Association for Computing Machinery, 1993, pp. 197–206. [Online]. Available: https://doi.org/10.1145/155090.155109
[5] G. C. Necula, S. McPeak, and W. Weimer, “Ccured: Type-safe retrofitting of legacy code,” SIGPLAN Not., vol. 37, no. 1, pp. 128–139, Jan. 2002. [Online]. Available: https://doi.org/10.1145/565816.503286
[6] A. Lochbihler, “Making the java memory model safe,” ACM Trans. Program. Lang. Syst., vol. 35, no. 4, Jan. 2014. [Online]. Available:https://doi.org/10.1145/2518191
[7] D. Song, J. Lettner, P. Rajasekaran, Y. Na, S. Volckaert, P. Larsen, and M. Franz, “Sok: Sanitizing for security,” in 2019 IEEE Symposium on Security and Privacy (SP), 2019, pp. 1275–1295.
[8] R. Baldoni, E. Coppa, D. C. D’Elia, C. Demetrescu, and I. Finocchi, “A survey of symbolic execution techniques,” ACM Comput. Surv., vol. 51, no. 3, 2018.
[9] C. Cadar, D. Dunbar, and D. R. Engler, “KLEE: Unassisted and automatic generation of high-coverage tests for complex systems programs,” in Proc. 8th USENIX Conf. on Operating Systems Design and Implementation, ser. OSDI’08. USENIX Association, 2008, pp. 209–224.
[10] D. Distefano, M. F¨ahndrich, F. Logozzo, and P. W. O’Hearn, “Scaling static analyses at facebook,” Commun. ACM, vol. 62, no. 8, pp. 62–70, Jul. 2019. [Online]. Available: https://doi.org/10.1145/3338112
[11] D. Kroening and M. Tautschnig, “CBMC – C bounded model checker,” in Tools and Algorithms for the Construction and Analysis of Systems. Springer Berlin Heidelberg, 2014, pp. 389–391.
[12] A. Zeller, R. Gopinath, M. B¨ohme, G. Fraser, and C. Holler, “The Fuzzing Book,” https://www.fuzzingbook.org/, 2019, [Online; accessed 25-May-2020].
[13] M. Payer, “The fuzzing hype-train: How random testing triggers thousands of crashes,” IEEE Security Privacy, vol. 17, no. 1, pp. 78–82, 2019.
[14] M. Muench, J. Stijohann, F. Kargl, A. Francillon, and D. Balzarotti, “What you corrupt is not what you crash: Challenges in fuzzing embedded devices,” in NDSS 2018, Network and Distributed Systems Security Symposium, 18-21 February 2018, San Diego, CA, USA, 02 2018. [Online]. Available: http://www.eurecom.fr/publication/5417
[15] S. Dinesh, N. Burow, D. Xu, and M. Payer, “Retrowrite: Statically instrumenting cots binaries for fuzzing and sanitization,” 2020.
[16] D. C. D’Elia, E. Coppa, S. Nicchi, F. Palmaro, and L. Cavallaro,“SoK: Using dynamic binary instrumentation for security (and how you may get caught red handed),” in Proceedings of the 2019 ACM Asia Conference on Computer and Communications Security, ser. Asia CCS ’19. Association for Computing Machinery, 2019, pp. 15–27. [Online]. Available: https://doi.org/10.1145/3321705.3329819
[17] K. Serebryany, D. Bruening, A. Potapenko, and D. Vyukov, “Address-sanitizer: A fast address sanity checker,” in Proceedings of the 2012 USENIX Conference on Annual Technical Conference, ser. USENIX ATC’12. USENIX Association, 2012, p. 28.
[18] A. Fioraldi, D. Maier, H. Eißfeldt, and M. Heuse, “AFL++: Combining incremental steps of fuzzing research,” in 14th USENIX Workshop on Offensive Technologies (WOOT 20). USENIX Association, 2020.
[19] M. Payer, Software Security: Principles, Policies, and Protection, 0th ed. HexHive Books, April 2019. [Online]. Available:http://nebelwelt.net/SS3P/[20] A. Srivastava and A. Eustace, “Atom: A system for buildingcustomized program analysis tools,” in Proceedings of the ACM SIGPLAN 1994 Conference on Programming Language Design and Implementation, ser. PLDI ’94. ACM, 1994, pp. 196–205. [Online]. Available: http://doi.acm.org/10.1145/178243.178260
[21] B. Buck and J. K. Hollingsworth, “An api for runtime code patching,” Int. J. High Perform. Comput. Appl., vol. 14, no. 4, pp. 317–329, Nov. 2000. [Online]. Available: http://dx.doi.org/10.1177/109434200001400404
[22] M. Zhang, R. Qiao, N. Hasabnis, and R. Sekar, “A platform for secure static binary instrumentation,” in Proceedings of the 10th ACM SIGPLAN/SIGOPS International Conference on Virtual Execution Environments, ser. VEE ’14. ACM, 2014, pp. 129–140. [Online]. Available: http://doi.acm.org/10.1145/2576195.2576208
[23] S. Wang, P. Wang, and D. Wu, “UROBOROS: Instrumenting stripped binaries with static reassembling,” in 2016 IEEE 23rd International Conference on Software Analysis, Evolution, and Reengineering (SANER), vol. 1, 2016, pp. 236–247.
[24] P. Feiner, A. D. Brown, and A. Goel, “Comprehensive kernel instrumentation via dynamic binary translation,” SIGPLAN Not., vol. 47, no. 4, pp. 135–146, Mar. 2012. [Online]. Available: https://doi.org/10.1145/2248487.2150992
[25] C.-K. Luk, R. Cohn, R. Muth, H. Patil, A. Klauser, G. Lowney, S. Wallace, V. J. Reddi, and K. Hazelwood, “Pin: Building customized program analysis tools with dynamic instrumentation,” in Proc. of the 2005 ACM SIGPLAN Conference on Programming Language Design and Implementation, ser. PLDI ’05. ACM, 2005, pp. 190–200. [Online]. Available: http://doi.acm.org/10.1145/1065010.1065034
[26] D. Bruening, Q. Zhao, and S. Amarasinghe, “Transparent dynamic instrumentation,” in Proceedings of the 8th ACM SIGPLAN/SIGOPS Conference on Virtual Execution Environments, ser. VEE ’12. ACM, 2012, pp. 133–144.
[27] N. Nethercote and J. Seward, “Valgrind: A framework for heavy-weight dynamic binary instrumentation,” in Proceedings of the 28th ACM SIGPLAN Conference on Programming Language Design and Implementation, ser. PLDI ’07. ACM, 2007, pp. 89–100.
[28] F. Bellard, “Qemu, a fast and portable dynamic translator,” in Proceedings of the Annual Conference on USENIX Annual Technical Conference, ser. ATEC ’05. USENIX Association, 2005, p. 41.
[29] D. Bruening and Q. Zhao, “Practical memory checking with dr.memory,” in International Symposium on Code Generation and Optimization (CGO 2011), 2011, pp. 213–223.
[30] Google Project Zero, “WinAFL,” 2020. [Online]. Available:https://github.com/googleprojectzero/winafl
[31] Y. Zheng, A. Davanian, H. Yin, C. Song, H. Zhu, and L. Sun, “Firm-afl: high-throughput greybox fuzzing of iot firmware via augmented process emulation,” in 28th USENIX Security Symposium (USENIX Security 19), 2019, pp. 1099–1114.
[32] V. Pham, M. Boehme, A. E. Santosa, A. R. Caciulescu, and A. Roychoudhury, “Smart greybox fuzzing,” IEEE Transactions on Software Engineering, 2019.
[33] A. Fioraldi, D. C. D’Elia, and E. Coppa, “WEIZZ: Automatic grey-box fuzzing for structured binary formats,” in Proc. of the 29th ACM SIGSOFT International Symposium on Software Testing and Analysis, ser. ISSTA 2020. Association for Computing Machinery, 2020. [Online]. Available: https://doi.org/10.1145/3395363.3397372
[34] D. C. D’Elia, C. Demetrescu, and I. Finocchi, “Mining hot calling contexts in small space,” Software: Practice and Experience, vol. 46, no. 8, pp. 1131–1152, Aug. 2016. [Online]. Available:https://doi.org/10.1002/spe.2348
[35] LLVMdev Mailing List, “Load widening conflicts with Address-Sanitizer,” https://lists.llvm.org/pipermail/llvm-dev/2011-December/046322.html, 2011, [Online; accessed 25-May-2020].
[36] RetroWrite project, “Issue #6: Load widening,” https://github.com/HexHive/retrowrite/issues/6, 2019, [Online; accessed 25-May-2020].
[37] S. Schumilo, C. Aschermann, R. Gawlik, S. Schinzel, and T. Holz, “kAFL: Hardware-Assisted Feedback Fuzzing for OS Kernels,” in USENIX Security Symposium, 2017.
[38] H. Liang, Y. Chen, Z. Xie, and Z. Liang, “X-afl: A kernel fuzzer combining passive and active fuzzing,” in Proc. of the 13th European Workshop on Systems Security, ser. EuroSec ’20. ACM, 2020, pp. 13–18. [Online]. Available: https://doi.org/10.1145/3380786.3391400
[39] Google, “KernelAddressSanitizer (KASAN),” 2020. [Online]. Available: https://github.com/google/kasan
[40] ——, “KMSAN (Kernel Memory Sanitizer),” 2020. [Online]. Available: https://github.com/google/kmsan
[41] E. Stepanov and K. Serebryany, “MemorySanitizer: Fast detector of uninitialized memory use in C++,” in 2015 IEEE/ACM Int. Symposium on Code Generation and Optimization (CGO), 2015, pp. 46–55.
[42] C. Aschermann, S. Schumilo, T. Blazytko, R. Gawlik, and T. Holz, “REDQUEEN: fuzzing with input-to-state correspondence,” in 26th Annual Network and Distributed System Security Symposium, NDSS, 2019. [Online]. Available: https://www.ndss-symposium.org/ndss-paper/redqueen-fuzzing-with-input-to-state-correspondence/
[43] M. Jurczyk, “CompareCoverage,” https://github.com/googleprojectzero/CompareCoverage/, 2020, [Online; accessed 25-May-2020].
[44] Google, “Set of tests for fuzzing engines,” https://github.com/google/fuzzer-test-suite, 2020, [Online; accessed 20-May-2020].
[45] ——, “OSS-Fuzz: continuous fuzzing of open source software,” https://github.com/google/oss-fuzz, 2020, [Online; accessed 20-May-2020].
[46] Mozilla Security, “fuzzdata: Fuzzing resources for feeding various fuzzers with input,” https://github.com/MozillaSecurity/fuzzdata,2020, [Online; accessed 20-May-2020].
[47] “libdislocator,” https://github.com/mirrorer/afl/tree/master/libdislocator, 2020, [Online; accessed 20-May-2020].
[48] “GWP-ASAN,” http://llvm.org/docs/GwpAsan.html, 2020, [Online; accessed 20-May-2020].
[49] B. Perens, “Electric fence malloc debugger,” 1993.
[50] D. Maier, L. Seidel, and S. Park, “BaseSAFE: BasebandSAnitized Fuzzing through Emulation,” in 13th ACM Conference on Securityand Privacy in Wireless and Mobile Networks (WiSec ’20), Jul. 2020.
[51] D.-Y. Hong, C.-C. Hsu, P.-C. Yew, J.-J. Wu, W.-C. Hsu, P. Liu, C.-M. Wang, and Y.-C. Chung, “Hqemu: A multi-threaded and retargetable dynamic binary translator on multicores,” in Proceedings of the Tenth International Symposium on Code Generation and Optimization, ser. CGO ’12. ACM, 2012, pp. 104–113. [Online]. Available: https://doi.org/10.1145/2259016.2259030
[52] Y.-H. Lyu, D.-Y. Hong, T.-Y. Wu, J.-J. Wu, W.-C. Hsu, P. Liu, and P.-C. Yew, “Dbill: An efficient and retargetable dynamic binary instrumentation framework using llvm backend,” in Proceedings of the 10th ACM SIGPLAN/SIGOPS International Conference on Virtual Execution Environments, ser. VEE ’14. Association for Computing Machinery, 2014, pp. 141–152. [Online]. Available: https://doi.org/10.1145/2576195.2576213
[53] S. ¨Osterlund, K. Razavi, H. Bos, and C. Giuffrida, “ParmeSan:Sanitizer-guided Greybox Fuzzing,” in USENIX Security, Aug. 2020.
[54] M.-D. Nguyen, S. Bardin, R. Bonichon, R. Groz, and M. Lemerre, “Binary-level directed fuzzing for use-after-free vulnerabilities,”ArXiv, vol. abs/2002.10751, 2020