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

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

في Android 8.1، قمنا بتمكين تنفيذ LLVM لـ CFI في حزمة الوسائط. في Android 9، قمنا بتمكين CFI في المزيد من المكونات وكذلك في النواة. يتم تشغيل نظام CFI بشكل افتراضي ولكنك تحتاج إلى تمكين kernel CFI.

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

لمزيد من التفاصيل الفنية حول CFI وكيفية التعامل مع فحوصات التحكم الأمامي الأخرى، راجع وثائق تصميم LLVM .

الأمثلة والمصادر

يتم توفير CFI بواسطة المترجم ويضيف أدوات إلى الملف الثنائي أثناء وقت الترجمة. نحن ندعم CFI في سلسلة أدوات Clang ونظام بناء Android في AOSP.

يتم تمكين CFI افتراضيًا لأجهزة Arm64 لمجموعة المكونات الموجودة في /platform/build/target/product/cfi-common.mk . كما يتم تمكينه مباشرةً في مجموعة من ملفات التكوين/ملفات المخططات الخاصة بمكونات الوسائط، مثل /platform/frameworks/av/media/libmedia/Android.bp و /platform/frameworks/av/cmds/stagefright/Android.mk .

تنفيذ نظام CFI

يتم تمكين CFI افتراضيًا إذا كنت تستخدم Clang ونظام إنشاء Android. نظرًا لأن CFI يساعد في الحفاظ على أمان مستخدمي Android، فلا ينبغي عليك تعطيله.

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

دعم CFI في ملفات makefiles

لتمكين 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. على سبيل المثال، يُظهر تتبع المكدس SIGABRT ويحتوي logcat على سطر حول تكامل تدفق التحكم الذي يجد عدم التطابق.

لإصلاح ذلك، تأكد من أن الدالة التي تم استدعاؤها لها نفس النوع الذي تم الإعلان عنه بشكل ثابت. فيما يلي مثالين لـ CLs:

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

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

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

تعطيل CFI

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

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

تصديق

حاليًا، لا يوجد اختبار CTS خصيصًا لـ CFI. بدلاً من ذلك، تأكد من اجتياز اختبارات CTS مع تمكين CFI أو بدونه للتحقق من أن CFI لا يؤثر على الجهاز.