POSIX CPU Timers TOCTOU race (CVE-2025-38352)
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Diese Seite dokumentiert eine TOCTOU-Rennbedingung in Linux/Android POSIX CPU timers, die den Timerzustand beschädigen und den Kernel zum Absturz bringen kann und unter bestimmten Umständen in Richtung Privilegieneskalation gesteuert werden kann.
- Betroffene Komponente: kernel/time/posix-cpu-timers.c
- Primitive: race zwischen expiry und deletion beim task exit
- Konfigurationsabhängig: CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (IRQ-context expiry path)
Kurze interne Zusammenfassung (relevant für exploitation)
- Drei CPU-Uhren treiben das Accounting für Timer via cpu_clock_sample():
- CPUCLOCK_PROF: utime + stime
- CPUCLOCK_VIRT: utime only
- CPUCLOCK_SCHED: task_sched_runtime()
- Beim Erstellen eines Timers wird ein Timer an ein task/pid gebunden und die timerqueue nodes initialisiert:
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 fügt Einträge in eine per-base timerqueue ein und kann den next-expiry-Cache aktualisieren:
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 vermeidet teure Verarbeitung, es sei denn zwischengespeicherte Ablaufzeiten deuten auf mögliche Auslösung hin:
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 sammelt abgelaufene Timer, markiert sie als ausgelöst, verschiebt sie aus der Warteschlange; die tatsächliche Zustellung wird verzögert:
#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;
}
Zwei Modi zur Ablaufverarbeitung
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y: Timer-Ablauf wird über task_work im Ziel-Task verzögert
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n: Timer-Ablauf wird direkt im IRQ-Kontext behandelt
POSIX CPU timer Laufpfade
```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 ```Im IRQ-Kontext-Pfad wird die firing list außerhalb von sighand verarbeitet
IRQ-Kontext-Verarbeitungspfad
```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 zwischen Ablauf zur IRQ-Zeit und gleichzeitiger Löschung während der Task-Beendigung Preconditions
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK is disabled (IRQ path in use)
- Der Ziel-Task beendet sich, ist aber noch nicht vollständig aufgeräumt
- Ein anderer Thread ruft gleichzeitig posix_cpu_timer_del() für denselben Timer auf
Sequence
- update_process_times() löst run_posix_cpu_timers() im IRQ-Kontext für den beendenden Task aus.
- collect_timerqueue() setzt ctmr->firing = 1 und verschiebt den Timer in die temporäre firing-Liste.
- handle_posix_cpu_timers() gibt sighand via unlock_task_sighand() frei, um Timer außerhalb der Sperre zu liefern.
- Unmittelbar nach dem unlock kann der beendende Task reaped werden; ein anderer Thread führt posix_cpu_timer_del() aus.
- In diesem Zeitfenster kann posix_cpu_timer_del() scheitern, den Zustand via cpu_timer_task_rcu()/lock_task_sighand() zu erlangen und überspringt dadurch die normale In-Flight-Prüfung, die timer->it.cpu.firing prüft. Die Löschung erfolgt, als würde der Timer nicht feuern, wodurch der Zustand während der Ablaufbehandlung korruptiert wird und es zu Abstürzen/UB kommt.
Why TASK_WORK mode is safe by design
- With CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y wird der Ablauf an task_work verschoben; exit_task_work läuft vor exit_notify, sodass die IRQ-Zeit-Überlappung mit dem Reaping nicht auftritt.
- Selbst dann, wenn der Task bereits beendet wird, schlägt task_work_add() fehl; das Prüfen von exit_state macht beide Modi konsistent.
Fix (Android common kernel) and rationale
- Füge eine frühe Rückgabe hinzu, wenn der current-Task sich im exiting-Zustand befindet, die die gesamte Verarbeitung sperrt:
// kernel/time/posix-cpu-timers.c (Android common kernel commit 157f357d50b5038e5eaad0b2b438f923ac40afeb)
if (tsk->exit_state)
return;
- Das verhindert das Aufrufen von handle_posix_cpu_timers() für beendende Tasks und eliminiert das Zeitfenster, in dem posix_cpu_timer_del() it.cpu.firing verpassen und mit der Ablaufverarbeitung um die Wette laufen könnte.
Auswirkung
- Kernel-Speicherbeschädigung von Timer-Strukturen während gleichzeitiger Ablauf-/Löschvorgänge kann zu sofortigen Abstürzen (DoS) führen und ist ein mächtiges Primitiv für Privilegieneskalation, da dadurch willkürliche Manipulationen des Kernel-Zustands möglich werden.
Auslösen des Bugs (sichere, reproduzierbare Bedingungen) Build/Konfiguration
- Stelle sicher, dass CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n gesetzt ist und verwende einen Kernel ohne den exit_state gating fix.
Laufzeitstrategie
- Wähle einen Thread, der kurz vor dem Beenden steht, und hänge einen CPU-Timer an ihn an (pro-Thread- oder pro-Prozess-Uhr):
- For per-thread: timer_create(CLOCK_THREAD_CPUTIME_ID, …)
- For process-wide: timer_create(CLOCK_PROCESS_CPUTIME_ID, …)
- Stelle einen sehr kurzen initialen Ablauf und ein kleines Intervall ein, um die Anzahl der Eintritte in den IRQ-path zu maximieren:
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");
}
- Aus einem Neben-Thread gleichzeitig denselben Timer löschen, während der Ziel-Thread beendet wird:
void *deleter(void *arg) {
for (;;) (void)timer_delete(t); // hammer delete in a loop
}
- Race amplifiers: hohe Scheduler-Tick-Rate, CPU-Last, wiederholte Thread-Exit/Recreate-Zyklen. Der Crash tritt typischerweise auf, wenn posix_cpu_timer_del() das Feuern übersieht, weil das Task-Lookup/Locking direkt nach unlock_task_sighand() fehlschlägt.
Detection and hardening
- Mitigation: den exit_state-Guard anwenden; vorzugsweise CONFIG_POSIX_CPU_TIMERS_TASK_WORK aktivieren, wenn möglich.
- Observability: tracepoints/WARN_ONCE um unlock_task_sighand()/posix_cpu_timer_del() hinzufügen; alarmieren, wenn it.cpu.firing==1 zusammen mit fehlgeschlagenem cpu_timer_task_rcu()/lock_task_sighand() beobachtet wird; auf timerqueue-Inkonsistenzen rund um Task-Exit achten.
Audit hotspots (for reviewers)
- update_process_times() → run_posix_cpu_timers() (IRQ)
- __run_posix_cpu_timers() selection (TASK_WORK vs IRQ path)
- collect_timerqueue(): setzt ctmr->firing und verschiebt Knoten
- handle_posix_cpu_timers(): entfernt sighand bevor die Auslöse-Schleife beginnt
- posix_cpu_timer_del(): verlässt sich auf it.cpu.firing, um laufende Expiry zu erkennen; diese Prüfung wird übersprungen, wenn Task-Lookup/Lock beim Exit/Reap fehlschlägt
Notes for exploitation research
- Das veröffentlichte Verhalten ist eine zuverlässige Kernel-Crash-Primitve; um es in eine Privilege Escalation zu verwandeln, wird typischerweise eine zusätzliche kontrollierbare Überlappung benötigt (Objekt-Lebensdauer oder write-what-where-Einfluss), die über den Umfang dieser Zusammenfassung hinausgeht. Behandle jeden PoC als potenziell destabilierend und führe ihn nur in Emulatoren/VMs aus.
Chronomaly exploit strategy (priv-esc without fixed text offsets)
- Tested target & configs: x86_64 v5.10.157 unter QEMU (4 cores, 3 GB RAM). Kritische Optionen:
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: Ein race-Thread (
race_func()) verbrennt CPU, während CPU-Timer feuern;free_func()polltSIGUSR1, um zu bestätigen, ob der Timer gefeuert hat. PasseCPU_USAGE_THRESHOLDso an, dass Signale nur manchmal eintreffen (intermittierende “Parent raced too late/too early”-Meldungen). Wenn Timer bei jedem Versuch feuern, Schwelle senken; wenn sie vor Thread-Exit nie feuern, Schwelle erhöhen. - Dual-process alignment into
send_sigqueue(): Parent/Child-Prozesse versuchen, ein zweites Race-Fenster innerhalb vonsend_sigqueue()zu treffen. Der Parent schläftPARENT_SETTIME_DELAY_USMikrosekunden, bevor Timer gesetzt werden; bei überwiegend “Parent raced too late” nach unten anpassen und bei überwiegend “Parent raced too early” nach oben. Wenn beide Meldungen erscheinen, befindet man sich im Fenster; mit Feinabstimmung ist Erfolg typischerweise innerhalb von ~1 Minute zu erwarten. - Cross-cache UAF replacement: Der Exploit freed ein
struct sigqueueund groomt dann den Allocator-Zustand (sigqueue_crosscache_preallocs()), sodass sowohl das danglinguaf_sigqueueals auch das Ersatz-realloc_sigqueueauf einer Pipe-Buffer-Datenseite landen (cross-cache Reallocation). Die Zuverlässigkeit setzt einen ruhigen Kernel mit wenigen vorherigensigqueue-Allokationen voraus; existieren bereits per-CPU/per-Node partielle Slab-Seiten (bei stark ausgelasteten Systemen), verfehlt die Ersetzung und die Kette bricht. Der Autor hat das bewusst nicht für laute Kernel optimiert.
See also
Ksmbd Streams Xattr Oob Write Cve 2025 37947
References
- Race Against Time in the Kernel’s Clockwork (StreyPaws)
- Android security bulletin – September 2025
- Android common kernel patch commit 157f357d50b5…
- Chronomaly exploit PoC (CVE-2025-38352)
- CVE-2025-38352 analysis – Part 1
- CVE-2025-38352 analysis – Part 2
- CVE-2025-38352 analysis – Part 3
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.


