POSIX CPU Timers TOCTOU race (CVE-2025-38352)
Reading time: 7 minutes
tip
AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking'i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter'da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
Bu sayfa, Linux/Android POSIX CPU timers içindeki bir TOCTOU yarış koşulunu belgeler; bu durum timer durumunu bozabilir ve kernel'in çökmesine sebep olabilir ve bazı durumlarda privilege escalation yönlendirilebilir.
- Etkilenen bileşen: kernel/time/posix-cpu-timers.c
- İlkel: expiry vs deletion yarışması görev çıkışı sırasında
- Konfigürasyona duyarlı: CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (IRQ-context expiry path)
Hızlı iç yapı özeti (relevant for exploitation)
- Üç CPU clock'u cpu_clock_sample() aracılığıyla timer muhasebesini sağlar:
- CPUCLOCK_PROF: utime + stime
- CPUCLOCK_VIRT: utime only
- CPUCLOCK_SCHED: task_sched_runtime()
- Timer oluşturma, bir timer'ı task/pid ile ilişkilendirir ve timerqueue düğümlerini başlatır:
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, per-base timerqueue'ya ekleme yapar ve next-expiry cache'i güncelleyebilir:
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;
}
- Hızlı yol, önbelleğe alınmış sona erme bilgileri olası bir tetiklemeyi gösterene kadar pahalı işlemlerden kaçınır:
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;
}
- Expiration, süresi dolmuş timer'ları toplar, bunları firing olarak işaretler, kuyruktan çıkarır; gerçek teslimat ertelenir:
#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;
}
İki sona erme işleme modu
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y: sona erme, hedef görevde task_work aracılığıyla ertelenir
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n: sona erme doğrudan IRQ bağlamında işlenir
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
IRQ-context yolunda, firing list sighand dışında işlenir.
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 between IRQ-time expiry and concurrent deletion under task exit Preconditions
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK is disabled (IRQ path in use)
- The target task is exiting but not fully reaped
- Another thread concurrently calls posix_cpu_timer_del() for the same timer
Sequence
- update_process_times() triggers run_posix_cpu_timers() in IRQ context for the exiting task.
- collect_timerqueue() sets ctmr->firing = 1 and moves the timer to the temporary firing list.
- handle_posix_cpu_timers() drops sighand via unlock_task_sighand() to deliver timers outside the lock.
- Immediately after unlock, the exiting task can be reaped; a sibling thread executes posix_cpu_timer_del().
- In this window, posix_cpu_timer_del() may fail to acquire state via cpu_timer_task_rcu()/lock_task_sighand() and thus skip the normal in-flight guard that checks timer->it.cpu.firing. Deletion proceeds as if not firing, corrupting state while expiry is being handled, leading to crashes/UB.
Why TASK_WORK mode is safe by design
- With CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y, expiry is deferred to task_work; exit_task_work runs before exit_notify, so the IRQ-time overlap with reaping does not occur.
- Even then, if the task is already exiting, task_work_add() fails; gating on exit_state makes both modes consistent.
Fix (Android common kernel) and rationale
- Add an early return if current task is exiting, gating all processing:
// kernel/time/posix-cpu-timers.c (Android common kernel commit 157f357d50b5038e5eaad0b2b438f923ac40afeb)
if (tsk->exit_state)
return;
- Bu, exit eden görevler için handle_posix_cpu_timers()'e girilmesini engeller; posix_cpu_timer_del()'in it.cpu.firing'i kaçırabileceği ve expiry işlemesi ile yarışabileceği pencereyi ortadan kaldırır.
Impact
- Zamanlayıcı yapılarına yönelik eşzamanlı expiry/silme sırasında kernel belleğinin bozulması, anında çöküşlere (DoS) yol açabilir ve keyfi kernel durumunun manipülasyonu imkanları nedeniyle ayrıcalık yükseltmeye yönelik güçlü bir primitive sağlar.
Triggering the bug (safe, reproducible conditions) Build/config
- Ensure CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n and use a kernel without the exit_state gating fix.
Runtime strategy
- Çıkmak üzere olan bir thread'i hedefleyin ve ona bir CPU timer atayın (per-thread veya process-wide clock):
- For per-thread: timer_create(CLOCK_THREAD_CPUTIME_ID, ...)
- For process-wide: timer_create(CLOCK_PROCESS_CPUTIME_ID, ...)
- Çok kısa bir initial expiration ve küçük bir interval ile arm edin ki IRQ-path girişlerini maksimize edin:
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");
}
- Bir kardeş iş parçacığından, hedef iş parçacığı çıkarken aynı timer'ı eşzamanlı olarak silin:
void *deleter(void *arg) {
for (;;) (void)timer_delete(t); // hammer delete in a loop
}
- Race amplifiers: yüksek scheduler tick hızı, CPU yükü, tekrarlayan thread çıkış/yine-oluşturma döngüleri. Çökme tipik olarak posix_cpu_timer_del()'in unlock_task_sighand()'den hemen sonra task lookup/locking başarısız olduğunda firing'i fark etmeyi atlamasıyla ortaya çıkar.
Detection and hardening
- Mitigation: exit_state guard'ını uygulayın; mümkünse CONFIG_POSIX_CPU_TIMERS_TASK_WORK'u etkinleştirmeyi tercih edin.
- Observability: unlock_task_sighand()/posix_cpu_timer_del() etrafına tracepoints/WARN_ONCE ekleyin; it.cpu.firing==1 ile cpu_timer_task_rcu()/lock_task_sighand() başarısızlığı birlikte gözlendiğinde alarm verin; task çıkışı çevresinde timerqueue tutarsızlıklarını izleyin.
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
- The disclosed behavior is a reliable kernel crash primitive; turning it into privilege escalation typically needs an additional controllable overlap (object lifetime or write-what-where influence) beyond the scope of this summary. Treat any PoC as potentially destabilizing and run only in emulators/VMs.
References
- Race Against Time in the Kernel’s Clockwork (StreyPaws)
- Android security bulletin – September 2025
- Android common kernel patch commit 157f357d50b5…
tip
AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking'i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter'da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.