سلامة تدفق التحكّم

اعتبارًا من عام 2016، كان حوالي% 86 من جميع الثغرات الأمنية في Android مرتبطًا بأمان الذاكرة. يستغل المهاجمون معظم الثغرات الأمنية من خلال تغيير مسار التحكّم العادي في التطبيق لتنفيذ أنشطة ضارة عشوائية بجميع امتيازات التطبيق المستغَل. وسلامة مسار التحكّم (CFI) هي آلية أمان تمنع إجراء تغييرات على الرسم البياني الأصلي لمسار التحكّم في ملف ثنائي مترجَم، ما يجعل تنفيذ هذه الهجمات أكثر صعوبة.

في نظام التشغيل Android 8.1، فعّلنا تنفيذ CFI في حزمة الوسائط باستخدام LLVM. في نظام التشغيل Android 9، فعّلنا ميزة "التحكّم في التدفق المتكامل" في المزيد من المكوّنات، بالإضافة إلى النواة. يكون وضع CFI على مستوى النظام مفعَّلاً تلقائيًا، ولكن عليك تفعيل وضع CFI على مستوى النواة.

تتطلّب ميزة CFI في LLVM إجراء عملية تجميع باستخدام تحسين وقت الربط (LTO). تحتفظ عملية التحسين في وقت الربط بتمثيل رمز البت LLVM لملفات الكائنات إلى حين الربط، ما يتيح للمترجم البرمجي التفكير بشكل أفضل في التحسينات التي يمكن إجراؤها. يؤدي تفعيل ميزة LTO إلى تقليل حجم الملف الثنائي النهائي وتحسين الأداء، ولكنّه يزيد من وقت الترجمة البرمجية. أثناء الاختبار على Android، أدّى الجمع بين LTO وCFI إلى حدوث زيادة طفيفة في حجم الرمز البرمجي والأداء، وفي بعض الحالات، تحسّن كل منهما.

للحصول على مزيد من التفاصيل الفنية حول CFI وكيفية التعامل مع عمليات التحقّق الأخرى من التحكّم المسبق، يُرجى الاطّلاع على مستندات تصميم LLVM.

أمثلة ومصدر

يوفّر المترجم البرمجي CFI ويضيف أدوات إلى الرمز الثنائي أثناء وقت الترجمة. نوفّر دعمًا لميزة "التحكّم في التدفق المتكامل" في سلسلة أدوات Clang ونظام إنشاء Android في مشروع 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 ونظام الإصدار في Android. بما أنّ ميزة "التحكّم في التدفق" تحافظ على أمان مستخدمي Android، يجب عدم إيقافها.

في الواقع، ننصحك بشدة بتفعيل CFI لمكوّنات إضافية. والمرشّحون المثاليون هم الرموز البرمجية الأصلية ذات الامتيازات أو الرموز البرمجية الأصلية التي تعالج مدخلات المستخدم غير الموثوق بها. إذا كنت تستخدم clang ونظام الإصدار في Android، يمكنك تفعيل CFI في المكوّنات الجديدة من خلال إضافة بضعة أسطر إلى ملفات makefiles أو ملفات 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_SANITIZE أنّ CFI هي أداة التنظيف أثناء عملية الإنشاء.
  • يؤدي LOCAL_SANITIZE_DIAG إلى تفعيل وضع التشخيص في CFI. يطبع وضع التشخيص معلومات إضافية لتصحيح الأخطاء في logcat أثناء حدوث الأعطال، ما يكون مفيدًا أثناء تطوير إصداراتك واختبارها. احرص على إزالة وضع التشخيص من إصدارات الإنتاج.
  • تسمح السمة LOCAL_SANITIZE_BLACKLIST للمكوّنات بإيقاف تتبُّع 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 سطرًا حول رصد عدم تطابق في سلامة تدفّق التحكّم.

لحلّ هذه المشكلة، تأكَّد من أنّ الدالة التي تم استدعاؤها لها النوع نفسه الذي تم تعريفه بشكل ثابت. في ما يلي مثالان على قوائم التغيير:

هناك مشكلة أخرى محتملة وهي محاولة تفعيل CFI في رمز يحتوي على استدعاءات غير مباشرة للتجميع. وبما أنّ رمز التجميع غير مكتوب، يؤدي ذلك إلى عدم تطابق النوع.

لحلّ هذه المشكلة، أنشئ برامج تضمين للتعليمات البرمجية الأصلية لكل استدعاء تجميع، ومنح برامج التضمين توقيع الدالة نفسه الذي يمنحه المؤشر الذي يستدعي. ويمكن بعد ذلك أن يستدعي برنامج التغليف رمز التجميع مباشرةً. سيؤدي ذلك إلى حلّ المشكلة لأنّ الفروع المباشرة لا يتم تزويدها بأدوات CFI (لا يمكن إعادة توجيهها في وقت التشغيل، وبالتالي لا تشكّل خطرًا أمنيًا).

إذا كان هناك عدد كبير جدًا من دوال التجميع ولا يمكن إصلاحها كلها، يمكنك أيضًا إدراج جميع الدوال التي تحتوي على طلبات غير مباشرة للتجميع في القائمة السوداء. لا يُنصح بذلك لأنّه يؤدي إلى إيقاف عمليات التحقّق من سلامة تدفق التحكّم في هذه الدوال، ما يؤدي إلى زيادة مساحة الهجوم.

إيقاف ميزة "التحكّم في التدفق"

لم نرصد أي تكلفة إضافية للأداء، لذا لن تحتاج إلى إيقاف CFI. ومع ذلك، إذا كان هناك تأثير على المستخدم، يمكنك إيقاف CFI بشكل انتقائي لوظائف أو ملفات مصدر فردية من خلال توفير ملف قائمة سوداء خاص بأداة التنظيف في وقت الترجمة البرمجية. توجّه القائمة السوداء المترجم البرمجي إلى إيقاف أدوات CFI في المواقع الجغرافية المحدّدة.

يتيح نظام الإصدار في Android استخدام القوائم السوداء لكل مكون (ما يتيح لك اختيار ملفات المصدر أو الدوال الفردية التي لن تتلقّى أدوات CFI) لكل من Make وSoong. لمزيد من التفاصيل حول تنسيق ملف القائمة السوداء، يُرجى الاطّلاع على مستندات Clang المصدر.

التحقُّق

لا تتوفّر حاليًا اختبارات CTS مخصّصة لـ CFI. بدلاً من ذلك، تأكَّد من اجتياز اختبارات CTS سواء كانت ميزة "سلامة الجهاز" مفعَّلة أو غير مفعَّلة للتحقّق من أنّها لا تؤثّر في الجهاز.