
متغير اصطناعي من تجاوز سعة المخزن المؤقت للمكدس (CWE-120) من CVE-2020-8597 (pppd EAP) كهدف للتحليل الثابت باستخدام CodeQL.
برنامج C مُعرَّض للثغرات عن قصد، بحجم ~170 سطرًا، يُستخدم كـ هدف للتحليل الساكن CodeQL. إنه يعيد إنتاج فئة الخطأ الخاصة بـ CVE-2020-8597 — تجاوز سعة المخزن المؤقت على المكدس في rhostname الخاص بـ EAP في pppd (CWE-120) — في برنامج لا يشترك في أي من أسماء دوال pppd، أو عمق الاستدعاء، أو بنية الإرسال.
الهدف هو اختبار العمومية: استعلام CodeQL المكتوب لالتقاط ثغرة pppd يجب أن ينطلق أيضًا على هذا البرنامج، دون تعديل. إذا فعل ذلك، فإن الاستعلام يعبّر عن فئة الخطأ بدلًا من شكل الكود الأصلي.
هذا البرنامج غير آمن عن قصد وموجود فقط لأغراض التحليل. لا تقم بنشره. الثغرة التي يحاكيها علنية (CVE-2020-8597، أُفصح عنها في 2020).
يُنسخ طول مشتق من المهاجم إلى مخزن مؤقت ثابت الحجم، دون حارس يربط ذلك الطول بحجم المخزن المؤقت.
في كل حالة يوجد فحص للحدود — لكنه يفشل فقط في ربط الكميتين المهمتين. هناك طريقتان بالضبط للخطأ في ذلك، والبرنامج يحتوي على واحدة من كل نوع:
sizeof(dest). يمنع القراءة الزائدة، ولا يفعل شيئًا بشأن الكتابة الزائدة. (handle_hello)sizeof(dest) — يبدو تمامًا كحد للمخزن المؤقت — لكنه يقيّد متغيرًا مختلفًا عن الذي يُستخدم كطول النسخ. (handle_stat)الثاني هو الأصعب، وهو ما يمثله فحص pppd الميت vallen >= len + sizeof(rhostname): مقارنة تذكر حجم الوجهة بينما تقيّد شيئًا ليس طول النسخ. الاستعلام الذي يسأل فقط "هل تذكر بعض المقارنات هنا sizeof(dest)؟" يُسكَت به.
| pppd / CVE-2020-8597 | هذا المشروع | |
|---|---|---|
| المصدر | read() على واصف PPP | recvfrom() على مقبس UDP |
| الإرسال | struct protent *protocols[] عام، مطابقة خطية على رقم البروتوكول | const struct frame_op ops[] محلي للملف، مطابقة خطية على وسم من بايت واحد |
| العمق إلى الحوض | get_input → (*input) → eap_input → eap_request | dispatch_frame → (*handle) → handle_hello |
| الوجهة | char rhostname[256] | char name[64] |
| الحارس الخاطئ | vallen مقيّد بـ len الخاص بالحزمة | vlen مقيّد بـ plen الخاص بالإطار |
كلاهما يحتفظ بالخاصية الوحيدة التي تجعل هذا تدفق بيانات، وليس grep: استدعاء غير مباشر عبر جدول مؤشرات دوال بين المصدر والحوض.
| المعالج | السطر | الفحص الموجود | الحكم |
|---|---|---|---|
handle_hello() | الحوض عند :80 | vlen > plen - 2 — قيمة صحيحة، حد خاطئ | يجب أن ينطلق |
handle_echo() | النسخ عند :106 | vlen >= sizeof(buf) — كلاهما صحيح | يجب أن يبقى صامتًا — ضابط سلبي |
handle_stat() | الحوض عند :145 | hlen >= sizeof(report) — حد صحيح، قيمة خاطئة | يجب أن ينطلق |
handle_stat هو الحالة المميّزة. فحصه يذكر sizeof(report)، لذا فإن استعلامًا يقبل أي مقارنة تذكر حجم الوجهة يعامله كمحمي ويفوّت الثغرة. لالتقاطه يلزم مقارنة القيمة التي تُفحص بـ القيمة المستخدمة كطول النسخ — ترقيم القيم العام. احذف ذلك من الاستعلام وسيصبح هذا المعالج نتيجة سلبية خاطئة بينما يحتفظ كل موقع آخر بحكمه.
حزمة UDP واحدة = إطار واحد:
[ type : 1 ] [ length : 2, big-endian ] [ value : length bytes ]
type 0x01 → hello، 0x02 → echo، 0x03 → stat. إطار hello بطول معلن بين 65 و~2045 يتجاوز سعة name[64]. إطار stat يحمل بدلًا من ذلك طولين من بايت واحد — طول رأس وطول جسم — وأي طول جسم فوق 32 يتجاوز سعة report[32]، مهما قال طول الرأس.
make # gcc -Wall -Wextra -O0 -g -o tlv_server tlv_server.c
Linux/POSIX (مقابس BSD). يُبنى نظيفًا دون تحذيرات.
يتتبع CodeQL عملية ترجمة حقيقية، لذا ابنِ من حالة نظيفة:
make clean
codeql database create db --language=cpp --command="make"
# or, without the clean step:
codeql database create db --language=cpp --command="make -B"
ثم شغّل استعلام الجزء 3 مقابل db؛ يجب أن يبلّغ عن memcpy في handle_hello وتلك الموجودة في handle_stat، ويبقى صامتًا بشأن handle_echo. الاستعلام وتعليمات تشغيله موجودة في codeql/.
يتغير المصدر كلما أُضيف معالج، لذا أعد بناء قاعدة البيانات — يلتقط CodeQL لقطة من الكود عند وقت database create ولن يرى db/ الموجود كودًا جديدًا.