
أبحاث استغلال CVE-2026-98260 (posix-cpu-timers exec UAF): S9280 Android 14 GKI 6.1 — محفّز UAF + بدائية الكتابة الصفرية + SELinux Permissive. وثيقة التسليم في HANDOFF.md
exec: Cleanup POSIX timers right after de_thread() (المؤلف Hyunwoo Kim، 2026-09-11 في المنبع،
Fixes: 55e8c8eb2c7b = بدءًا من 5.7).
تم تعليق مؤقت CLOCK_THREAD_CPUTIME_ID على خيط غير قائد E؛ في execve()
بعد أن يبدّل de_thread() الـ tid، يفشل البحث العكسي عن المهمة بواسطة PID ويبقى العقدة في
posix_cputimers الخاصة بـ E. إذا أعاد mmap_read_lock_killable() في exec_mmap() القيمة -EINTR
(فشل exec وكود التنظيف يأتي لاحقًا)، فإن do_exit() → exit_itimers() يحرّر k_itimer
لكن العقدة لم تُزل؛ وعندما تسترد العملية الأب عبر wait4() فإن cleanup_timerqueue() → timerqueue_del()
تعمل على كائن محرَّر → UAF.
| الفرع | posixtimer_exec | الحالة |
|---|---|---|
| linux-6.1.y | غير موجود | غير مُرقَّع |
| linux-5.15.y | غير موجود | غير مُرقَّع |
| linux-6.6.y | موجود | مُرقَّع (6.6.158+) |
| linux-6.12.y | موجود | مُرقَّع (6.12.112+) |
→ أهداف هذا المشروع (خط 5.15 / 6.1، وكذلك 6.6 الأقدم من 6.6.158) كلها ضمن النطاق المتأثر.
fragment_vmas() كلاً من memfd + MAP_FIXED لتقسيم فضاء العناوين إلى
--vmas (افتراضيًا 50000) من VMA غير القابلة للدمج (كل واحدة منها vm_pgoff تساوي 0،
والعناوين المتجاورة pgoff غير متصلة → يفشل vma_merge). خيط locker يكرر mprotect
على المقطع بأكمله، فيجعل المرور على آلاف الـ VMA قفل كتابة mmap محتجزًا لمستوى الميلي ثانية.ESRCH العائدة من tgkill(tgid, الـ tid القديم, 0) للحكم
بأن "de_thread() أكمل تبديل الـ tid"، وعندها فقط يرسل SIGKILL.exec_mmap() ينحبس حتمًا في mmap_read_lock_killable()
(قفل القراءة محجوب بقفل الكتابة)، ويصل SIGKILL بعده بميكروثوانٍ → يعيد ذلك الاستدعاء -EINTR
→ يفشل exec. النافذة (ms) ≫ زمن الاستجابة (µs)، لذا لا يعتمد على الحظ.⚠️ تنبيه للتمييز: التشغيل حتمي؛ أما الانتقال من UAF إلى كتابة عشوائية/تعديل cred فيعتمد على
تنظيم إعادة استخدام slab الخاص بـ posix_timers_cache (order، objects/slab، CPU partial،
انتماء memcg)، وهو هندسة كومة قابلة للقياس والتكرار، وليس سباقًا.
export ANDROID_NDK_HOME=/home/nt/项目/samsung_root_research/06_工具链/android-ndk-r29
make # 产物 trigger(aarch64 / Android 21+ / 仅依赖 libc、libdl)
adb push trigger /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/trigger"
adb shell "/data/local/tmp/trigger --check" # 先看环境
adb shell "/data/local/tmp/trigger; echo exit=\$?"
علامات النجاح (يكفي واحد من الثلاثة):
[*] 开始触发 تحدث إعادة تشغيل الجهاز/شاشة سوداء (الأكثر مباشرة عند panic_on_oops=1)exit=137/139)، ويبقى النظام حيًّا بعد ذلكcleanup_timerqueue / timerqueue_del /
rb_erase / rb_next في dmesg (يتطلب root)عند الفشل (عدم التحفيز) يعيد البرنامج المحاولة تلقائيًا --attempts مرة ويطبع نتيجة wait4 في كل جولة،
ثم يقترح ضبط المعاملات: --vmas 120000 --lockers 4 --cpu 0.
panic_on_oops لدى Samsung مساوية لـ 0: عندها تُقتل المهمة ويستمر النواة في العمل مع rbtree تالف،
ويُنصح بالتشغيل المتكرر 2~3 مرات لملاحظة ظهور panic لاحق.بيئة الاختبار: S9280ZHU6EZIF، النواة 6.1.162-android14-11-34343818-abS9280ZHU6EZIF
(بناء 2026-09-20)، بدون root، نطاق shell (uid 2000)، SELinux Enforcing.
| الجولة | الملاحظة | المعنى |
|---|---|---|
| كل جولة | إرجاع العملية الأب tgkill(child_tgid, etid, 0) لقيمة ESRCH | اكتمل تبادل tid في de_thread() |
| كل جولة | ثم kill(child_tgid, SIGKILL)، والعملية الابن rc=-9 | تم إجهاض exec (وليس rc=0 الخاص بـ exec ناجح) |
مقارنة --no-killer | استغرق exec من armed حتى النهاية 277 ms (287 ms مع killer) | قفل الكتابة في mmap أعاق فعلًا mmap_read_lock_killable() في exec_mmap() |
أصابت 5/5 جولات جميعها "ESRCH → SIGKILL → فشل exec"، ولم ينجح exec ولو مرة واحدة.
وهذا يثبت أن نافذة إجهاض exec بعد de_thread قابلة للتحكم ومستقرة؛ فالنافذة تأتي من mprotect الخاص بـ locker
(60 ألف VMA، من 200–700 ms للمرة الواحدة)، بينما استجابة العملية الأب بمستوى µs.
de_thread() ينفذ zap_other_threads() فيقتل جميع
الخيوط الأخرى في العملية نفسها، وبالتالي فإن killer المكتوب داخل العملية الابن يموت بنفسه لحظة de_thread،
ولن يرى ESRCH أبدًا (ويتجلى ذلك في نجاح exec في كل مرة).SIGKILL غير فعّال——ففي تلك اللحظة لم تمت العملية الابن بعد،
ولم يُحرَّر k_itimer بعد بواسطة RCU. الترتيب الصحيح هو
waitid(WNOWAIT) لانتظار الزومبي → انتظار تحرير رد نداء RCU → انتزاع الصفحة → أخيرًا wait4 لتحفيز مسار الاسترجاع.حاليًا تحققت بشكل مستقر خطوة "بقاء العقدة في الطابور بعد التحرير"، لكن rb_erase لحظة wait4
لم يُحدث oops —— ما يعني أن صفحة slab الضحية لم ينتزعها الـ spray الخاص بنا بعد (فالـ partial slab الخاص بـ posix_timers_cache
لا يزال مخزَّنًا في الذاكرة المؤقتة، ولم تعد الصفحة إلى buddy). وهذا يندرج ضمن تنسيق الاسترجاع في الـ exploit،
وليس مشكلة سباق، ويتطلب اتباع أسلوب ملاحظات vivo: أولًا ملء/تفريغ الـ slab بواسطة k_itimer نفسه
لدفع الصفحة كاملة للعودة، ثم استخدام حامل order-0 (مثل AIO ring) لالتقاط النصف المنقسم بدقة.
الاقتراح للخطوة التالية: اتخاذ هذا الجهاز كمرجع، وإجراء استرجاع موجَّه وفق فكرة
docs/08-reclaim-choreography.md (ملاحظات vivo)؛ أو التحقق من حدوث UAF فعليًا باستخدام نواة مبنية ذاتيًا مع KASAN.
السلسلة العامة (Honor uid0.c) تعتمد على أحداث kmem في tracefs للقيام بأمرين حاسمين:
| الاستخدام | المستخدم في Honor 611/6.12 | الجهاز المحلي (Samsung 6.1.162) |
|---|---|---|
إيجاد عنوان الضحية k_itimer V (لحساب إزاحة الـ payload داخل الصفحة) | kmem:kmem_cache_alloc | الحدث موجود، لكن events/kmem/*/enable هو 0644 root:root، والـ shell لا يستطيع enable (events/sched/*/enable قابل للكتابة، أما kmem فلا) |
| تحديد أن صفحة slab من order-1 قد أُعيدت إلى buddy (لحظة الاستحواذ الدقيقة على الصفحة) | kmem:mm_page_free | كما سبق، موجود لكن لا يمكن enable |
| قراءة مخزن trace الخام | per_cpu/cpuN/trace_pipe_raw | قابل للقراءة (-r--r----- root readtracefs) ✓ |
| KASLR slide | مواقع static site لأحداث trace المختلفة | ممكن: أحداث sched قابلة لـ enable، وللمشروع أسلوب مماثل ✓ |
الخلاصة: السلسلة العامة لا يمكن نقلها كما هي. الناقص ليس قدرة النواة (الأحداث كلها موجودة)، بل عدم امتلاك الـ shell صلاحية enable لأحداث kmem.