
CVE-2026-39259
%sالمشروع: https://github.com/alexfru/SmallerC
لا يفرض تنفيذ الدالة scanf في SmallerC حدًا أعلى لقراءة السلاسل النصية عند استخدام %s أو %[ دون تحديد عرض حقل صريح في سلسلة التنسيق. ستستمر بيئة التشغيل في الكتابة إلى المخزن المؤقت الهدف حتى تواجه مسافة بيضاء أو نهاية الملف (EOF)، بغض النظر عن الحجم الفعلي المخصص للمخزن المؤقت. أي بايت تتجاوز الحدود تهبط مباشرة على المكدس، مما يعيد كتابة ما وضعه المترجم فوق المخزن المؤقت (المتغيرات المحلية، السجلات المحفوظة، عنوان العودة).
هذه ليست فئة ثغرات جديدة. توثيق قراءات scanf غير المحدودة للسلاسل يعود إلى الأيام الأولى للغة C، وأي أداة تحليل ثابتة كفؤة ستشير إليها. ما يجعل هذا الأمر يستحق الإبلاغ في سياق SmallerC تحديدًا هو البيئة المستهدفة. صُمم SmallerC لنظام DOS والأهداف المضمنة (bare-metal) – وهي منصات لا توفر بطبيعتها canaries المكدس، أو ASLR، أو بتات NX، أو أي من وسائل التخفيف التي تجعل الاستغلال صعبًا على الأنظمة الحديثة. نفس الأولية التي تتطلب جهدًا بحثيًا كبيرًا لتحويلها إلى استغلال عملي على ثنائي Linux محصن تصبح أكثر قابلية للتنفيذ على برنامج DOS يعمل على مكدس مسطح يمكن التنبؤ به.
المخزن المؤقت هو 16 بايت. الإدخال هو 20 بايت غير مسافة بيضاء. تحويل %s لا يحتوي على محدد عرض، لذا يقرأ sscanf كل 20 بايت بالإضافة إلى فاصل ختامي (null terminator) – إجمالي 21 بايت – في تخصيص بحجم 16 بايت. الـ 5 بايتات التي تتجاوز الحدود تفسد ذاكرة المكدس المجاورة. ما الذي يتم إفساده بالضبط يعتمد على تخطيط المكدس الذي يقرره المترجم لتلك الدالة المحددة، لكن الكتابة فوق نفسها حتمية وغير مشروطة في كل مرة يعمل فيها مسار الكود هذا مع هذا الإدخال.
إثبات المفهوم
#include <stdio.h>
#include <string.h>
/*
* Build with SmallerC targeting DOS or bare-metal
* Demonstrates unbounded %s write past a fixed stack buffer
*
* buffer is 16 bytes payload is 20 non-whitespace bytes
* sscanf writes 21 bytes (20 + null terminator) into buffer
* corrupting 5 bytes of adjacent stack memory
*
* To observe the corruption inspect stack memory after the call:
* the 5 bytes immediately above buffer will contain 'A' (0x41)
*/
int main() {
char buffer[16];
char canary[8];
memset(buffer 0x00 sizeof(buffer));
memset(canary 0xCC sizeof(canary)); /* marker to detect overwrite */
printf("[*] canary before: ");
for (int i = 0; i < 8; i++) printf("%02x " (unsigned char)canary[i]);
printf("\n");
sscanf("AAAAAAAAAAAAAAAAAAAA" "%s" buffer); /* 20 bytes into 16-byte buffer */
printf("[*] canary after: ");
for (int i = 0; i < 8; i++) printf("%02x " (unsigned char)canary[i]);
printf("\n");
if (memcmp(canary "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" 8) != 0)
printf("[!] stack corruption confirmed canary overwritten\n");
else
printf("[-] canary intact (stack layout placed it elsewhere)\n");
return 0;
}
المخرجات المتوقعة على بناء متأثر:
[*] canary before: cc cc cc cc cc cc cc cc
[*] canary after: 41 41 41 41 41 cc cc cc
[!] stack corruption confirmed canary overwritten
يعتمد وضع canary بالنسبة للمخزن المؤقت على تخطيط المكدس للمترجم. إذا أظهرت المخرجات أن canary سليم، فإن الكتابة فوق ما زالت تحدث – إنها تهبط على شيء آخر فوق المخزن المؤقت. قم بتعديل أداة إعادة الإنتاج عن طريق فحص إطار المكدس الفعلي باستخدام مصحح لتحديد مكان هبوط الـ 5 بايتات المفسدة.
تصحيح جدير بالذكر: بعض التقارير في هذه الفئة من الثغرات تحاول إظهار التحكم في عنوان العودة بإلحاق عنوان هدف بعد بايت فارغ (null byte) في الحمولة، مثل "AAAAAAAAAAAAAAAAAAA\x00\x90\x04\x08" هذا لا يعمل. تحويل %s في sscanf يعامل \x00 كفاصل سلسلة ويتوقف عن القراءة فور مواجهته. البايتات التي تلي البايت الفارغ لا تتم معالجتها أبدًا. إظهار التحكم الفعلي في عنوان العودة يتطلب توصيل الكتابة فوق دون بايت فارغ في الجزء الحرج من الحمولة، الأمر الذي يتطلب بدوره معرفة التخطيط الدقيق للمكدس للثنائي الهدف – المسافة من المخزن المؤقت إلى عنوان العودة المحفوظ، وما إذا كان المترجم قد أدرج أي حشو، وأية قيود محاذاة مطبقة. لا شيء من ذلك يظهر تلقائيًا من أداة إعادة الإنتاج هذه.
ما تثبته أداة إعادة الإنتاج بشكل نظيف هو الأولية الأساسية للفساد. الكتابة خارج الحدود حقيقية وقابلة للتكرار ولا تعتمد على أي حالة سباق أو توقيت. على هدف DOS أو مضمن حيث تخطيط المكدس ثابت ويمكن التنبؤ به عبر عمليات البناء، فإن الانتقال من هذه الأولية إلى استغلال عملي هو جهد بحثي واقعي وليس تمرينًا نظريًا.
السيناريو المتأثر ضيق لكنه غير مصطنع. يجب أن يكون البرنامج مبنيًا مع SmallerC، ويستخدم تحليل scanf مع محدد %s أو %[ غير محدود، ويكتب في مخزن مؤقت ثابت الحجم على المكدس، ويقبل مدخلات من مصدر يمكن للمهاجم التأثير عليه. جميع الشروط الأربعة يجب أن تتحقق في وقت واحد. البرامج التي تستخدم عرض حقول صحيح (مثل %15s لمخزن char[16]) ليست متأثرة. البرامج التي لا تحلل مدخلات يتحكم فيها المهاجم ليست متأثرة. المشكلة هي عيب في كيفية معالجة بيئة تشغيل SmallerC للقيد المفقود للعرض، لكنها تصبح مصدر قلق أمني فقط عندما يعرض كود التطبيق ذلك العيب لمدخلات غير موثوقة.
على جانب التطبيق، الإصلاح واضح: حدد عرض حقل يترك مساحة للفاصل الختامي: %15s لمخزن بحجم 16 بايت، %63s لمخزن بحجم 64 بايت. هذه ممارسة C قياسية ومدعومة بالكامل بواسطة صيغة سلسلة التنسيق. على جانب مشروع SmallerC، العمل الأكثر دوامًا هو إضافة اختبارات انحدار (regression tests) تغطي سلوك %s و%[ المحدود وغير المحدود عبر scanf وsscanf وfscanf، والتحقق من أن عروض الحقول الصريحة يتم احترامها فعليًا في التنفيذ، وتوثيق النمط غير الآمن بشكل بارز. تشخيص على مستوى المترجم يحذر عندما يظهر %s أو %[ بدون عرض حقل في سلسلة تنسيق نصية سيمنع هذه الفئة من الأخطاء بشكل استباقي وسيكون إضافة ذات معنى للأدوات.
درجة الخطورة متوسطة (Medium) عندما يصل الإدخال الخارجي إلى مسار الكود الضعيف. وتنخفض إلى منخفضة (Low) عندما يكون الإدخال محليًا أو غير مميز. البيئة المستهدفة – وتحديدًا غياب وسائل تخفيف الاستغلال الحديثة على المنصات المخصصة لـ SmallerC – هو ما يفصل هذا عن نصيحة عامة "لا تستخدم scanf غير المحدودة" ويجعله يستحق الإبلاغ على مستوى المشروع بدلاً من معاملته فقط كإساءة استخدام على مستوى التطبيق.
الائتمان: يوسف وزني (Yousif Wazni)