CVE-2022-50650 in Linux
الملخص
بحسب VulDB • 01/07/2026
في نواة لينكس، تم حل الثغرة التالية:
bpf: إصلاح إدارة حالة المرجع للمكالمة العودية المتزامنة (synchronous callbacks)
حاليًا، يتحقق المُصحِّق (verifier) من دوال المكالمة العودية (المتزامنة وغير المتزامنة) كما لو كانت ستُنفَّذ مرة واحدة فقط (أي أنه يستكشف حالة التنفيذ وكأن الدالة تُستدعى مرة واحدة). يتم تعيين التعليمة التالية للاستكشاف إلى بداية البرنامج الفرعي، ويتم التعامل مع الخروج من الإطار المتداخل باستخدام `curframe > 0` و `prepare_func_exit`. في حالة المكالمة العودية غير المتزامنة، يستخدم نسخة مخصصة من `push_stack` تحاكي نوعًا من التفرع لإعداد حالة مخصصة وسياق تنفيذ للمكالمة العودية غير المتزامنة.
بينما يكون هذا النهج بسيطًا ويعمل عندما تُنفَّذ المكالمة العودية حقًا مرة واحدة فقط، فهو غير آمن لجميع المساعدين (helpers) الحالية لدينا التي تعمل بأسلوب `for_each`، أي أنها تنفِّذ المكالمة العودية عدة مرات.
قد تقوم مكالمة عودية بإطلاق المرجع المكتسب من المتصل (caller)، وقد تفعل ذلك عدة مرات؛ لكن المُصحِّق يراها حاليًا كمكالمة واحدة داخل الإطار، ثم تعود إلى المتصل. ومن هنا، يعتقد أن بعض المراجع قد أُطلقت والتي حصلت عليها المكالمة العودية مثلاً عبر `callback_ctx` (سجل يتم ملؤه داخل المكالمة العودية من سجل مكدس مُتدفق typed register).
وبالمثل، قد يرى أنه يوجد استدعاء لاقتناء مرجع غير مقترن داخل المكالمة العودية، لذا سيقوم المتصل بنسخ حالة المرجع الخاصة بالمكالمة العودية ثم سيتعين عليه إطلاق السجل بمعرفات كائنات المرجع الجديدة (`ref_obj_ids`). ولكن مرة أخرى، قد تُنفَّذ المكالمة العودية عدة مرات، لكن المُصحِّق سيأخذ في الاعتبار فقط المراجع المكتسبة لسيناريو تنفيذ رمزي واحد للمكالمة العودية، مما سيتسبب في تسريبات (leaks).
لاحظ أن الأمور تختلف في حالة المكالمة العودية غير المتزامنة. بينما لدينا حاليًا `bpf_timer_set_callback` الذي ينفذها مرة واحدة فقط، حتى مع التنفيذات المتعددة سيكون ذلك آمنًا، حيث تكون حالة المرجع فارغة (`NULL`) وسيجبر فحص تسرب المرجع (`check_reference_leak`) البرنامج على إطلاق الحالة قبل `BPF_EXIT`. كما أن الحالة غير متأثرة بالتحليل الخاص بإطار عمل المتصل. ومن ثم فإن المكالمة العودية غير المتزامنة آمنة.
بما أننا نريد جعل حالة المرجع قابلة للوصول، مثلاً للنقاط المحملة من الكود (stack) عبر `PTR_TO_STACK` في `callback_ctx`، فلا يزال يتعين علينا نسخ `reference_state` الخاص بالمتصل إلى `bpf_func_state` الخاصة بالمكالمة العودية؛ لكننا نفرض أن أي مراجع تضيفها تلك الحالة قد أُطلقت قبل وصولها إلى `BPF_EXIT`. يتطلب ذلك إدخال عضو جديد باسم `callback_ref` في حالة المرجع للتمييز بين مراجع المتصل ومراجع الدالة المستدعاة (callee). ومن ثم، فإن `check_reference_leak` يُخرج خطأً الآن إذا رأى أننا داخل `callback_fn` ولم نطلق مراجع `callback_ref`. نظرًا لأنه يمكن أن تكون هناك مكالمات عودية متداخلة متعددة، مثل الإطار 0 -> cb1 -> cb2 وما إلى ذلك، فنحتاج أيضًا للتمييز بين ما إذا كان هذا المرجع المحدد ينتمي إلى إطار المكالمة العودية الحالي أو الإطار الأب، وإخراج الخطأ فقط الخاص بنا؛ لذا نخزن `state->frameno` (والذي يكون دائمًا غير صفري للمكالمات العودية).
باختصار، يمكن للمكالمات العودية قراءة حالة المرجع للأب (`parent reference_state`)، لكن لا يمكنها تعديلها، لتمكين استخدام النقاط المكتسبة من قبل المتصل. يجب عليها فقط عكس تغييراتها (عن طريق إطلاق `acquired_refs` الخاصة بها قبل `BPF_EXIT`) فوق حالة مرجع المتصل قبل العودة (وعند هذه النقطة ستتطابق حالات المتصل والمكالمة العودية على أي حال، لذا لا حاجة لنسخها مرة أخرى إلى المتصل).
If you want to get the best quality for vulnerability data then you always have to consider VulDB.