
CVE-2026-74970، تجاوز عزل الموقع في Fission في Firefox WebRender
يحتوي هذا الدليل على هيكل إعادة إنتاج أمين لثغرة تجاوز تفويض عزل الموقع في Fission ضمن Firefox WebRender. في ظل Fission، تمتلك كل عملية محتوى نطاق PipelineId خاصًا بها، لكن شيفرة عملية GPU التي تسجّل PipelineId قابلاً للتركيب لم تتحقق قط من أن نطاق PipelineId ينتمي إلى العملية المُرسِلة. لذلك يمكن لعملية محتوى مخترقة أن تسجّل PipelineId في نطاق عملية أخرى وتستبدل خط أنابيب الصور غير المتزامن (async image pipeline) الخاص بذلك الأصل في المدير المشترك.
كل ما هنا يستهدف شجرة Firefox مبنية محليًا بمحتوى اصطناعي فقط. لا يهاجم هذا المحتوى أي نظام حي أو تابع لطرف ثالث، ولا يتصل به، ولا يستقصيه، ولا يشير إليه. وهو ليس تنفيذًا عن بُعد للشيفرة ولا إفسادًا للذاكرة؛ نموذج التهديد هو عملية محتوى مخترقة مسبقًا.
مستودع PoC المطابق: https://github.com/defineid/Trespasser
الشرح المكتوب: writeup-webrender-pipelineid.html في هذه المحفظة.
| الحقل | القيمة |
|---|---|
| CVE | CVE-2026-74970 |
| المكوّن | Core / Graphics: WebRender |
| الفئة | تجاوز عزل الموقع |
| CWE | 862 (غياب التفويض) · 284 (تحكم وصول غير سليم) |
| تم الإصلاح في | Firefox 154 · ESR 153.1 |
| العلّة | https://bugzilla.mozilla.org/show_bug.cgi?id=2056558 |
| مستودع PoC | https://github.com/defineid/Trespasser |
في ظل Fission، يخصص WebRender لكل عملية محتوى نطاق PipelineId خاصًا بها. من المتوقع أن يرفض جسر عملية GPU أي مورد لا ينتمي نطاقه إلى العملية التي أرسلته، بحيث لا تستطيع عملية محتوى واحدة مخاطبة موارد العرض الخاصة بعملية أخرى أو استبدالها.
كانت WebRenderBridgeParent::AddPipelineIdForCompositable، التي تعمل في عملية GPU، تتحقق من خط الأنابيب الوارد مقابل حارس الجذر/WebRender ومقابل مجموعة خطوط الأنابيب المسجّلة الخاصة بهذا الجسر، لكنها لم تستدعِ قط MatchesNamespace على PipelineId. مسارات مفتاح الصورة ومفتاح الخط ومفتاح الكتلة (blob-key) في الملف نفسه كلها تُجري فحص الملكية ذلك عبر MatchesNamespace؛ أما مسار PipelineId فببساطة لم يفعل. وبالمثل فإن RemovePipelineIdForCompositable غير مُتحقَّق منها، لذا توجد الثغرة نفسها في ملكية النطاق عند الإزالة.
في المصب، تحمي AsyncImagePipelineManager::AddAsyncImagePipeline تفرد خط الأنابيب باستخدام MOZ_ASSERT فقط، والذي يُترجم إلى عملية لا تفعل شيئًا في إصدارات الإصدار النهائي (release builds). لذا عندما يصل PipelineId مزوّر من نطاق آخر إلى AsyncImagePipelineManager المشترك، لا يرفض المدير التكرار: بل يستبدل بصمت إدخال خط أنابيب الصور غير المتزامن الموجود لأصل الضحية.
وعند ربطها معًا، تستطيع عملية محتوى مخترقة A تزوير PipelineId ينتمي نطاقه إلى عملية المحتوى B (أصل مختلف، في عملية منفصلة في ظل Fission)، وإرساله عبر مسار تسجيل المكوّنات القابلة للتركيب، فيقبله عملية GPU ويطمس خط أنابيب الصور الخاص بـ B في المدير المشترك.
عملية محتوى مخترقة مسبقًا في ظل Fission. يُفترض أن المهاجم يملك بالفعل تنفيذ شيفرة في عملية محتوى واحدة (على سبيل المثال عبر علّة منفصلة في عملية المحتوى) وأنه قادر على إرسال رسائل WebRender IPC بحقول يختارها المهاجم. هذا العيب هو التفويض المفقود الذي يتيح لتلك العملية الوصول عبر حدود عزل الموقع إلى موارد العرض الخاصة بأصل آخر.
هذا تجاوز تفويض لعزل الموقع في Fission، وليس إفسادًا للذاكرة. الأثر هو ارتباك في العرض عبر الأصول وتعطّل خدمة عبر التبويبات. المستهلكون في المصب محميون بفحوصات null ومُعدّون بعدّ المراجع، لذا لا يؤدي ذلك إلى تنفيذ شيفرة. يجب ألا يدّعي PoC تنفيذ شيفرة، وهو لا يفعل.
README.md — هذا الملف.forge-namespace.patch — رقعة بحثية لشجرة Firefox محلية. على جانب
المحتوى، محمية بـ XRE_IsContentProcess() ومتغير البيئة
MOZ_POC_FORGE_WR_NAMESPACE، تقوم بتزوير نطاق PipelineId الصادر.
وعلى جانب العملية الأم لـ GPU، تطبع تشخيصًا ACCEPTED
عند تسجيل PipelineId عبر النطاقات، مما يُظهر أن الفحص المفقود سمح بمروره.poc.html — حالة اختبار مجردة على جانب المحتوى: لوحة WebGL (بحيث تُنشأ
خط أنابيب صور غير متزامن) بالإضافة إلى iframe عبر الأصل وخارج العملية (بحيث
تضعها Fission في عملية محتوى منفصلة). على بناء قياسي تُعرض بشكل طبيعي
ولا تفعل شيئًا ملحوظًا.victim.html — الأصل الثاني الاصطناعي الذي يُحمّله الـ iframe. إنها صفحة
بسيطة خالية من السكربتات ومهمتها الوحيدة أن تكون مستندًا حيًا عبر الأصل، بحيث
تهبط تحت Fission في عملية المحتوى الخاصة بها مع نطاق PipelineId الخاص بها
(النطاق الذي يستهدفه تسجيل مزوّر).poc-render.png — لقطة شاشة لـ poc.html محمّلة في Firefox 140.11.0esr
القياسي المحلي. تُظهر صفحة الاختبار تُعرض بشكل طبيعي: ترسم لوحة WebGL
إطارها ويُحمّل الـ iframe خارج العملية victim.html. تُظهر فقط عرضًا
حميدًا. التجاوز نفسه يتطلب الرقعة البحثية وبناءً قابلًا للاستغلال ولم
يُشغَّل هنا (انظر حالة التحقق).هذه الخطوات مُقصد أن ينفذها المؤلف مقابل نسخة Firefox محلية. لم يُنفَّذ أي شيء هنا مقابل Firefox في هذه البيئة (انظر حالة التحقق أدناه).
اسحب شجرة مصدر Firefox قابلة للاستغلال (قبل 154 / قبل ESR
153.1) وتأكد من تفعيل Fission (fission.autostart = true، وهو
الافتراضي في البناءات الحديثة).
طبّق الرقعة البحثية من أعلى شجرة المصدر:
patch -p1 < forge-namespace.patch
يمسّ التصحيح الملف gfx/layers/wr/WebRenderBridgeChild.cpp (جانب المحتوى،
يزوّر مساحة الأسماء) والملف gfx/layers/wr/WebRenderBridgeParent.cpp
(جانب عملية GPU، يطبع التشخيص ACCEPTED). كلا التغييرين خاملان
ما لم تتحقق شروط الحماية.
ابنِ بشكل طبيعي (./mach build).
قدّم هذا الدليل بحيث يُحمّل إطار iframe خارج العملية في poc.html الملف
المرافق victim.html من نطاق قابل للتسجيل مختلف فعليًا، بحيث يضع
Fission إطار iframe في عملية محتواه الخاصة. على سبيل المثال،
python3 -m http.server 8000 --bind :: وحمّل إطار iframe من
http://victim.localtest.me:8000/victim.html. راجع التعليق المضمّن في
poc.html.
شغّل البناء مع تعيين متغير التزوير وحمّل صفحة الاختبار:
MOZ_POC_FORGE_WR_NAMESPACE=999999 ./mach run --temp-profile poc.html
999999 هي قيمة مساحة أسماء عشوائية اختيرت بحيث لا تنتمي إلى أي عملية حقيقية؛ وهي تجعل التسجيل المزوّر عبر مساحات الأسماء واضحًا في السجل.
على إصدار مُعرَّض للثغرة مع تطبيق الترقيع، يقبل أب عملية GPU ويسجّل PipelineId تنتمي مساحة أسمائه إلى أي عملية من هذا القبيل، ويطبع (هذا هو الشكل الدقيق الموضح في التقرير):
[POC-2056558] ACCEPTED cross-namespace PipelineId:
sender-namespace=4 pipeline-namespace=999999 (should have been rejected)
sender-namespace هي مساحة الأسماء المملوكة بشكل شرعي من قِبل عملية المحتوى المُرسِلة؛ بينما pipeline-namespace هي القيمة المُزوَّرة. القيمتان المختلفتان، بالإضافة إلى حقيقة قبول التسجيل، هما بيت القصيد: فحص الملكية الذي كان سيرفضه مفقود.
على بناء مُصلَح (154 / ESR 153.1 وما بعده)، تُرفَض الرسالة نفسها مع IPC_FAIL ولا يُطبَع أي سطر ACCEPTED من هذا القبيل، لأن مسار PipelineId يفرض الآن MatchesNamespace.
فرض، على مسار PipelineId، نفس فحص ملكية مساحة الأسماء الذي تستخدمه مسارات مفتاح المورد بالفعل:
if (!MatchesNamespace(aPipelineId)) {
return IPC_FAIL(this, "PipelineId namespace does not belong to the sending process");
}
يُطبَّق هذا في AddPipelineIdForCompositable (وبشكل متماثل على
RemovePipelineIdForCompositable)، فيرفض أي PipelineId لا تنتمي مساحة أسمائه
إلى العملية المُرسِلة، مما يُغلق مسار التجاوز.