POSIX CPU Timers TOCTOU race (CVE-2025-38352)
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Cette page documente une condition de course TOCTOU dans les POSIX CPU timers de Linux/Android qui peut corrompre l’état d’un timer et faire planter le kernel, et dans certaines circonstances être exploitée pour une escalation de privilèges.
- Affected component: kernel/time/posix-cpu-timers.c
- Primitive : race entre expiration et suppression lors de la terminaison d’une tâche
- Config sensitive: CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (IRQ-context expiry path)
Rappel rapide des internals (pertinent pour l’exploitation)
- Trois horloges CPU pilotent la comptabilité des timers via cpu_clock_sample():
- CPUCLOCK_PROF: utime + stime
- CPUCLOCK_VIRT: utime uniquement
- CPUCLOCK_SCHED: task_sched_runtime()
- La création d’un timer associe un timer à une task/pid et initialise les nœuds de la 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;
}
- L’armement insère dans une timerqueue par base et peut mettre à jour le cache next-expiry:
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;
}
- Le chemin rapide évite un traitement coûteux sauf si les expirations mises en cache indiquent un déclenchement possible :
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 collecte les timers expirés, les marque comme firing, les déplace hors de la queue ; la livraison effective est différée :
#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;
}
Deux modes de traitement des expirations
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y: l’expiration est différée via task_work sur la tâche cible
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n: l’expiration est gérée directement dans le contexte 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
Dans le chemin IRQ-context, la firing list est traitée en dehors de 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);
}
}
Root cause: TOCTOU between IRQ-time expiry and concurrent deletion under task exit
Préconditions
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK is disabled (le chemin IRQ est utilisé)
- La tâche cible est en train de se terminer mais ses ressources n’ont pas encore toutes été libérées
- Un autre thread appelle simultanément posix_cpu_timer_del() pour le même timer
Sequence
- update_process_times() déclenche run_posix_cpu_timers() en contexte IRQ pour la tâche en cours d’arrêt.
- collect_timerqueue() met ctmr->firing = 1 et déplace le timer vers la liste temporaire des timers en cours d’exécution.
- handle_posix_cpu_timers() relâche sighand via unlock_task_sighand() pour délivrer les timers en dehors du verrou.
- Immédiatement après le unlock, la tâche en cours d’arrêt peut être reclamée ; un thread sibling exécute posix_cpu_timer_del().
- Dans cette fenêtre, posix_cpu_timer_del() peut échouer à acquérir l’état via cpu_timer_task_rcu()/lock_task_sighand() et ainsi sauter la garde normale en vol qui vérifie timer->it.cpu.firing. La suppression se poursuit comme si le timer n’était pas en cours d’exécution, corrompant l’état pendant que l’expiration est traitée, conduisant à des plantages/UB.
Why TASK_WORK mode is safe by design
- Avec CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y, l’expiration est différée vers task_work ; exit_task_work s’exécute avant exit_notify, donc le chevauchement en contexte IRQ avec la réclamation n’a pas lieu.
- Même alors, si la tâche est déjà en cours de sortie, task_work_add() échoue ; conditionner sur exit_state rend les deux modes cohérents.
Fix (Android common kernel) and rationale
- Ajouter un return anticipé si la tâche courante est en train de se terminer, conditionnant tout le traitement :
// kernel/time/posix-cpu-timers.c (Android common kernel commit 157f357d50b5038e5eaad0b2b438f923ac40afeb)
if (tsk->exit_state)
return;
- Cela empêche d’entrer dans handle_posix_cpu_timers() pour les tâches en cours de sortie, éliminant la fenêtre où posix_cpu_timer_del() pourrait manquer it.cpu.firing et entrer en condition de course avec le traitement d’expiration.
Impact
- La corruption de la mémoire du noyau des structures de timer lors d’expiration/suppression concurrentes peut provoquer des plantages immédiats (DoS) et constitue un primitif puissant pour l’élévation de privilèges, en offrant des opportunités de manipulation arbitraire de l’état du noyau.
Triggering the bug (safe, reproducible conditions) Build/config
- Assurez-vous que CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n et utilisez un noyau sans le correctif de gating exit_state.
Runtime strategy
- Ciblez un thread sur le point de se terminer et attachez-lui un CPU timer (par-thread ou horloge globale au processus) :
- Pour un timer par-thread : timer_create(CLOCK_THREAD_CPUTIME_ID, …)
- Pour un timer global au processus : timer_create(CLOCK_PROCESS_CPUTIME_ID, …)
- Armez-le avec une expiration initiale très courte et un intervalle faible pour maximiser les entrées sur l’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");
}
- Depuis un thread frère, supprimer simultanément le même timer pendant que le thread cible se termine :
void *deleter(void *arg) {
for (;;) (void)timer_delete(t); // hammer delete in a loop
}
- Amplificateurs de race : high scheduler tick rate, CPU load, cycles répétés d’arrêt/re-création de thread. Le crash se manifeste typiquement lorsque posix_cpu_timer_del() omet de remarquer le firing en raison d’un échec de la recherche/verrouillage de la task juste après unlock_task_sighand().
Détection et durcissement
- Atténuation : appliquer le exit_state guard ; préférer activer CONFIG_POSIX_CPU_TIMERS_TASK_WORK quand c’est possible.
- Observabilité : ajouter des tracepoints/WARN_ONCE autour de unlock_task_sighand()/posix_cpu_timer_del() ; alerter lorsqu’on observe it.cpu.firing==1 conjointement avec un échec de cpu_timer_task_rcu()/lock_task_sighand() ; surveiller les incohérences de timerqueue autour de la sortie de la task.
Points chauds d’audit (pour les relecteurs)
- update_process_times() → run_posix_cpu_timers() (IRQ)
- __run_posix_cpu_timers() selection (TASK_WORK vs IRQ path)
- collect_timerqueue() : définit ctmr->firing et déplace des nœuds
- handle_posix_cpu_timers() : libère sighand avant la boucle de firing
- posix_cpu_timer_del() : s’appuie sur it.cpu.firing pour détecter une expiration en vol ; cette vérification est contournée lorsque la recherche/verrouillage de la task échoue pendant exit/reap
Notes pour la recherche d’exploitation
- Le comportement divulgué est une primitive fiable de crash du kernel ; le convertir en escalation de privilèges nécessite généralement un chevauchement contrôlable supplémentaire (durée de vie d’objet ou influence write-what-where) hors du périmètre de ce résumé. Traitez tout PoC comme potentiellement déstabilisant et exécutez-le uniquement dans des émulateurs/VMs.
Voir aussi
Ksmbd Streams Xattr Oob Write Cve 2025 37947
Références
- Course contre la montre dans le mécanisme d’horloge du kernel (StreyPaws)
- Bulletin de sécurité Android – septembre 2025
- Commit de correctif du kernel common Android 157f357d50b5…
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
HackTricks

