اعتبارًا من عام 2016، كان حوالي% 86 من جميع الثغرات الأمنية في Android مرتبطًا بأمان الذاكرة. يستغل المهاجمون معظم الثغرات الأمنية من خلال تغيير مسار التحكّم العادي في التطبيق لتنفيذ أنشطة ضارة عشوائية بجميع امتيازات التطبيق المستغَل. سلامة مسار التحكّم (CFI) هي آلية أمان تمنع إجراء تغييرات على الرسم البياني الأصلي لمسار التحكّم في ملف ثنائي مترجَم، ما يجعل تنفيذ هذه الهجمات أكثر صعوبة.
في نظام التشغيل Android 8.1، فعّلنا تنفيذ CFI في حزمة الوسائط باستخدام LLVM. في نظام التشغيل Android 9، فعّلنا ميزة CFI في المزيد من المكوّنات وفي النواة أيضًا. يكون نظام CFI مفعَّلاً تلقائيًا، ولكن عليك تفعيل CFI للنواة.
تتطلّب ميزة CFI في LLVM إجراء عملية تجميع باستخدام تحسين وقت الربط (LTO). تحتفظ ميزة LTO بتمثيل رمز LLVM الثنائي لملفات الكائنات إلى حين وقت الربط، ما يتيح للمترجم البرمجي التفكير بشكل أفضل في التحسينات التي يمكن إجراؤها. يؤدي تفعيل ميزة LTO إلى تقليل حجم الملف الثنائي النهائي وتحسين الأداء، ولكنّه يزيد من وقت الترجمة البرمجية. أظهرت الاختبارات على Android أنّ الجمع بين LTO وCFI يؤدي إلى تكلفة إضافية ضئيلة جدًا من حيث حجم الرمز البرمجي والأداء، بل إنّه في بعض الحالات، تحسّن كل منهما.
لمزيد من التفاصيل الفنية حول CFI وكيفية التعامل مع عمليات التحقّق الأخرى من التحكّم المسبق، يُرجى الاطّلاع على مستندات تصميم LLVM.
الأمثلة والمصدر
يوفّر برنامج التجميع واجهة CFI ويضيف قياس حالة التطبيق إلى البرنامج الثنائي أثناء وقت التجميع. نوفّر دعمًا لميزة "التحكّم في التدفق المتكامل" في سلسلة أدوات Clang ونظام التصميم Android في مشروع Android المفتوح المصدر (AOSP).
يكون CFI مفعَّلاً تلقائيًا على أجهزة Arm64 لمجموعة المكوّنات في /platform/build/target/product/cfi-common.mk.
يتم تفعيلها أيضًا مباشرةً في ملفات makefile/blueprint لمجموعة من مكوّنات الوسائط، مثل /platform/frameworks/av/media/libmedia/Android.bp و/platform/frameworks/av/cmds/stagefright/Android.mk.
تنفيذ CFI للنظام
يتم تفعيل ميزة "التحكّم في التدفق المتكامل" تلقائيًا إذا كنت تستخدم Clang ونظام التصميم في Android. بما أنّ ميزة "واجهة برمجة تطبيقات سلامة الجهاز" تساعد في الحفاظ على أمان مستخدمي Android، يجب عدم إيقافها.
في الواقع، ننصحك بشدة بتفعيل CFI لمكوّنات إضافية. والمرشّحون المثاليون هم الرموز البرمجية الأصلية ذات الامتيازات أو الرموز البرمجية الأصلية التي تعالج بيانات أدخلها المستخدم غير الموثوق بها. إذا كنت تستخدم clang ونظام التصميم في Android، يمكنك تفعيل CFI في المكوّنات الجديدة من خلال إضافة بضعة أسطر إلى ملفات makefile أو ملفات blueprint.
إتاحة CFI في ملفات makefile
لتفعيل CFI في ملف إنشاء، مثل /platform/frameworks/av/cmds/stagefright/Android.mk،
أضِف ما يلي:
LOCAL_SANITIZE := cfi # Optional features LOCAL_SANITIZE_DIAG := cfi LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
- تحدّد
LOCAL_SANITIZECFI كأداة تنظيف أثناء عملية الإنشاء. - يؤدي
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 على سطر حول العثور على عدم تطابق في سلامة تدفق التحكّم.
لحلّ هذه المشكلة، تأكَّد من أنّ الدالة التي تم استدعاؤها لها النوع نفسه الذي تم تعريفه بشكل ثابت. في ما يلي مثالان على قوائم التغيير:
- البلوتوث: /c/platform/system/bt/+/532377
- الاتصال القصير المدى (NFC): /c/platform/system/nfc/+/527858
من المشاكل المحتملة الأخرى محاولة تفعيل CFI في رمز يحتوي على استدعاءات غير مباشرة للتجميع. وبما أنّ رمز التجميع غير مكتوب، يؤدي ذلك إلى عدم تطابق النوع.
لحلّ هذه المشكلة، أنشئ برامج تضمين للتعليمات البرمجية الأصلية لكل استدعاء تجميع، ومنح برامج التضمين توقيع الدالة نفسه الذي يمنحه المؤشر الذي يستدعي. ويمكن بعد ذلك أن يستدعي برنامج التغليف رمز التجميع مباشرةً. بما أنّ الفروع المباشرة لا يتم تزويدها بأدوات CFI (لا يمكن إعادة توجيهها في وقت التشغيل وبالتالي لا تشكّل خطرًا أمنيًا)، سيؤدي ذلك إلى حلّ المشكلة.
إذا كان هناك عدد كبير جدًا من دوال التجميع ولا يمكن إصلاحها كلها، يمكنك أيضًا إدراج جميع الدوال التي تحتوي على طلبات غير مباشرة للتجميع في القائمة السوداء. لا يُنصح بذلك لأنّه يؤدي إلى إيقاف عمليات التحقّق من CFI في هذه الدوال، ما يؤدي إلى زيادة مساحة الهجوم.
إيقاف CFI
لم نرصد أي تكلفة إضافية للأداء، لذا لن تحتاج إلى إيقاف CFI. ومع ذلك، إذا كان هناك تأثير على المستخدم، يمكنك إيقاف CFI بشكل انتقائي لوظائف أو ملفات مصدر فردية من خلال توفير ملف قائمة سوداء لأداة التنظيف في وقت الترجمة البرمجية. توجّه القائمة السوداء المترجم البرمجي إلى إيقاف أدوات CFI في المواقع الجغرافية المحدّدة.
يتيح نظام التصميم في Android استخدام القوائم السوداء لكل مكون (ما يتيح لك اختيار ملفات المصدر أو الدوال الفردية التي لن تتلقّى أدوات CFI) لكل من Make وSoong. لمزيد من التفاصيل حول تنسيق ملف القائمة السوداء، يُرجى الاطّلاع على مستندات Clang الخاصة بالمصدر.
التحقق من صحة البيانات
في الوقت الحالي، لا يتوفّر اختبار CTS مخصّص لـ CFI. بدلاً من ذلك، تأكَّد من اجتياز اختبارات CTS سواء كانت ميزة "سلامة التعليمات البرمجية" مفعَّلة أم لا، وذلك للتحقّق من أنّ هذه الميزة لا تؤثّر في الجهاز.