Breaking News
القائمة
Advertisement

ثغرة CVE-2026-31532: كيف اكتشف الذكاء الاصطناعي عيباً أمنياً حرجاً في نواة لينكس

ثغرة CVE-2026-31532: كيف اكتشف الذكاء الاصطناعي عيباً أمنياً حرجاً في نواة لينكس
صورة ذكاء اصطناعي
Advertisement

محتويات المقال

تُعرّض ثغرة أمنية حرجة من نوع الاستخدام بعد الحذف (Use-After-Free) تحمل الرمز CVE-2026-31532 في نظام Controller Area Network (CAN) داخل نواة لينكس (Linux Kernel) الأنظمة لخطر رفع الامتيازات (Privilege Escalation) محلياً. اكتُشفت هذه الثغرة بشكل مستقل عبر نظام أمني يعتمد على النماذج اللغوية الكبيرة (LLMs) طوّرته شركة Bynario باستخدام نموذج Opus 4.6، وتكمن المشكلة في تنفيذ المقابس الخام (Raw Sockets) ضمن مسار net/can. تسمح هذه الثغرة لعملية استقبال الإطارات المتزامنة بالوصول إلى حالة مخصصة لكل معالج تم تحريرها مسبقاً، وذلك بسبب غياب مزامنة التحديث والنسخ والقراءة (RCU) أثناء عملية إغلاق المقبس.

يُعد بروتوكول CAN تقنية شبكات تُستخدم بكثافة في أنظمة السيارات، والأتمتة الصناعية، والأجهزة المدمجة لربط وحدات التحكم الإلكترونية (ECUs). ونظراً لأن وحدة CONFIG_CAN ووحدة CAN الافتراضية (CONFIG_VCAN) مفعّلتان افتراضياً في معظم توزيعات لينكس (Linux Distributions) الرئيسية، فإن مساحة الهجوم تمتد لتشمل بيئات الحواسيب المكتبية والخوادم القياسية حيث يمكن إنشاء واجهات افتراضية بسهولة.

التشريح التقني لثغرة الاستخدام بعد الحذف في بروتوكول CAN

تنبع الثغرة من حالة تسابق (Race Condition) في عملية إغلاق المقبس الخام لبروتوكول CAN عبر دالة raw_release(). عند إنشاء مقبس خام، فإنه يسجل وظيفة استدعاء لاستقبال البيانات لكل عامل تصفية عبر دالة raw_rcv(). يتضمن هيكل المقبس تخصيصاً لكل معالج يُسمى uniq، والذي يُستخدم لمنع تكرار التطابقات عندما يتطابق إطار واحد مع عدة عوامل تصفية.

/* A raw socket has a list of can_filters attached to it, each receiving
 * the CAN frames matching that filter.  If the filter list is empty,
 * no CAN frames will be received by the socket.  The default after
 * opening the socket, is to have one filter which receives all frames.
 * The filter list is allocated dynamically with the exception of the
 * list containing only one item.  This common case is optimized by
 * storing the single filter in dfilter, to avoid using dynamic memory.
 */

struct uniqframe { const struct sk_buff *skb; u32 hash; unsigned int join_rx_count; };

struct raw_sock { struct sock sk; struct net_device *dev; // ... int count; /* number of active filters */ struct can_filter dfilter; /* default/single filter */ struct can_filter *filter; /* pointer to filter(s) */ struct uniqframe __percpu *uniq; };

أثناء إنشاء المقبس في دالة raw_init()، يتم تخصيص هذا الكائن لكل معالج:

static int raw_init(struct sock *sk)
{
	struct raw_sock *ro = raw_sk(sk);

// ...

/* alloc_percpu provides zero'ed memory */ ro->uniq = alloc_percpu(struct uniqframe); if (unlikely(!ro->uniq)) return -ENOMEM;

يحدث الخطأ الحرج خلال مرحلة الإغلاق في دالة raw_release(). تستدعي الدالة raw_disable_allfilters()، والتي تمر عبر عوامل التصفية المسجلة وتُشغّل can_rx_unregister(). تؤدي عملية إلغاء التسجيل هذه إلى إزالة المستقبل من قائمة التجزئة المحمية بنظام RCU وتُجدول عملية الحذف بشكل غير متزامن عبر call_rcu().

static int raw_release(struct socket *sock)
{
	struct sock *sk = sock->sk;
	struct raw_sock *ro;
	struct net *net;

if (!sk) return 0;

ro = raw_sk(sk); net = sock_net(sk);

spin_lock(&raw_notifier_lock); while (raw_busy_notifier == ro) { spin_unlock(&raw_notifier_lock); schedule_timeout_uninterruptible(1); spin_lock(&raw_notifier_lock); } list_del(&ro->notifier); spin_unlock(&raw_notifier_lock);

rtnl_lock(); lock_sock(sk);

/* remove current filters & unregister */ if (ro->bound) { if (ro->dev) { raw_disable_allfilters(dev_net(ro->dev), ro->dev, sk); // [1] netdev_put(ro->dev, &ro->dev_tracker); } else { raw_disable_allfilters(net, NULL, sk); } }

if (ro->count > 1) kfree(ro->filter);

ro->ifindex = 0; ro->bound = 0; ro->dev = NULL; ro->count = 0; free_percpu(ro->uniq); // [3]

نظراً لأن دالة call_rcu() غير متزامنة، فإنها تنتظر انتهاء فترة السماح الحالية لنظام RCU قبل تحرير المستقبل. ومع ذلك، لا تنتظر دالة raw_release()، بل تنتقل مباشرة إلى free_percpu(ro->uniq). إذا كانت الإطارات لا تزال قيد الاستقبال، فقد تُنفّذ دالة raw_rcv() بشكل متزامن على معالج آخر داخل rcu_read_lock()، مما يؤدي مباشرة إلى سيناريو الاستخدام بعد الحذف عندما تحاول قراءة أو كتابة تخصيص uniq المحرر.

static void raw_rcv(struct sk_buff *oskb, void *data)
{
	struct sock *sk = (struct sock *)data;
	struct raw_sock *ro = raw_sk(sk);
	// ...

/* eliminate multiple filter matches for the same skb */ if (this_cpu_ptr(ro->uniq)->skb == oskb && // READ from freed memory this_cpu_ptr(ro->uniq)->hash == oskb->hash) { // READ from freed memory if (!ro->join_filters) return;

this_cpu_inc(ro->uniq->join_rx_count); // WRITE to freed memory /* drop frame until all enabled filters matched */ if (this_cpu_ptr(ro->uniq)->join_rx_count < ro->count) return; } else { this_cpu_ptr(ro->uniq)->skb = oskb; // WRITE to freed memory this_cpu_ptr(ro->uniq)->hash = oskb->hash; // WRITE to freed memory this_cpu_ptr(ro->uniq)->join_rx_count = 1; // WRITE to freed memory /* drop first frame to check all enabled filters? */ if (ro->join_filters && ro->count > 1) return; }

// ... }

التحقق من الثغرة والأدوات المخصصة

شكّل التحقق من هذه الثغرة تحدياً فريداً. كان الكائن المستخدم بعد الحذف عبارة عن تخصيص لكل معالج، وهو أمر لا يتم تتبعه عادةً بواسطة أداة اكتشاف أخطاء الذاكرة في النواة (KASAN). لإثبات الثغرة، قام نظام التحقق الخاص بشركة Bynario بتجميع النواة مع أدوات مخصصة، مضيفاً علامة منطقية (uniq_freed) لتأكيد أن دالة raw_rcv() تصل إلى الذاكرة بعد تحريرها.

diff --git a/net/can/raw.c b/net/can/raw.c
index eee244ffc31e..ae47f113d5ea 100644
--- a/net/can/raw.c
+++ b/net/can/raw.c
@@ -102,6 +102,7 @@ struct raw_sock {
 	struct can_filter dfilter; /* default/single filter */
 	struct can_filter *filter; /* pointer to filter(s) */
 	struct uniqframe __percpu *uniq;
+	int uniq_freed;		       /* UAF logical free marker */
 };

static LIST_HEAD(raw_notifier_list); @@ -163,6 +164,16 @@ static void raw_rcv(struct sk_buff *oskb, void *data) } }

+ /* UAF: detect raw_rcv running after free_percpu(ro->uniq). */ + if (unlikely(READ_ONCE(ro->uniq_freed))) { + WARN_ONCE(1, + "raw_rcv racing with raw_release!\n" + " sk=%px ro=%px ro->uniq=%px cpu=%d bound=%d count=%d\n", + sk, ro, ro->uniq, smp_processor_id(), + ro->bound, ro->count); + return; + } + /* eliminate multiple filter matches for the same skb */ if (this_cpu_ptr(ro->uniq)->skb == oskb && this_cpu_ptr(ro->uniq)->hash == oskb->hash) { @@ -437,6 +448,7 @@ static int raw_release(struct socket *sock) ro->dev = NULL; ro->count = 0; free_percpu(ro->uniq); + WRITE_ONCE(ro->uniq_freed, 1);

sock_orphan(sk); sock->sk = NULL;

باستخدام هذا التحديث المخصص، نجح إثبات المفهوم (PoC) في إطلاق حالة التسابق من خلال تشغيل عدة خيوط إرسال جنباً إلى جنب مع خيوط تسابق تقوم بإنشاء وإغلاق مقابس CAN خام قصيرة العمر بشكل متكرر.

[*] Running PoC (10 rounds)...
[*] Round 1/10...
[*] PoC: Finding #17 - CAN raw free_percpu UAF
[*] vcan0 ifindex=4, 8 senders, 8 racers, 20000 iterations
[   12.578151] ------------[ cut here ]------------
[   12.578353] raw_rcv racing with raw_release!
[   12.578353]   sk=ffff0000cbad3400 ro=ffff0000cbad3400 ro->uniq=0000bd0f5723e078 cpu=6 bound=0 count=0
[   12.578962] WARNING: net/can/raw.c:169 at raw_rcv+0x828/0xb38, CPU#6: poc/116

كيفية حماية نظامك

تم إدخال الكود البرمجي المعرّض للخطر في التزام 514ac99c64b2 في أبريل 2015، مما يعني أن إصدارات نواة لينكس (Linux Kernel) بدءاً من 4.1 وما بعدها متأثرة. لاستغلال هذه الثغرة، يحتاج المهاجم إلى محول CAN فعلي يمكن الوصول إليه أو واجهة CAN افتراضية (vcan). إذا لم تكن هناك واجهة موجودة، فيجب عليه إنشاء واحدة، وهو ما يتطلب صلاحيات CAP_NET_ADMIN. لتأمين بنيتك التحتية، اتبع الخطوات التالية:

  • تطبيق التحديث الرسمي: ينقل الإصلاح الرسمي دالة free_percpu(ro->uniq) خارج raw_release() ويضعها في أداة إتلاف مقبس مخصصة (raw_sock_destruct). يضمن هذا عدم تحرير المنطقة المخصصة لكل معالج حتى تكتمل جميع استدعاءات RCU ذات الصلة. التحديث متاح عبر القائمة البريدية الرسمية للنواة.
  • تقييد مساحات أسماء المستخدمين: إذا لم يكن التحديث الفوري ممكناً، قم بتقييد مساحات أسماء المستخدمين غير المتمتعة بامتيازات (Unprivileged user namespaces). يعتمد المهاجمون على هذه المساحات لإنشاء واجهات CAN افتراضية. يمكنك التحقق من حالة نظامك بتشغيل الأمر cat /proc/sys/kernel/unprivileged_userns_clone (القيمة 1 تعني أنها مفعلة).
  • تعطيل وحدة CAN: إذا كانت بيئة الخادم أو سطح المكتب لديك لا تتطلب شبكات CAN، فقم بتعطيل وحدتي CONFIG_CAN و CONFIG_VCAN بالكامل للقضاء على مساحة الهجوم.

التحول نحو اكتشاف الثغرات بالذكاء الاصطناعي

يُبرز اكتشاف ثغرة CVE-2026-31532 قفزة نوعية في كيفية تعاملنا مع أمان النواة. غالباً ما تواجه أدوات التحليل الثابت التقليدية صعوبة في تتبع مسارات الإغلاق غير المتزامنة وحالة التخصيص لكل معالج، ويرجع ذلك أساساً إلى أن هذه الآليات تعتمد بشكل كبير على واجهات برمجة تطبيقات خاصة بالنواة مثل RCU. من خلال الاستفادة من نظام يعتمد على النماذج اللغوية الكبيرة (LLMs)، تمكنت شركة Bynario من تحليل دورة الحياة المنطقية لتخصيص uniq عبر نوى معالجة متعددة.

ومع ذلك، فإن الإنجاز الحقيقي هنا ليس مجرد الاكتشاف، بل التحقق الآلي. نظراً لأن الأدوات القياسية مثل KASAN تفشل في تتبع التخصيصات لكل معالج بفعالية، فإن المخرجات الأولية للذكاء الاصطناعي كانت ستؤدي عادةً إلى تنبيه مزعج وغير مؤكد. من خلال كتابة أدوات مخصصة للنواة بشكل مستقل لإثبات حالة التسابق، سد النظام الفجوة بين تحليل الكود النظري والهندسة الأمنية القابلة للتطبيق. مع تزايد تطور النماذج المحلية، يمكننا أن نتوقع منها الكشف عن عيوب منطقية عمرها عقود لطالما أغفلتها الأدوات التقليدية.

المصادر: bynar.io ↗
هل أعجبك هذا المقال؟
Advertisement

عمليات البحث الشائعة