
تراجع لثلاثة إصلاحات منشورة لـ f_hid (بما في ذلك CVE-2026-31721 وCVE-2026-31606) إلى نواة Android الخاصة بالمورّد Linux 4.14.190 التي وصلت إلى نهاية العمر الافتراضي، مع سجلات تحقق على الجهاز.
ثلاثة إصلاحات لملف drivers/usb/gadget/function/f_hid.c، نُقلت يدويًا إلى نواة Android vendor بإصدار 4.14.190 وتم التحقق منها على الجهاز الذي تعمل عليه.
الإصدار 4.14 وصل إلى نهاية عمره (end-of-life). لا يوجد أي backport مستقر لأي من هذه الإصلاحات، ولن يأتي أي منها. وهذا هو السبب الكامل لوجود هذا المستودع: الجهاز ملكي، وأحد هذه العيوب كان يعيد تشغيله.
وللدقة بشأن من يصون ماذا: شجرة NetHunter التي بُني هذا منها
(AzeAstro/samsung_kernel_sm7125، الفرع
OneUI-6) تُصان فعليًا وبشكل نشط، وهذا العمل ليس محل تساؤل هنا. لكنه تمكين للجهاز وNetHunter — فالقاعدة تبقى مثبتة عند إصدار Samsung vendor 4.14.190. لا أحد ينقل إصلاحات الأمان المستقرة من 4.14.y إليها، وSamsung لا توفّرها هي الأخرى — فقد انتهى 4.14.y نفسه عند 4.14.336 في 10 يناير 2024، ولم تعد السلسلة مُدرجة ضمن الإصدارات المدعومة في kernel.org. هذه الفجوة هي ما تقع فيه هذه الرقع الثلاث.
أنا لم أكتشف هذه الثغرات. الثلاث جميعها منشورة، مع رقع upstream كتبها آخرون، ومُوثّقون أدناه. ما فعلته:
النقطة 3 هي الجزء الذي أطلب من القارئ أن يزنه. اثنان من هذه مؤكدان بالقياس مقابل خط أساس. والثالث ليس كذلك، ولا يمكن أن يكون، على هذا الجهاز. هذا التمييز محفوظ في كل مكان بدلًا من تجاوزه بسلاسة.
| # | الرقعة | مؤلف upstream | الحالة على العتاد |
|---|---|---|---|
| 1 | عمر f_hidg مقابل cdev (use-after-free) | John Keeping، 2022-11-22 | ✅ الفعالية مقيسة |
| 2 | CVE-2026-31721 — إعادة تهيئة حالة عمر الكائن في hidg_bind | Michael Zimmermann، 2026-03-31 | ✅ الفعالية مقيسة |
| 3 | CVE-2026-31606 — cdev_init على cdev قيد الاستخدام | Michael Zimmermann (81ebd43cc0d6d)، 2026-03-27؛ متابعة -ENOMEM بواسطة Ethan Tidmore، 2026-04-02 | ⛔ غير قابل للملاحظة على هذا الجهاز |
f_hidg مقابل cdevكان f_hidg_open يخزّن f_hidg * دون أخذ أي مرجع. وكان f_hidg_poll يسجّل منتظري poll على
قوائم انتظار مضمّنة في f_hidg المخصصة عبر kzalloc. وكان hidg_unbind يستدعي cdev_del()، التي لا
تُبطل واصفات الملفات المفتوحة، ثم يقوم hidg_free بـ kfree للكائن. لم يكن هناك أي شيء يعدّه مرجعيًا مقابل الملفات
المفتوحة — فبحث grep على الملف بأكمله عن refcount|kref|atomic_|open_count لم يجد سوى opts->lock.
لذا فإن حلقة select()/poll() على /dev/hidg0 التي تنجو من إعادة تركيب Android للـ gadget كانت
ستستدعي remove_wait_queue() على slab محرَّر، وتأخذ spin_lock_irqsave على كلمة تذكرة (ticket word) تالفة، وتدور
مع تعطيل المقاطعات (IRQs) إلى الأبد. يتوقف المؤقت المحلي، ولا يُوقَظ خيط watchdog pet أبدًا، وينبح
watchdog غير الآمن بعد ~11 ثانية. عمليًا: يُعاد تشغيل الهاتف بقوة (hard-reset). يقوم
UsbDeviceManager في Android بإعادة التركيب من تلقاء نفسه — قيس ذلك مرتين عند ~550 مللي ثانية — دون أي إجراء من المستخدم، لذا لم يكن هذا بحاجة إلى محفّز غير عادي.
إصلاح upstream: "usb: gadget: f_hid: fix f_hidg lifetime vs cdev"، John Keeping، 2022-11-22،
Fixes: 71adf1189469. الـ backport نظيف — كل primitive موجود في 4.14.190 — باستثناء
kfree(hidg->set_report_buf) في الرقعة، وهو حقل يفتقر إليه هذا الإصدار، فتم إسقاطه.
CVSS 3.1 5.5، المتجه AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H. كان hidg_bind يعيد تهيئة حالة
تنتمي إلى عمر الكائن، لا إلى الـ bind: قفلان دوريان (spinlocks)، ورأسا قائمة انتظار (wait queue heads)، ورأس
قائمة (list head)، لا يظهر أي منها في hidg_alloc. يعيد init_waitqueue_head ضبط رأس القائمة ليشير إلى نفسه،
لذا فإن منتظر poll/select المسجَّل قبل إعادة الربط (rebind) يُصبح يتيمًا بصمت، ثم يجد
poll_freewait → remove_wait_queue الخاص به prev->next خاطئًا.
تُخرج الترقيةُ upstream تسع تهيئات من hidg_bind؛ خمسٌ منها فقط موجودة في 4.14.190.
⚠ تُعنون عدةُ استشارات من البائعين هذا الأمر بأنه "تصعيد صلاحيات". المتجه هو C:N/I:N/A:H —
التوافر فقط، أي حجب خدمة محلي. أُشير إليه لأن القراءةَ الأكثر إثارةً للخوف كانت ستبرّر أولويةً
لا يدعمها المتجه، ولا أحد يطلب من صفحة بائع أن تُثبت نفسها.
تتجاوز هذه الرقعة أيضًا الترقيةَ بخطوة. فإخراج التهيئات من hidg_bind يترك مخزنين غير مقفلين
لحقول محمية بقفل، وهي مُقنَّعة اليوم فقط لأن القفل كان يُعاد تهيئته مباشرةً فوقهما. وبمجرد أن يصبح
القفل دائمًا يصبح التسابق حقيقيًا: كاتبٌ محجوب في حلقة الانتظار في f_hidg_write، على واصف ملف
نجا من إلغاء الربط، يمكنه أخذ القفل واجتياز الفحص وقراءة hidg->req بينما يخزّن bind قيمة NULL
خارجه — إذ تأتي إعادة الفحص بعد إزالة الإشارة. لذا تأخذ الرقعة write_spinlock حول المخزنين.
يجدر ذكر الصيغة العامة: إخراج تهيئة من bind قد يُنشئ تسابقًا كانت إعادة التهيئة تُخفيه. اسأل عمّا كان التهيئةُ المحذوفة يحميه عن غير قصد.
CVSS 5.5، المتجه نفسه. struct cdev cdev مضمّنة في struct f_hidg، وكان hidg_bind يستدعي
cdev_init() عليها — وهي memset(cdev, 0, sizeof *cdev) بالإضافة إلى kobject_init — مباشرةً
قبل cdev_device_add. يحمل واصف ملف مفتوح مرجعًا على كائن kobject نفسه عبر chrdev_open →
cdev_get → kobject_get_unless_zero. عند إلغاء الربط ثم إعادة الربط مع واصف ملف مفتوح،
يمسح bind حالةَ مرجع حيّة على كائن مُشار إليه.
الإصلاح تغيير في البنية وليس حارسًا: تصبح struct cdev المضمّنة struct cdev * مع
cdev_alloc() لكل ربط. عندئذٍ يظهر cdev_init صفر مرة في الملف — فلا يبقى كائن قيد الاستخدام
لإعادة تهيئته.
⛔ لا يستطيع هذا الجهاز كشف هذا العيب، قبل الإصلاح أو بعده. انظر
verification/03-cve-2026-31606.md. هذا ادعاء إيجابي حول أدوات القياس في الجهاز، وليس
تحفّظًا.
المنهج والسجلات لكل عيب في verification/. باختصار: أُغلقت الرقعتان 1 و2
بانحدار أحادي المتغير مقابل البناء السابق مباشرةً، مع ضوابط إيجابية، وأُكِّدت الرقعة 2 مرة ثانية
من مصدر سجل مختلف. أما الرقعة 3 فمُستندة إلى قراءة المصدر فقط.
الرقع هي مخرجات git format-patch مقابل شجرة بائع Samsung بإصدار 4.14.190، بالترتيب:
git am patches/0001-*.patch patches/0002-*.patch patches/0003-*.patch
وهي تراكمية ويُفترض تطبيقها بالتسلسل — فالترقيم 3 يفترض أن البنية كما تركها الترقيم 1.
GPL-2.0، باعتبارها أعمالًا مشتقة من نواة Linux. راجع LICENSE. يُنسب تأليف الإصلاحات الأصلية في المنبع أعلاه؛ أما النقل العكسي (backporting)، وإضافة الأقفال في الترقيم 2، وأعمال التحقق، فهي لي.
لا تُوزَّع هنا أي مخرجات مُجمَّعة — لا صورة نواة، ولا وحدات، ولا حزمة قابلة للوميض. هذا المستودع مصدر وأدلة فقط.