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

Tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Hierdie bladsy dokumenteer ’n TOCTOU race-voorwaarde in Linux/Android POSIX CPU Timers wat die timer-status kan korrupteer en die kernel kan laat crash, en onder sekere omstandighede na privilege escalation gelei kan word.

  • Geaffekteerde komponent: kernel/time/posix-cpu-timers.c
  • Primitief: expiry vs deletion race onder taak-afsluiting
  • Konfigurasie-sensitief: CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (IRQ-context expiry path)

Kort interne samevatting (relevant vir exploitation)

  • Drie CPU-klokke bestuur rekeninghou van timers via cpu_clock_sample():
  • CPUCLOCK_PROF: utime + stime
  • CPUCLOCK_VIRT: utime only
  • CPUCLOCK_SCHED: task_sched_runtime()
  • Timer-skepping koppel ’n timer aan ’n taak/pid en initialiseer die timerqueue nodes:
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 voer invoegings in ’n per-base timerqueue uit en kan die next-expiry cache bywerk:
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;
}
  • Snelpad vermy duur verwerking tensy gecachede vervaltye moontlike afgaan aandui:
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 versamel vervalde timers, merk hulle as ‘firing’, verwyder hulle uit die tou; werklike aflewering word uitgestel:
#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;
}

Twee vervalverwerkingsmodusse

  • CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y: verval word via task_work op die teiken-taak uitgestel
  • CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n: verval word direk in die IRQ context hanteer
POSIX CPU timer run paths ```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 ```

In die IRQ-context-pad word die firing list buite sighand verwerk

IRQ-context hanteringspad ```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); } } ```

Worteloorsaak: TOCTOU tussen IRQ-time expiry en gelyktydige verwydering tydens taak uitskakeling Voorvereistes

  • CONFIG_POSIX_CPU_TIMERS_TASK_WORK is disabled (IRQ path in use)
  • Die teikentaak is in die proses van uitskakeling, maar nog nie volledig verwyder nie
  • Nog ’n draad roep gelyktydig posix_cpu_timer_del() aan vir dieselfde timer

Volgorde

  1. update_process_times() trigger run_posix_cpu_timers() in IRQ context vir die taak wat uitskakel.
  2. collect_timerqueue() stel ctmr->firing = 1 en skuif die timer na die tydelike firing-lys.
  3. handle_posix_cpu_timers() vrylaat die sighand via unlock_task_sighand() om timers buite die lock af te lewer.
  4. Onmiddellik na die unlock kan die taak verwyder word; ’n suster-thread voer posix_cpu_timer_del() uit.
  5. In hierdie venster kan posix_cpu_timer_del() misluk om state te bekom via cpu_timer_task_rcu()/lock_task_sighand() en sodoende die normale in-flight guard wat timer->it.cpu.firing nagaan oorslaan. Verwydering gaan voort asof dit nie firing is nie, wat state korrupteer terwyl expiry hanteer word, en lei tot crashes/UB.

Waarom TASK_WORK mode per ontwerp veilig is

  • Met CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y word expiry uitgestel na task_work; exit_task_work hardloop voor exit_notify, so die IRQ-time oorvleueling met reaping gebeur nie.
  • Selfs dan, as die taak reeds besig is om te eindig, misluk task_work_add(); gating on exit_state maak beide modes konsekwent.

Fix (Android common kernel) en rasionaal

  • Voeg ’n vroeë terugkeer by as die huidige taak besig is om te eindig, wat alle verwerking gate:
// kernel/time/posix-cpu-timers.c (Android common kernel commit 157f357d50b5038e5eaad0b2b438f923ac40afeb)
if (tsk->exit_state)
return;
  • Dit voorkom dat handle_posix_cpu_timers() vir take wat besig is om te verlaat betree word, en elimineer die venster waar posix_cpu_timer_del() cpu.firing kon mis en met die verstrykingverwerking kon meeding.

Impact

  • Kernel-geheuekorrupsie van timer-strukture tydens gelyktydige verstryking/verwydering kan onmiddellike crashes (DoS) veroorsaak en is ’n sterk primitief vir privilege escalation weens geleenthede vir arbitrêre kernel-state-manipulasie.

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

  • Verseker CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n en gebruik ’n kernel sonder die exit_state gating fix.

Runtime strategy

  • Rig op ’n thread wat op die punt staan om te verlaat en heg ’n CPU timer daaraan (per-thread of process-wide clock):
  • For per-thread: timer_create(CLOCK_THREAD_CPUTIME_ID, …)
  • For process-wide: timer_create(CLOCK_PROCESS_CPUTIME_ID, …)
  • Arm with a very short initial expiration and small interval to maximize IRQ-path entries:
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");
}
  • Vanaf ’n suster-draad, terselfdertyd dieselfde timer verwyder terwyl die teiken-draad beëindig:
void *deleter(void *arg) {
for (;;) (void)timer_delete(t);     // hammer delete in a loop
}
  • Wedrenversterkers: hoë skeduleerder-tiktempo, CPU-belasting, herhaalde thread exit/re-create siklusse. Die crash manifesteer gewoonlik wanneer posix_cpu_timer_del() die afvuur nie opmerk nie as gevolg van mislukte taak-opsoek/locking onmiddellik ná unlock_task_sighand().

Opsporing en verharding

  • Versagting: pas die exit_state-guard toe; verkies om CONFIG_POSIX_CPU_TIMERS_TASK_WORK aan te skakel waar dit uitvoerbaar is.
  • Waarneembaarheid: voeg tracepoints/WARN_ONCE rondom unlock_task_sighand()/posix_cpu_timer_del(); waarsku wanneer it.cpu.firing==1 waargeneem word tesame met mislukte cpu_timer_task_rcu()/lock_task_sighand(); hou dop vir timerqueue-ongeregeldhede rondom taak-exit.

Oudit-hotspots (vir beoordelaars)

  • update_process_times() → run_posix_cpu_timers() (IRQ)
  • __run_posix_cpu_timers() seleksie (TASK_WORK vs IRQ path)
  • collect_timerqueue(): stel ctmr->firing en skuif nodes
  • handle_posix_cpu_timers(): loslaat sighand voor die afvuur-lus
  • posix_cpu_timer_del(): vertrou op it.cpu.firing om in-flight expiry te detecteer; hierdie kontrole word oorgeslaan wanneer taak-opsoek/slot misluk tydens exit/reap

Aantekeninge vir exploit-navorsing

  • Die bekendgemaakte gedrag is ’n betroubare kernel crash primitive; om dit in ’n privilege escalation om te skakel vereis tipies ’n bykomende beheerbare oorvleueling (object lifetime of write-what-where invloed) buite die bestek van hierdie opsomming. Beskou enige PoC as potensieel destabiliserend en voer slegs in emulators/VMs uit.

Chronomaly exploit strategy (priv-esc without fixed text offsets)

  • Getoetste teiken & konfigurasies: x86_64 v5.10.157 onder QEMU (4 cores, 3 GB RAM). Kritieke opsies: CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n, CONFIG_PREEMPT=y, CONFIG_SLAB_MERGE_DEFAULT=n, DEBUG_LIST=n, BUG_ON_DATA_CORRUPTION=n, LIST_HARDENED=n.
  • Race steering with CPU timers: ’n racende thread (race_func()) verbruik CPU terwyl CPU timers afvuur; free_func() poll SIGUSR1 om te bevestig of die timer afgevuur het. Stel CPU_USAGE_THRESHOLD so dat seine slegs soms aankom (intermitterende “Parent raced too late/too early” boodskappe). As timers elke poging afvuur, verlaag die drempel; as hulle nooit voor thread-exit afvuur nie, verhoog dit.
  • Dual-process alignment into send_sigqueue(): Parent/child processes probeer om ’n tweede race-venster binne send_sigqueue() te tref. Die ouer slaap PARENT_SETTIME_DELAY_US mikrosekondes voor die armen van timers; pas dit neerwaarts aan wanneer jy meestal “Parent raced too late” sien en opwaarts wanneer jy meestal “Parent raced too early” sien. Om beide te sien dui aan dat jy die venster dek; sukses word verwag binne ~1 minuut sodra dit ingestel is.
  • Cross-cache UAF replacement: Die exploit vry struct sigqueue en maak dan die allocator-toestand gereed (sigqueue_crosscache_preallocs()) sodat beide die dangleende uaf_sigqueue en die vervanging realloc_sigqueue op ’n pipe buffer data page land (cross-cache reallocation). Betroubaarheid veronderstel ’n stil kernel met min vorige sigqueue-toewysings; as per-CPU/per-node gedeeltelike slab-bladsye reeds bestaan (besige stelsels), sal die vervanging misluk en die ketting faal. Die outeur het dit opsetlik on-geoptimaliseer gelaat vir lawaaierige kernels.

See also

Ksmbd Streams Xattr Oob Write Cve 2025 37947

Verwysings

Tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks