کنترل یکپارچگی جریان

از سال ۲۰۱۶، حدود ۸۶٪ از کل آسیب‌پذیری‌های اندروید مربوط به ایمنی حافظه هستند. اکثر آسیب‌پذیری‌ها توسط مهاجمانی مورد سوءاستفاده قرار می‌گیرند که جریان کنترل عادی یک برنامه را تغییر می‌دهند تا فعالیت‌های مخرب دلخواه را با تمام امتیازات برنامه مورد سوءاستفاده انجام دهند. یکپارچگی جریان کنترل (CFI) یک مکانیسم امنیتی است که اجازه تغییر در نمودار جریان کنترل اصلی یک فایل باینری کامپایل شده را نمی‌دهد و انجام چنین حملاتی را به طور قابل توجهی دشوارتر می‌کند.

در اندروید ۸.۱، پیاده‌سازی CFI توسط LLVM در پشته رسانه فعال شد. در اندروید ۹، CFI در اجزای بیشتر و همچنین هسته فعال شد. CFI سیستم به طور پیش‌فرض فعال است، اما شما باید CFI هسته را فعال کنید.

CFI در LLVM نیاز به کامپایل با بهینه‌سازی زمان پیوند (LTO) دارد. LTO نمایش بیت‌کد LLVM از فایل‌های شیء را تا زمان پیوند حفظ می‌کند، که به کامپایلر اجازه می‌دهد تا در مورد اینکه چه بهینه‌سازی‌هایی می‌تواند انجام شود، بهتر استدلال کند. فعال کردن LTO اندازه فایل باینری نهایی را کاهش می‌دهد و عملکرد را بهبود می‌بخشد، اما زمان کامپایل را افزایش می‌دهد. در آزمایش روی اندروید، ترکیب LTO و CFI منجر به سربار ناچیزی برای اندازه کد و عملکرد می‌شود. در چند مورد، هر دو بهبود یافته‌اند.

برای جزئیات فنی بیشتر در مورد CFI و نحوه‌ی مدیریت سایر بررسی‌های کنترل رو به جلو، به مستندات طراحی LLVM مراجعه کنید.

مثال‌ها و منابع

CFI توسط کامپایلر ارائه می‌شود و در زمان کامپایل، ابزار دقیق را به فایل باینری اضافه می‌کند. ما از CFI در زنجیره ابزار Clang و سیستم ساخت اندروید در AOSP پشتیبانی می‌کنیم.

CFI به طور پیش‌فرض برای دستگاه‌های Arm64 برای مجموعه‌ای از کامپوننت‌ها در /platform/build/target/product/cfi-common.mk فعال است. همچنین مستقیماً در مجموعه‌ای از فایل‌های makefiles/blueprint کامپوننت‌های رسانه‌ای، مانند /platform/frameworks/av/media/libmedia/Android.bp و /platform/frameworks/av/cmds/stagefright/Android.mk فعال است.

پیاده‌سازی سیستم CFI

اگر از Clang و سیستم ساخت اندروید استفاده می‌کنید، CFI به طور پیش‌فرض فعال است. از آنجا که CFI به حفظ امنیت کاربران اندروید کمک می‌کند، نباید آن را غیرفعال کنید.

در واقع، ما اکیداً شما را تشویق می‌کنیم که CFI را برای اجزای اضافی فعال کنید. گزینه‌های ایده‌آل، کد بومی ممتاز یا کد بومی هستند که ورودی‌های غیرقابل اعتماد کاربر را پردازش می‌کنند. اگر از clang و سیستم ساخت اندروید استفاده می‌کنید، می‌توانید CFI را در اجزای جدید با اضافه کردن چند خط به فایل‌های makefile یا blueprint خود فعال کنید.

پشتیبانی از CFI در makefileها

برای فعال کردن CFI در یک فایل make، مانند /platform/frameworks/av/cmds/stagefright/Android.mk ، دستور زیر را اضافه کنید:

LOCAL_SANITIZE := cfi
# Optional features
LOCAL_SANITIZE_DIAG := cfi
LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
  • LOCAL_SANITIZE در طول ساخت، CFI را به عنوان ضدعفونی‌کننده مشخص می‌کند.
  • LOCAL_SANITIZE_DIAG حالت تشخیصی را برای CFI فعال می‌کند. حالت تشخیصی در هنگام خرابی، اطلاعات اشکال‌زدایی اضافی را در logcat چاپ می‌کند که هنگام توسعه و آزمایش نسخه‌های نهایی مفید است. با این حال، حتماً حالت تشخیصی را در نسخه‌های نهایی غیرفعال کنید.
  • LOCAL_SANITIZE_BLACKLIST به کامپوننت‌ها اجازه می‌دهد تا به صورت انتخابی ابزار دقیق CFI را برای توابع یا فایل‌های منبع منفرد غیرفعال کنند. می‌توانید از یک لیست سیاه به عنوان آخرین راه حل برای رفع هرگونه مشکل کاربری که ممکن است در غیر این صورت وجود داشته باشد، استفاده کنید. برای جزئیات بیشتر، به غیرفعال کردن CFI مراجعه کنید.

پشتیبانی از CFI در فایل‌های طرح اولیه

برای فعال کردن CFI در یک فایل طرح اولیه، مانند /platform/frameworks/av/media/libmedia/Android.bp ، دستور زیر را اضافه کنید:

   sanitize: {
        cfi: true,
        diag: {
            cfi: true,
        },
        blacklist: "cfi_blacklist.txt",
    },

عیب‌یابی

اگر CFI را در کامپوننت‌های جدید فعال می‌کنید، ممکن است با چند مشکل در رابطه با خطاهای عدم تطابق نوع تابع و خطاهای عدم تطابق نوع کد اسمبلی مواجه شوید.

خطاهای عدم تطابق نوع تابع به این دلیل رخ می‌دهند که CFI فراخوانی‌های غیرمستقیم را محدود می‌کند تا فقط به توابعی پرش کنند که نوع پویای یکسانی با نوع استاتیک مورد استفاده در فراخوانی دارند. CFI فراخوانی‌های توابع عضو مجازی و غیر مجازی را محدود می‌کند تا فقط به اشیاء پرش کنند که یک کلاس مشتق شده از نوع استاتیک شیء مورد استفاده برای انجام فراخوانی هستند. این بدان معناست که وقتی کدی دارید که هر یک از این فرضیات را نقض می‌کند، ابزاری که CFI اضافه می‌کند، لغو می‌شود. به عنوان مثال، ردیابی پشته یک SIGABRT را نشان می‌دهد و logcat حاوی خطی در مورد یکپارچگی جریان کنترل است که یک عدم تطابق را پیدا می‌کند.

برای رفع این مشکل، مطمئن شوید که تابع فراخوانی شده همان نوعی را دارد که به صورت استاتیک تعریف شده است. در اینجا دو مثال از CLها آورده شده است:

مشکل احتمالی دیگر، تلاش برای فعال کردن CFI در کدی است که شامل فراخوانی‌های غیرمستقیم به اسمبلی است. از آنجا که کد اسمبلی تایپ نشده است، این منجر به عدم تطابق نوع می‌شود.

برای رفع این مشکل، برای هر فراخوانی اسمبلی، پوشش‌دهنده‌های کد بومی ایجاد کنید و به پوشش‌دهنده‌ها امضای تابعی مشابه با اشاره‌گر فراخوانی‌کننده بدهید. سپس پوشش‌دهنده می‌تواند مستقیماً کد اسمبلی را فراخوانی کند. از آنجا که شاخه‌های مستقیم توسط CFI ابزاردهی نمی‌شوند (نمی‌توانند در زمان اجرا مجدداً اشاره‌گری شوند و بنابراین خطر امنیتی ایجاد نمی‌کنند)، این کار مشکل را برطرف می‌کند.

اگر تعداد توابع اسمبلی زیاد است و نمی‌توان همه آنها را اصلاح کرد، می‌توانید تمام توابعی را که شامل فراخوانی‌های غیرمستقیم به اسمبلی هستند، در لیست سیاه قرار دهید. این کار توصیه نمی‌شود زیرا بررسی‌های CFI را روی این توابع غیرفعال می‌کند و در نتیجه زمینه حمله را فراهم می‌کند.

غیرفعال کردن CFI

ما هیچ سربار عملکردی مشاهده نکردیم، بنابراین نیازی به غیرفعال کردن CFI ندارید. با این حال، اگر تأثیری بر کاربر داشته باشد، می‌توانید با ارائه یک فایل لیست سیاه ضدعفونی‌کننده در زمان کامپایل، CFI را برای توابع یا فایل‌های منبع به صورت انتخابی غیرفعال کنید. لیست سیاه به کامپایلر دستور می‌دهد که ابزار CFI را در مکان‌های مشخص غیرفعال کند.

سیستم ساخت اندروید از لیست‌های سیاه به ازای هر کامپوننت (که به شما امکان می‌دهد فایل‌های منبع یا توابع منفردی را انتخاب کنید که ابزار CFI را دریافت نمی‌کنند) برای هر دو سیستم Make و Soong پشتیبانی می‌کند. برای جزئیات بیشتر در مورد قالب یک فایل لیست سیاه، به مستندات Clang در بالادست مراجعه کنید.

اعتبارسنجی

در حال حاضر، هیچ آزمایش CTS مخصوص CFI وجود ندارد. در عوض، مطمئن شوید که آزمایش‌های CTS با یا بدون فعال بودن CFI با موفقیت انجام می‌شوند تا تأیید شود که CFI بر دستگاه تأثیر نمی‌گذارد.