
تحليل وPOC وسلسلة استغلال arm64 لثغرة تجاوز سعة الكومة DACL في Linux cifs.ko (CVE-2026-97555)، تصعيدًا إلى الكتابة فوق بيانات الاعتماد uid-0 على البنية المرجعية.
English | 简体中文
تجاوز سعة الكومة في نواة لينكس داخل إعادة كتابة DACL في عميل SMB. تحدد الدالة id_mode_to_cifs_acl() حجم واصف الأمان الناتج بناءً على DACL الذي يعيده الخادم، لكن الدالة replace_sids_and_copy_aces() تعيد كتابة كل إدخال ACE باستخدام SID المالك/المجموعة الجديد (حتى 68 بايت). لذلك فإن الخادم الذي يعيد DACL بـ SID قصير يجعل إعادة الكتابة تتجاوز حجم التخصيص وتكتب خارج كائن kmalloc.
يوفّر هذا المستودع التحليل، وPOC يعيد إنتاج الكتابة عبر الكائنات، وEXP يحوّل تجاوز السعة إلى سلسلة استغلال متكاملة ومُتحكَّم بها (تسريب الكومة، استعادة جدول الصفحات، كتابة PTE عشوائية، الكتابة فوق بيانات الاعتماد الذاتية للوصول إلى uid 0) على البنية المرجعية.
| العنصر | القيمة |
|---|---|
| CVE | CVE-2026-97555 |
| الفئة | تجاوز سعة المخزن المؤقت في الكومة (كتابة، CWE-122) في id_mode_to_cifs_acl() |
| المكوّن | cifs.ko (عميل SMB) |
| المتأثر | Linux cifs.ko بدون الإصلاح (تم التحقق منه على نواة Android مبنية على GKI 6.12) |
| التصحيح | upstream 0ee150794c75 (stable fb736ab4fb8c) |
| البنية المرجعية | نواة Android 16 مبنية على GKI 6.12، arm64، صفحات 4 KiB، struct page بحجم 64 بايت |
| POC | تم إعادة إنتاج الكتابة عبر الكائنات (SET_INFO security descriptor بحجم 696 بايت) |
| EXP | سلسلة كاملة للوصول إلى صدفة بصلاحيات uid-0 على البنية المرجعية |
/proc/kpageflags بصلاحيات الجذر. الحصول على قدرة
التركيب هو تجهيزة اختبار وليس جزءاً من الثغرة، وهو خارج نطاق
هذا المستودع.uid/suid/euid/fsuid فقط في بيانات اعتمادها الخاصة؛
ولا يُمسّ أي حقل قدرة أو securebits. التنظيف يتم بإعادة التشغيل.src/exp.c (عناوين الرموز، هندسة vmemmap، إزاحات BTF)
تنتمي إلى البنية المرجعية. أعد اشتقاقها لأي بنية أخرى
قبل التشغيل؛ انظر scripts/derive_offsets.sh.تحسب id_mode_to_cifs_acl() التخصيص من dacl_ptr->size أو
num_aces * sizeof(struct smb_ace)، ثم تعيد replace_sids_and_copy_aces()
بناء كل إدخال ACE باستخدام SID المالك/المجموعة الجديد. عندما تكون SIDs
الخادم قصيرة (0-subauth) ويكون SID الجديد بحجم 68 بايت، يصبح DACL
المعاد كتابته أطول من التخصيص، وتكتب build_sec_desc() خارج كائن
الـ slab.
تتحقق أداة FORTIFY الديناميكية في النواة من مخازن رأس ACE المضمّنة،
لكن نسخ SID تمر عبر cifs_copy_sid() غير المضمّنة
وليست مُخضعَة للأداة. DACL المحاذى بحيث يبقى كل رأس ACE داخل
nsecdesclen - 28 يصل إلى النسخ الثغرة.
تخطيط خادم cross7 (7 إدخالات ACE، أحجام المصدر 76/76/76/44/44/16/16) يجعل
التخصيص 512 بايت بالضبط؛ نسخ SID بحجم 68 بايت للإدخال السابع يبدأ عند
الإزاحة 492 ويعبر حدود الكائن، وينمو الواصف المعاد كتابته إلى 696 بايت — أي
184 بايت خارج الكائن. لا يزال الواصف الموسّع يُسلَّم في SET_INFO، لذا
تكون الكتابة قابلة للملاحظة من جانب الخادم.
fb736ab4fb8c.kpageflags، التركيب،
chown) ونافذة SELinux متساهلة (أو سماح سياسة
مكافئ) لاستدعاء cifs idmap upcall.src/poc_smb2_acl.py، مركّبة مع
cifsacl/modefromsid عبر عميل النواة القياسي
(scripts/mount_cifs.sh). التركيب نفسه تجهيزة مميزة؛ وكيفية
حصول المختبر على تلك الصلاحية خارج نطاق هذا المستودع.cifs.idmap محمّلة مسبقاً للمعرّفات المستهدفة: بيئة المرجع
لا تحتوي على مساعد upcall /sbin/cifs.idmap (src/poc.c وsrc/exp.c
يحمّلانها مسبقاً؛ وsrc/acl_key_preload.c يفعل ذلك بشكل مستقل).# host
python3 src/poc_smb2_acl.py # ACL_LAYOUT=cross7 for the overflow
adb reverse tcp:1468 tcp:1468
# device (root)
sh /data/local/tmp/e2e/mount_cifs.sh 127.0.0.1 share /mnt/cifs
make && make push
/data/local/tmp/poc /mnt/cifs
الأدلة المتوقعة: تُطبع سجلات الخادم
SET_INFO security: len=696 ... (cross7)، أي واصف أطول من
تخصيص 512 بايت، ويُظهر scripts/trace_acl02.sh
دخول/عودة id_mode_to_cifs_acl حول chown.
لا يُفعّل POC سوى المسار الثغرة؛ فهو لا يقوم بتهيئة الذاكرة (grooming) أو تسريبها أو التصعيد. يُجري المُشغّل كتابة خارج حدود الذاكرة في النواة: قد تتعطل عمليات البناء التي تُطلق تخطيطاتها فحوصات FORTIFY الديناميكية، لذا شغّله فقط على جهاز اختبار يمكنك إعادة تشغيله.
EXP هو السلسلة الكاملة، من البداية إلى النهاية على نسخة البناء المرجعية:
cross7 كتابة datalen الخاص بحمولة مفتاح
مجاور؛ ويُفرّغ keyctl_read مساحة 64 كيبي بايت بما في ذلك مصفوفة pipe_buffer
حيّة. يمنح مؤشر anon_pipe_buf_ops المُسرّب إزاحة KASLR
وتمنح صفحة المخزن المؤقت P هدف struct page الحقيقي.buffer[0] لأحد أنابيب hit
بالقيم page=P وlen=0x1234 وops=default_pipe_buf_ops.P؛ ويستعيدها
خطأ صفحة عند base كصفحة جدول صفحات، بشرط أن يُثبت kretprobe أن
__alloc_pages_noprof أعاد P من __pte_alloc.P+16 (PTE[2])؛ أول ثمانية بايتات منه هي
واصف خريطة ذاتية لـ P نفسه. ثم يقرأ وضع المستخدم ويكتب
صفحة جدول الصفحات عبر base+8 KiB.KPF_SLAB عبر
PTEs جديدة وتُمسح بحثًا عن comm الخاص بهذه المهمة؛ وتتحقق pid/tgid ونافذة
الخريطة الخطية وcred->uid/euid من الإصابة، وتُعيد عناوين افتراضية جديدة لم تُستخدم قط
التحقق من الصفحة الفيزيائية.setresuid(2000) حقيقية (تُخصص
النواة بيانات اعتماد جديدة)، وتقرأ task->cred عبر الخريطة الذاتية،
وتُصفّر فقط uid/suid/euid/fsuid، وتؤكد getuid()==0، وتُعطّل
الخريطة الذاتية وتُنفّذ execve لـ /system/bin/sh. تعمل الصدفة بمعرّف المستخدم 0.make exp
make push
# on the device, as root:
sh /data/local/tmp/e2e/launch_exp.sh /mnt/cifs 10 1 r1 4000 400 2400 7 0 0 cred
# then read /data/local/tmp/e2e/e2e_status.txt and
# /data/local/tmp/e2e/root_shell.txt
مرحلة cred اختيارية عبر الوسيط الأخير (cred)؛ يتوقف scout
بعد التحقق للقراءة فقط. يحتفظ EXP بجداول صفحاته (PTEs) داخل العملية
والتنظيف يتم بإعادة التشغيل.
المتطلبات: Android NDK r29 (يمكن تجاوزه بـ NDK=)، GNU make، adb.
make # POC (arm64, API 24)
make exp # EXP
make build-all # both
make check # push and run the read-only pre-check (poc --check)
make push # push binaries and helper scripts
make run # push and run the POC against $(MNT) (default /mnt/cifs; destructive)
make host # static POC build for qemu-aarch64
make emu # host build and run
make logs # dump logcat/dmesg into logs/
make help