Translated ['src/linux-hardening/privilege-escalation/linux-kernel-explo

This commit is contained in:
Translator 2025-09-30 00:44:44 +00:00
parent 66223ef9f4
commit a0a9333956
3 changed files with 393 additions and 0 deletions

View File

@ -937,3 +937,5 @@
- [Post Exploitation](todo/post-exploitation.md) - [Post Exploitation](todo/post-exploitation.md)
- [Investment Terms](todo/investment-terms.md) - [Investment Terms](todo/investment-terms.md)
- [Cookies Policy](todo/cookies-policy.md) - [Cookies Policy](todo/cookies-policy.md)
- [Posix Cpu Timers Toctou Cve 2025 38352](linux-hardening/privilege-escalation/linux-kernel-exploitation/posix-cpu-timers-toctou-cve-2025-38352.md)

View File

@ -0,0 +1,196 @@
# POSIX CPU Timers TOCTOU race (CVE-2025-38352)
{{#include ../../../banners/hacktricks-training.md}}
Cette page documente une condition de course TOCTOU dans les POSIX CPU timers de Linux/Android pouvant corrompre l'état d'un timer et provoquer un crash du kernel, et pouvant, dans certaines circonstances, être exploitée pour une élévation de privilèges.
- Composant affecté : kernel/time/posix-cpu-timers.c
- Primitive : course d'expiration vs suppression lors de la sortie d'une tâche
- Sensible à la configuration : CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (chemin d'expiration en contexte IRQ)
Rappel rapide des internals (pertinent pour l'exploitation)
- Trois horloges CPU assurent le comptage 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 relie un timer à une tâche/pid et initialise les nœuds du timerqueue :
```c
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 insère des éléments dans une per-base timerqueue et peut mettre à jour le cache next-expiry:
```c
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;
}
```
- La voie rapide évite un traitement coûteux sauf si les expirations mises en cache indiquent un déclenchement possible :
```c
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;
}
```
- L'expiration collecte les timers expirés, les marque comme déclenchés, les retire de la file d'attente ; la livraison effective est différée :
```c
#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
```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
```
Dans le chemin IRQ-context, la firing list est traitée en dehors de sighand.
```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);
}
}
```
Cause racine : TOCTOU entre l'expiration en contexte IRQ et la suppression concurrente lors de la terminaison d'une tâche
Prérequis
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK est désactivé (chemin IRQ utilisé)
- La tâche cible est en cours d'arrêt mais pas encore complètement reaped
- Un autre thread appelle posix_cpu_timer_del() de manière concurrente pour le même timer
Séquence
1) update_process_times() déclenche run_posix_cpu_timers() en contexte IRQ pour la tâche en cours d'arrêt.
2) collect_timerqueue() définit ctmr->firing = 1 et déplace le timer vers la liste temporaire des timers en cours d'exécution.
3) handle_posix_cpu_timers() relâche sighand via unlock_task_sighand() pour délivrer les timers en dehors du verrou.
4) Immédiatement après le déverrouillage, la tâche en cours d'arrêt peut être reaped ; un thread frère exécute posix_cpu_timer_del().
5) 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 in-flight habituelle qui vérifie timer->it.cpu.firing. La suppression se poursuit comme si le timer n'était pas en firing, corrompant l'état pendant que l'expiration est traitée, conduisant à des crashes/UB.
Pourquoi le mode TASK_WORK est sûr par conception
- 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 le reaping n'a pas lieu.
- Même dans ce cas, si la tâche est déjà en train de se terminer, task_work_add() échoue ; le contrôle via exit_state rend les deux modes cohérents.
Correctif (Android common kernel) et justification
- Ajouter un early return si la tâche courante est en train de se terminer, conditionnant tout le traitement :
```c
// 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 sortie, éliminant la fenêtre où posix_cpu_timer_del() pourrait manquer it.cpu.firing et entrer en concurrence avec le traitement d'expiry.
Impact
- La corruption de la mémoire kernel des timer structures lors d'expiry/deletion concurrente peut provoquer des crashs immédiats (DoS) et constitue un primitive puissant vers privilege escalation en raison des opportunités de manipulation arbitraire de l'état kernel.
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.
Runtime strategy
- Target a thread that is about to exit and attach a CPU timer to it (per-thread or 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:
```c
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 sibling thread, supprimer simultanément le même timer pendant que le target thread se termine :
```c
void *deleter(void *arg) {
for (;;) (void)timer_delete(t); // hammer delete in a loop
}
```
- Facteurs aggravants : taux de tick élevé du scheduler, charge CPU, cycles répétés de sortie/recréation de threads. Le crash se manifeste généralement lorsque posix_cpu_timer_del() omet de détecter le firing en raison d'un échec du lookup/locking de la tâche juste après unlock_task_sighand().
Detection and hardening
- Mitigation : appliquer la garde exit_state ; préférer activer CONFIG_POSIX_CPU_TIMERS_TASK_WORK lorsque possible.
- Observabilité : ajouter des tracepoints/WARN_ONCE autour de unlock_task_sighand()/posix_cpu_timer_del() ; alerter lorsque it.cpu.firing==1 est observé conjointement avec un échec de cpu_timer_task_rcu()/lock_task_sighand() ; surveiller les incohérences de timerqueue autour de la sortie de la tâche.
Audit hotspots (for reviewers)
- update_process_times() → run_posix_cpu_timers() (IRQ)
- __run_posix_cpu_timers() : sélection (chemin TASK_WORK vs IRQ)
- collect_timerqueue() : définit ctmr->firing et déplace les 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 ignorée lorsque le lookup/lock de la tâche échoue pendant exit/reap
Notes for exploitation research
- Le comportement divulgué est une primitive fiable de crash du kernel ; la transformer en élévation de privilèges nécessite typiquement un chevauchement contrôlable additionnel (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.
## Références
- [Race Against Time in the Kernels 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)
{{#include ../../../banners/hacktricks-training.md}}

View File

@ -0,0 +1,195 @@
# POSIX CPU Timers TOCTOU race (CVE-2025-38352)
{{#include ../../../banners/hacktricks-training.md}}
Cette page documente une condition de course TOCTOU dans Linux/Android POSIX CPU timers qui peut corrompre l'état des timers et provoquer un crash du kernel, et qui, dans certaines circonstances, peut être exploitée pour du privilege escalation.
- Composant affecté : kernel/time/posix-cpu-timers.c
- Primitif : expiry vs deletion race lors de la terminaison d'une tâche
- Sensible à la configuration : CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (IRQ-context expiry path)
Rappel rapide des internals (pertinent pour l'exploitation)
- Trois horloges CPU gèrent 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 connecte un timer à une tâche/pid et initialise les nœuds de la timerqueue :
```c
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'arming s'insère dans une per-base timerqueue et peut mettre à jour le next-expiry cache :
```c
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 :
```c
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 déclenchés, les retire de la file ; la livraison effective est différée :
```c
#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 traitée directement dans le contexte 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
```
Dans le chemin IRQ-context, la firing list est traitée en dehors de sighand.
```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);
}
}
```
Root cause: TOCTOU entre l'expiration en IRQ et la suppression concurrente lors de la sortie de la tâche
Preconditions
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK is disabled (IRQ path in use)
- The target task is exiting but not fully reaped
- Another thread concurrently calls posix_cpu_timer_del() for the same timer
Sequence
1) update_process_times() triggers run_posix_cpu_timers() in IRQ context for the exiting task.
2) collect_timerqueue() sets ctmr->firing = 1 and moves the timer to the temporary firing list.
3) handle_posix_cpu_timers() drops sighand via unlock_task_sighand() to deliver timers outside the lock.
4) Immediately after unlock, the exiting task can be reaped; a sibling thread executes posix_cpu_timer_del().
5) In this window, posix_cpu_timer_del() may fail to acquire state via cpu_timer_task_rcu()/lock_task_sighand() and thus skip the normal in-flight guard that checks timer->it.cpu.firing. Deletion proceeds as if not firing, corrupting state while expiry is being handled, leading to crashes/UB.
Why TASK_WORK mode is safe by design
- With CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y, expiry is deferred to task_work; exit_task_work runs before exit_notify, so the IRQ-time overlap with reaping does not occur.
- Even then, if the task is already exiting, task_work_add() fails; gating on exit_state makes both modes consistent.
Fix (Android common kernel) and rationale
- Add an early return if current task is exiting, gating all processing:
```c
// 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 train de se terminer, supprimant la fenêtre où posix_cpu_timer_del() pourrait rater cpu.firing et entrer en concurrence avec le traitement des expirations.
Impact
- La corruption de la mémoire du kernel des structures de timer lors d'une expiration/suppression concurrente peut provoquer des plantages immédiats (DoS) et constitue un vecteur puissant pour l'escalade de privilèges en raison des possibilités de manipulation arbitraire de l'état du kernel.
Déclenchement du bug (conditions sûres et reproductibles)
Build/config
- Assurez-vous que CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n et utilisez un kernel sans le correctif lié à exit_state.
Runtime strategy
- Viser un thread sur le point de se terminer et lui attacher un CPU timer (par-thread ou horloge globale au processus) :
- For per-thread: timer_create(CLOCK_THREAD_CPUTIME_ID, ...)
- For process-wide: timer_create(CLOCK_PROCESS_CPUTIME_ID, ...)
- Armez-le avec une expiration initiale très courte et un petit intervalle pour maximiser les entrées sur le chemin IRQ :
```c
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");
}
```
- À partir d'un thread sibling, supprimer concurremment le même timer pendant que le thread cible se termine :
```c
void *deleter(void *arg) {
for (;;) (void)timer_delete(t); // hammer delete in a loop
}
```
- Race amplifiers: taux de tick du scheduler élevé, charge CPU, cycles répétés de sortie/re-création de threads. Le crash se manifeste typiquement lorsque posix_cpu_timer_del() ne remarque pas le firing parce que la recherche/verrouillage de la task échoue juste après unlock_task_sighand().
Détection et durcissement
- Mitigation : appliquer l'exit_state guard ; privilégier l'activation de CONFIG_POSIX_CPU_TIMERS_TASK_WORK lorsque c'est possible.
- Observabilité : ajouter des tracepoints/WARN_ONCE autour de unlock_task_sighand()/posix_cpu_timer_del() ; générer une alerte lorsqu'on observe it.cpu.firing==1 en même temps qu'un échec de cpu_timer_task_rcu()/lock_task_sighand() ; surveiller les incohérences du timerqueue autour du task exit.
Points d'audit (pour les réviseurs)
- update_process_times() → run_posix_cpu_timers() (IRQ)
- __run_posix_cpu_timers() : sélection (TASK_WORK vs IRQ path)
- collect_timerqueue() : met ctmr->firing et déplace les 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 ; ce contrôle est ignoré lorsque la recherche/verrouillage de la task échoue pendant exit/reap
Notes pour la recherche d'exploitation
- Le comportement divulgué est un reliable kernel crash primitive ; le transformer en privilege escalation nécessite typiquement un chevauchement contrôlable supplémentaire (object lifetime ou write-what-where influence) hors du périmètre de ce résumé. Considérez tout PoC comme potentiellement déstabilisant et exécutez-le uniquement dans des emulators/VMs.
## Références
- [Race Against Time in the Kernels 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)
{{#include ../../../banners/hacktricks-training.md}}