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

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

در اندروید 8.1، اجرای CFI توسط LLVM را در پشته رسانه فعال کردیم. در اندروید 9، 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/bluprint اجزای رسانه، مانند /platform/frameworks/av/media/libmedia/Android.bp و /platform/frameworks/av/cmds/stagefright/Android.mk فعال می‌شود.

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

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

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

پشتیبانی از CFI در فایل های ایجاد شده

برای فعال کردن CFI در یک فایل ساخت، مانند /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 اضافه می کند لغو می شود. به عنوان مثال، stack trace یک SIGABRT را نشان می‌دهد و logcat حاوی یک خط در مورد یکپارچگی جریان کنترل است که یک عدم تطابق را پیدا می‌کند.

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

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

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

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

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

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

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

اعتبار سنجی

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