
تحليل لثغرة استخدام بعد التحرير (UAF) في نواة لينكس الخاصة بـ CVE-2026-64560 مع كود إثبات المفهوم (PoC) المُحفِّز للسباق، ومراجعة التصحيح، ومصفوفة الإصدارات المتأثرة من LTS/أندرويد، وفحص ذاتي للأجهزة المحدّثة.
مُعيد الإنتاج / PoC (للتحقق من حدوث الثغرة): Linux وAndroid (NDK) يُستخدم هذا المستودع فقط للتحقق من حالة التصحيح ولأغراض البحث والتعلّم على أجهزة الاختبار الخاصة بك، ولا يحتوي على أي بدائيات تصعيد صلاحيات أو استغلال.
| الحقل | المحتوى |
|---|---|
| CVE ID | CVE-2026-64560 |
| العنوان | posix-cpu-timers: Prevent UAF caused by non-leader exec() race |
| النوع | استخدام بعد التحرير (Use-After-Free) (CWE-416)، حالة سباق (race condition) |
| CNA | kernel.org (Linux CNA) |
| CVSS v3.1 | 7.8 High — CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CVSS v4.0 (SUSE) | 8.5 High — CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
| EPSS | ~0.12% (المئين الثاني، عند 2026-08) |
| CISA KEV | غير مُدرج |
| تاريخ الإفصاح | 2026-07-29 |
| التزام الإصلاح (mainline) | 920f893f735e92ba3a1cd9256899a186b161928d |
| الالتزام المُسبِّب للمشكلة (Fixes:) | 55e8c8eb2c7b (v5.7, 2020) — "posix-cpu-timers: Store a reference to a pid not a task" |
| المُصلِح | Thomas Gleixner <[email protected]> |
| المُبلِّغ | Wongi Lee <[email protected]>, Jungwoo Lee <[email protected]> |
| الملفات المتأثرة | kernel/exit.c, kernel/signal.c, kernel/time/posix-cpu-timers.c |
أُدخلت الثغرة في v5.7 (2020-05)، ونُقل الإصلاح (backport) إلى فروع stable المختلفة:
| الفرع | المتأثر | الإصدار المُصحَّح (≥) | التزام الإصلاح في stable |
|---|---|---|---|
| 5.10 LTS | 5.7 ~ 5.10.261 | 5.10.262 | 67aa823e3e8c |
| 5.15 LTS | ~ 5.15.212 | 5.15.213 | d8bcb28abad8 |
| 6.1 LTS | ~ 6.1.179 | 6.1.180 | cc35ddbc4973 |
| 6.6 LTS | ~ 6.6.146 | 6.6.147 | 12a891c773ae |
| 6.12 LTS | ~ 6.12.99 | 6.12.100 | e74443f5db00 |
| 6.18 | ~ 6.18.40 | 6.18.41 | 6a7ecc25abe6 |
| 7.1 | ~ 7.1.4 | 7.1.5 | ad1cafa1bdaa |
| mainline | < 7.2-rc3 | 7.2-rc3 | 920f893f735e |
نواة Android GKI مبنية على 5.10 / 5.15 / 6.1 / 6.6 / 6.12 LTS، وجميعها ضمن النطاق المتأثر. نظرًا لأن التزام الإصلاح الرئيسي (mainline) صدر في 2026-07-29، فإن أجهزة Android التي يكون مستوى التصحيح الأمني (SPL) فيها 2026-08-01 أو قبل ذلك لا تتضمن هذا الإصلاح في الغالب. يمكنك التأكد من إصدار النواة وSPL على الجهاز باستخدام adb shell cat /proc/version وgetprop ro.build.version.security_patch.
تُدار موقّتات POSIX CPU (timer_create(CLOCK_PROCESS_CPUTIME_ID, ...) / timer_create(CLOCK_THREAD_CPUTIME_ID, ...)) داخل النواة عبر kernel/time/posix-cpu-timers.c. يحتفظ كل k_itimer بالمهمة المستهدفة من خلال it.cpu.pid؛ وعند التعامل مع الموقّت يلزم استدعاء lock_task_sighand(p, &flags) للحصول على sighand->siglock الخاص بتلك المهمة لحماية timerqueue.
الالتزام 55e8c8eb2c7b لعام 2020 استبدل مؤشر task المخزَّن مؤقتًا في الموقّت بـ مرجع pid (لإصلاح مشكلة سبّبها workaround عام 2010 e0a70217107e)، مع إجراء بحث pid_task(pid, type) قبل كل عملية. ترك هذا التغيير نافذة السباق (race window) الخاصة بهذه الثغرة.
عندما يتم استدعاء execve() بواسطة خيط غير قائد (non-leader thread)، يؤدي de_thread() → switch_leader() إلى نقل TGID من القائد القديم إلى القائد الجديد، ويمرّ القائد القديم عبر release_task() → __exit_signal()، حيث يتم تعيين old_leader->sighand = NULL واستدعاء unhash_task(old_leader).
في الوقت نفسه، على نواة معالجة (CPU) أخرى، يُنفَّذ sys_timer_delete() → posix_cpu_timer_del():
sys_timer_delete() exec()
posix_cpu_timer_del()
// 观察到旧 leader
p = pid_task(pid, pid_type); de_thread()
switch_leader();
release_task(old_leader)
__exit_signal(old_leader)
sighand = lock(old_leader, sighand);
posix_cpu_timers*_exit();
sighand = lock_task_sighand(p) unhash_task(old_leader);
sh = lock(p, sighand) old_leader->sighand = NULL;
unlock(sighand);
(p->sighand == NULL)
unlock(sh)
return NULL;
// 直接返回,没有摘链!
if (!sighand)
return 0;
free_posix_timer(); // ← k_itimer 被释放
إن p الذي يعثر عليه posix_cpu_timer_del() هو القائد القديم، وعند هذه النقطة يكون p->sighand == NULL، فتعتبر الدالة أن «المهمة في طور الخروج وأن مسار exit سيتكفل بفكّ الارتباط»، وبالتالي تعود بالنجاح دون أن تفعل أي شيء. بعد ذلك يُحرِّر free_posix_timer() كائن k_itimer.
النقطة الأساسية: يختلف exec() عن exit() — ففي exec() لا يتغير TGID، والموقّتات المفعلة (armed) المرتبطة بمستوى العملية (p->signal->cpu_timers) تُورَّث وتبقى في قائمة الانتظار. وبالتالي:
run_posix_cpu_timers() (الذي يجتاز timerqueue في كل tick) يصل إلى timerqueue_node لكائن مُحرَّر → قراءة/كتابة UAF؛مشكلات مماثلة موجودة أيضًا في:
posix_cpu_timer_set(): بالنسبة إلى الموقّت العادي فإنها تُرجع -ESRCH مؤقتًا فقط؛ لكن do_cpu_nanosleep() داخل النواة تستخدم k_itimer مُخَصَّصًا على المكدس (stack)، وهي نفس مشكلة UAF.posix_cpu_timer_rearm(): فشل صامت في إعادة التسليح (rearm)، فلا يعود الموقّت ينقضي (خطأ وظيفي).أشار Frederic Weisbecker إلى أن tsk->sighand = NULL داخل __exit_signal() هو تخزين عادي (plain store)؛ وعلى البنى ضعيفة الترتيب مثل ARM64، عندما يرصد posix_cpu_timer_del() أن sighand == NULL فإنه ليس مضمونًا أن يرصد أيضًا عمليات الكتابة الخاصة بفكّ الارتباط التي سبقت posix_cpu_timers*_exit()، مما قد يؤدي إلى إنذار خاطئ من WARN_ON_ONCE(timer_queued(tmr)).
__exit_signal() يتم التعديل إلى smp_store_release(&tsk->sighand, NULL)؛smp_acquire__after_ctrl_dep() في مسار !sighand داخل lock_task_sighand()؛timer_lock_sighand(): تبحث عن المهمة + تقفل sighand، وإذا كان sighand == NULL فإنها لا تعود بل تعيد محاولة البحث — في سيناريو exec ستجد القائد الجديد، ولا تستسلم إلا في سيناريو exit عندما يفشل البحث؛_del / _set / _rearm) تتحول جميعها إلى استخدام هذه الدالة المساعدة بشكل موحّد.انظر الـ diff الكامل في patches/920f893f735e.patch.
يوفر مجلد poc/ مُحفِّز سباق (race trigger): خيطان يعمل كل منهما في حلقة عالية الكثافة
timer_create(CLOCK_PROCESS_CPUTIME_ID) → تسليح (arm) (بزمن انتهاء ابتدائي قصير جدًا) → انتظار مشغول (busy-wait) لحدوث الإثارة → timer_delete()؛fork() → داخل العملية الابنة إنشاء خيط غير قائد، يقوم هذا الخيط باستدعاء execve() (exec من خيط غير قائد شرط ضروري لهذه الثغرة)، وتقوم العملية الأم فورًا باستدعاء waitpid() لاستعادة (reap) العملية.