
إثبات مفهوم لـ CVE-2026-86476: قراءة/كتابة خارج حدود الكومة (heap out-of-bounds) في GStreamer GstVideoMeta عبر إلغاء تسلسل unixfd، مع إعادة إنتاج ASan وسلسلة RCE.
تجاوز سعة المخزن المؤقت في كومة الذاكرة في libgstvideo الخاص بـ GStreamer (gst-plugins-base). تصف بنية GstVideoMeta كيفية توزيع بكسلات إطار الفيديو في الذاكرة. عندما تُفكّ تسلسل هذه الوصفية من نظير unixfd غير موثوق، لا يتحقق أي شيء من أن الأبعاد الهندسية التي تدّعيها تتسع داخل المخزن المؤقت الذي يسندها. يمكن للنظير تعيين خطوات (strides) مفرطة الحجم للمستويات (planes)، فيقرأ المستهلك أو يكتب خارج حدود التخصيص.
| الحقل | القيمة |
|---|---|
| CVE | CVE-2026-86476 |
| الفئة | CWE-125 (قراءة خارج الحدود)، CWE-787 (كتابة خارج الحدود)، يُبلَغ إليها عبر CWE-502 (فك تسلسل بيانات غير موثوقة) |
| المكوّن | libgstvideo: gstvideometa.c، video-frame.c |
| التأثير | قراءة خارج الحدود (مؤكدة)، كتابة خارج الحدود وصولاً إلى تنفيذ الشيفرة (مُبرهَن عليها) |
| المُحفِّز | بنية GstVideoMeta بخطوات مستويات مُلفَّقة، مفكوكة التسلسل من نظير unixfd |
| موضع الخلل | video_orc_convert_I420_BGRA، نواة ORC الخاصة بـ videoconvert |
الخلل هو ثلاثة فحوصات مفقودة تتراكم معاً.
لا يوجد فحص للحجم في فرع الوصفية من خريطة الإطار. تحتوي gst_video_frame_map_id() في video-frame.c على فرعين. ينتهي الفرع الذي لا يحتوي على وصفية بـ if (frame->map[0].size < info->size) goto invalid_size;. أما فرع الوصفية، الذي يُسلَك كلما وُجدت GstVideoMeta، فلا يفرض سوى info->width <= meta->width و info->height <= meta->height. هذان حدّان أدنيان. لا شيء يقارن الأبعاد الهندسية للمستوى بحجم الذاكرة الفعلية.
default_map() يتحقق من بايت واحد لكل مستوى. في gstvideometa.c، تُحلّ ذاكرة المستوى باستخدام gst_buffer_find_memory(buffer, offset, 1, ...). وسيط الحجم هو 1. تؤكد الشيفرة وجود بايت واحد عند إزاحة بداية المستوى، ثم تنسخ الخطوة كما هي. لا يُقارَن المدى الفعلي للمستوى، stride * height، بالتخصيص أبداً. ويوجد تعليق FIXME use plane size مباشرة فوق الفحص.
مُفكِّك التسلسل لا يتحقق من أي نطاق. تتحقق video_meta_deserialize() فقط من أن قارئ البايتات لم ينفد من المدخلات، وأن n_planes <= GST_VIDEO_MAX_PLANES، وأن version != 0. تُنسخ الإزاحات والخطوات مباشرة من التدفق. تُقرأ كل خطوة كـ gint32 موقّعة، لذا تُقبل الخطوة السالبة.
على جانب الاستقبال، تفكّ gst_unix_fd_src_create() تسلسل الوصفيات مقابل مخزن مؤقت فارغ وتُرفق ذاكرة النظير بعد ذلك. لا تُقارَن الخطوة التي تحملها الوصفية وحجم الذاكرة التي تسند المخزن المؤقت أبداً.
يقوم نظير خبيث بتسلسل GstVideoMeta لإطار I420 بأبعاد 320x240 مع تعيين كل خطوة مستوى إلى 1000000. يفكّ المستهلك تسلسلها دون فحص للنطاق، ويُرفق الذاكرة الحقيقية الصغيرة، ويُخطّط الإطار. تُستوفى default_map() لأن البايت الأول من كل مستوى موجود. ثم يقرأ videoconvert كل مستوى إدخال لعدد stride * height من البايتات ويتجاوز حدود التخصيص.
تلك هي القراءة. أما الكتابة فهي صورتها المرآة. تُجري gst_video_frame_copy_plane() عملية memcpy لعدد stride * height من البايتات، لذا عندما يكون مخزن مؤقت صغير يحمل الوصفية الكاذبة هو وجهة النسخ، تكتب memcpy خارج حدود ما يسندها.
ينسخ نقل unixfd المخازن المؤقتة غير المرتبطة بـ FD إلى memfd، لذا تُصادف خط الأنابيب القياسي القراءة عند حدود تعيين mmap (انهيار وتسريب معلومات). أما تشغيل نفس مُفكِّك التسلسل بنفس البايتات غير الموثوقة مقابل وجهة مسندة بكومة الذاكرة فيمارس بدائية الكتابة. يحدد النقل ما إذا كان التجاوز يصيب حدود تعيين أو كومة الذاكرة. لكنه لا يحدد ما إذا كان الخلل موجوداً.
مع الكتابة في كومة الذاكرة، يستبدل إثبات المفهوم مؤشر الدالة free في ترويسة GstMiniObject مجاورة، ويضع سلسلة الأوامر حيث تحط في سجل الوسيط الأول، ويُطلق unref بحيث يُستدعى المؤشر المُلفَّق كـ system().
أي خط أنابيب يقبل مخازن الفيديو المؤقتة عبر unixfd من عملية لا يثق بها بالكامل. فاكّات ترميز الوسائط المعزولة (sandboxed)، وخطوط الأنابيب المنقسمة على نمط البوابات (portal-style)، وأي شيء يستخدم unixfdsrc لاستقبال الإطارات عبر حدود صلاحيات أو ثقة.
مؤكَّد:
libgstreamer-1.0.so.0.2804.0 الخاص بـ Debian (1.28.4-2) مع glibc 2.42.عمليتان عبر مقبس unix، بعناصر قياسية فقط.
fakesrc ! capsfilter ! unixfdsink، يدفع مخزنًا مؤقتًا صالحًا 320x240 I420 بخطوات حقيقية 320/160/160 لكن مع GstVideoMeta خطوات مستوياته 1000000.unixfdsrc ! videoconvert ! capsfilter ! fakesink، حيث يُجبر capsfilter على تحويل I420 إلى BGRA بحيث يجب على videoconvert قراءة الإطار وفق خطوات الـ meta.| التشغيل | خطوات الـ Meta | النتيجة |
|---|---|---|
| 1 | 1000000 / 1000000 / 1000000 | قراءة خارج الحدود تحت ASan في video_orc_convert_I420_BGRA |
| 2 | 320 / 160 / 160 | اكتمال طبيعي، يصل المخزن المؤقت إلى fakesink |
ابنِ GStreamer باستخدام -Db_sanitize=address لإثبات جانب القراءة. يعمل إثبات مفهوم RCE على بناء قياسي، لأن بناء ASan يتوقف عند أول memcpy يتجاوز الحدود.
default_map()، استبدل gst_buffer_find_memory(..., 1, ...) ذا البايت الواحد بفحص مقابل امتداد المستوى stride * height.gst_video_frame_map_id() نفس فحص invalid_size الموجود في الفرع الذي لا يحتوي على meta.video_meta_deserialize()، افحص نطاق الإزاحات والخطوات مقابل المخزن المؤقت، وارفض الخطوات السالبة.أي واحد من هذه يكسر السلسلة. والثلاثة معًا هي الإصلاح الصحيح لبنية تعبر حدود الثقة.