
نسخ فاشل: 732 بايت للوصول إلى صلاحيات الجذر على كل توزيعات لينكس الرئيسية.
732 بايت. أي توزيعة. صلاحيات الجذر.
خلل منطقي مباشر في قالب التشفير
authencesnفي نواة لينكس يمكّن مستخدمًا محليًا غير مميز من إجراء كتابة دقيقة ومضبوطة بحجم 4 بايت في ذاكرة التخزين المؤقت للصفحات لأي ملف قابل للقراءة — بما في ذلك ثنائيات setuid. لا سباقات. لا إعادة محاولة. لا إعادة ترجمة. صلاحيات الجذر على كل توزيعة لينكس رئيسية صدرت منذ 2017.
📄 التقرير الفني · 🔗 تصحيح النواة · 🛡️ CVSS: حرج
| التوزيعة | إصدار النواة |
|---|---|
| Ubuntu 24.04 LTS | 6.17.0-1007-aws |
| Amazon Linux 2023 | 6.18.8-9.213.amzn2023 |
| RHEL 10.1 | 6.12.0-124.45.1.el10_1 |
| SUSE 16 | 6.12.0-160000.9-default |
تم اختراق جميع التوزيعات الأربع باستخدام نفس سكربت بايثون المكوّن من 732 بايت، دون أي تعديل.
AF_ALG يعرض نظام التشفير الفرعي في النواة لمستخدمي الفضاء غير المميزين. splice() ينقل بيانات الملف إلى أنبوب بالمرجع — مررًا صفحات ذاكرة التخزين المؤقت مباشرة، دون نسخ. عندما يقوم مستخدم بتقسيم ملف إلى مقبس AF_ALG AEAD، تحتوي قائمة تشتت الإدخال الخاصة بالمقبس على مراجع حية لصفحات النواة المخزنة مؤقتًا لذلك الملف.
في algif_aead.c، قام تحسين الموضع الداخلي لعام 2017 بنسخ AAD والنص المشفر من قائمة تشتت TX إلى مخزن RX المؤقت، لكنه ربط صفحات علامة المصادقة بالمرجع باستخدام sg_chain()، ثم ضبط req->src = req->dst:
Input SGL: [ AAD | CT | Tag ]
^
└─ sg_chain() → لا تزال تشير إلى صفحات ذاكرة التخزين المؤقت
Output SGL: [ AAD | CT ] ──→ [ Tag (صفحات ذاكرة التخزين المؤقت) ]
(مخزن RX المؤقت) (مربوطة من TX SGL)
req->src ──┐
├──→ نفس قائمة التشتت المدمجة
req->dst ──┘
أصبحت صفحات ذاكرة التخزين المؤقت من splice() الآن داخل قائمة تشتت وجهة قابلة للكتابة، مفصولة عن منطقة الكتابة المشروعة بحدود إزاحة فقط. لا شيء في الواجهة البرمجية يفرض أن تبقى الخوارزميات ضمن الحدود.
authencesnauthencesn هو غلاف AEAD يستخدمه IPsec لدعم أرقام التسلسل الموسعة 64-بت (ESN). لإعادة ترتيب بايتات ESN لحساب HMAC، يستخدم مخزن الوجهة المؤقت للمتصل كمساحة خدش — بما في ذلك كتابة عند الإزاحة assoclen + cryptlen، التي تقع بعد حدود علامة المصادقة:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // قراءة AAD[0..7]
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // الكتابة فوق dst[4..7]
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // ← الكتابة بعد العلامة
الاستدعاء الثالث يكتب 4 بايتات (seqno_lo) عند dst[assoclen + cryptlen]. في مسار AF_ALG الموضعي، يعبر scatterwalk من مخزن RX المؤقت إلى صفحات علامة ذاكرة التخزين المؤقت المربوطة. تقوم النواة بتعيين صفحة ذاكرة التخزين المؤقت عبر kmap_local_page وتكتب مباشرة في النسخة المخزنة مؤقتًا من الملف الهدف.
ثم يفشل HMAC (النص المشفر مُلفَّق)، ويعيد recvmsg() خطأً — لكن الكتابة بحجم 4 بايت تبقى بشكل دائم.
لا يوجد تغيير واحد كان خاطئًا بشكل فردي. الثغرة تعيش عند تقاطع الثلاثة جميعًا.
الهدف الافتراضي هو /usr/bin/su، وهو ثنائي setuid-root موجود على جميع التوزيعات المختبرة.
الخطوة 1 — إعداد المقبس
فتح مقبس AF_ALG، الربط بـ authencesn(hmac(sha256),cbc(aes))
تعيين المفتاح. قبول مقبس الطلب. (لا تتطلب صلاحيات.)
الخطوة 2 — حلقة الكتابة (مرة واحدة لكل كتلة شيفرة بحجم 4 بايت)
sendmsg() → بايتات AAD [4:8] تحمل الـ 4 بايتات للكتابة (seqno_lo)
splice() → صفحات ذاكرة التخزين المؤقت للملف الهدف في مقبس AF_ALG
recv() → تشغيل فك التشفير → authencesn يكتب seqno_lo في ذاكرة التخزين المؤقت
(recvmsg يعيد خطأً؛ الكتابة تبقى)
الخطوة 3 — التنفيذ
execve("/usr/bin/su")
النواة تحمّل الثنائي من ذاكرة التخزين المؤقت (المفسدة الآن)
الثنائي setuid-root ينفذ الشيفرة المحقونة → UID 0
a = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# ... تعيين المفتاح، قبول مقبس الطلب u ...
u.sendmsg([b"A"*4 + payload_chunk], [cmsg_headers], MSG_MORE)
os.splice(target_fd, pipe_wr, offset)
os.splice(pipe_rd, alg_fd, offset)
u.recv(...) # تشغيل كتابة ذاكرة التخزين المؤقت
التحديث إلى نواة تحتوي على التصحيح a664bf3d603d. الإصلاح يعيد algif_aead.c إلى التشغيل خارج الموضع: req->src يشير إلى TX SGL؛ req->dst يشير إلى مخزن RX المؤقت. صفحات ذاكرة التخزين المؤقت من splice() تبقى للقراءة فقط. آلية sg_chain() التي ربطتها بالوجهة القابلة للكتابة تمت إزالتها.
// قبل (قابل للاستغلال): src و dst يتشاركان نفس قائمة التشتت
aead_request_set_crypt(&areq->cra_u.aead_req, rsgl_src, rsgl_src, used, ctx->iv);
// بعد (مُصلح): src هو TX SGL، dst هو مخزن RX المؤقت — منفصلان تمامًا
aead_request_set_crypt(&areq->cra_u.aead_req, tsgl_src, rsgl_dst, used, ctx->iv);
تعطيل وحدة النواة algif_aead:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
أو حظر إنشاء مقبس AF_ALG عبر سياسة seccomp في ملفات تعريف عبء العمل لديك.
ملاحظة لبيئات الحاويات: نظرًا لأن ذاكرة التخزين المؤقت للصفحات مشتركة عبر المضيف، فإن هذه الثغرة تعبر حدود الحاويات. طبّق التخفيفات على مستوى العقدة، وليس فقط لكل بود. راجع الجزء 2 للحصول على تفاصيل كاملة حول الهروب من Kubernetes.
الباحث في Theori Taeyang Lee حدد، من خلال أعمال سابقة في kernelCTF، أن AF_ALG + splice() ينشئ مسارًا يمكن من خلاله للفضاء غير المميز تغذية صفحات ذاكرة التخزين المؤقت مباشرة في نظام التشفير الفرعي — وأن أصل صفحات قائمة التشتت كان فئة ثغرات غير مستكشفة بشكل كافٍ.
استخدم فريق البحث Xint Code لتوسيع هذه الرؤية عبر نظام crypto/ الفرعي بأكمله مع موجه المشغل التالي:
"هذا هو نظام crypto/ الفرعي في لينكس. يرجى فحص جميع مسارات الكود القابلة للوصول من استدعاءات نظام الفضاء المستخدم. لاحظ ملاحظة رئيسية واحدة: يمكن لـ splice() تسليم مراجع ذاكرة التخزين المؤقت للصفحات للملفات للقراءة فقط (بما في ذلك ثنائيات setuid) إلى قوائم تشتت TX للتشفير."
بعد حوالي ساعة واحدة من التحليل الآلي، كان Copy Fail هو الناتج الأعلى خطورة. الثغرات الإضافية المكتشفة خلال نفس الفحص تبقى تحت الكشف المنسق.
الجزء 2: من البود إلى المضيف — كيف يهرب Copy Fail من كل منصة Kubernetes سحابية رئيسية. قريبًا.
| الخاصية | التفاصيل |
|---|
| حتمي | خلل منطقي مباشر — لا شروط سباق، لا نوافذ توقيت، لا إعادة محاولة |
| قابل للنقل | نفس السكربت، نفس البايتات، يعمل عبر جميع التوزيعات والمعماريات المختبرة |
| صغير الحجم | سكربت بايثون من 732 بايت يستخدم المكتبة القياسية فقط (os, socket, zlib). يتطلب Python 3.10+ لـ os.splice |
| خفي | الصفحة التالفة لا تُعلَّم أبدًا كمتسخة. مجاميع الاختبار على القرص تبقى دون تغيير؛ فقط ذاكرة التخزين المؤقت للصفحات في الذاكرة يتم تعديلها |
| عابر للحاويات | ذاكرة التخزين المؤقت للصفحات مشتركة على مستوى النظام عبر حدود الحاويات — وهذا أيضًا بدائي للهروب من عقد Kubernetes (انظر الجزء 2) |
| المتغير | التحكم عبر |
|---|
| الملف الهدف | أي ملف قابل للقراءة من قبل المستخدم الحالي |
| إزاحة الكتابة | assoclen، إزاحة splice، وطول splice |
| قيمة الكتابة | البايتات 4–7 من AAD المقدمة في sendmsg() (seqno_lo) |
| السنة | الحدث |
|---|
| 2011 | إضافة authencesn إلى النواة (a5079d084f8b) لدعم ESN في IPsec. كانت الكتابة الخدشية موجودة لكنها غير ضارة — فقط طبقة xfrm الداخلية كانت تستدعيها، وكان AAD يعيش في قائمة تشتت منفصلة. |
| 2015 | AF_ALG يكتسب دعم AEAD. تحويل authencesn إلى واجهة AEAD الجديدة (104880a6b470)، مع إدخال إزاحة الكتابة assoclen + cryptlen. لا تزال خارج الموضع: صفحات ذاكرة التخزين المؤقت كانت في src (للقراءة فقط). غير قابلة للاستغلال بعد. |
| 2017 | إضافة تحسين الموضع الداخلي إلى algif_aead.c (72548b093ee3). req->src = req->dst. ربط صفحات علامة ذاكرة التخزين المؤقت في الوجهة القابلة للكتابة. تشكّل الثغرة. |
| 2026-03-23 | الإبلاغ إلى فريق أمان نواة لينكس. |
| 2026-04-01 | دمج التصحيح في الخط الرئيسي. |
| 2026-04-22 | تعيين CVE-2026-31431. |
| 2026-04-29 | الكشف العام. |
| التاريخ | الحدث |
|---|
| 2026-03-23 | الإبلاغ عن الثغرة إلى فريق أمان نواة لينكس |
| 2026-03-24 | استلام إقرار أولي |
| 2026-03-25 | اقتراح التصحيحات ومراجعتها |
| 2026-04-01 | الالتزام بالتصحيحات في النواة الرئيسية |
| 2026-04-22 | تعيين CVE-2026-31431 |
| 2026-04-29 | الكشف العام |