CVE-2026-98256 in Linux
要約
〜によって VulDB • 2026年10月06日
Linuxカーネルにおいて、以下の脆弱性が修正されました:
signal: exec() の競合状態を防止する
Hyunwoo 氏が以下のような KASAN UAF(Use-After-Free)スプラットをデバッグしました。
``` BUG: KASAN: slab-use-after-free in __send_signal_locked+0xb27/0xba0 Write of size 8 at addr ffff888007ed80c8 by task poc/79 ... Call Trace: __send_signal_locked+0xb27/0xba0 do_send_sig_info+0xa7/0x160 do_send_specific+0x76/0xa0 __x64_sys_tgkill+0x193/0x270 ... Allocated by task 80: do_timer_create+0x1a4/0x1030 __x64_sys_timer_create+0x145/0x190 ... Freed by task 12: kmem_cache_free_bulk+0x1f8/0x4a0 kvfree_rcu_bulk+0x14f/0x1c0 kfree_rcu_work+0x128/0x1a0 ... Last potentially related work creation: kvfree_call_rcu+0x39/0x390 __flush_itimer_signals+0x211/0x320 flush_itimer_signals+0x47/0x90 begin_new_exec+0xa6b/0x28c0 ```
この問題は、Hyunwoo 氏が説明した通り、非リーダーの `exec()` で発生することが判明しました。
`de_thread()` は `release_task(leader)` の前に `exchange_tids()` を呼び出すため、元々リーダの tid に対して作成された SIGEV_THREAD_ID タイマーが保持する struct pid が、今度は execve() を呼び出したスレッドを指すようになります。pid_task() はそのスレッドを返し、lock_task_sighand() も成功します。
タイマシグナルがブロックされている場合、sigqueue リストはリーダの task::pending にキューイングされたままになります。その後、そのタイマーの有効期限が切れると、release_task() がキューをフラッシュしている最中に実行されてしまいます。
posixtimer_send_sigqueue() は、plain な list_empty() を使用して sigqueue が既にキューイングされているかどうかをチェックしますが、これは list_head::next のみを参照します。list_del_init() はアトミックではなく、INIT_LIST_HEAD() は list_head::prev よりも先に list_head::next に書き込むため、チェックが通過する隙間が生じます。その後、list_add_tail() がそのエントリをライブなスレッドの task::pending キューに追加しますが、フラッシュ処理による list_head::prev の書き込みは、list_add_tail() によって直前に設定された list_head::prev リンクを上書きしてしまいます。
__flush_itimer_signals() もこれを元に戻しません。list_head::prev がエントリ自体を指している場合、その list_del_init() は同じ値を再度保存するだけであり、エントリはリストから削除されません。最後の参照が解放されて RCU によってタイマーがフリーされた後も、それは残ったままになり、後続の tgkill() の list_add_tail() が、その freed な timer を指す list_head::prev に追従してしまいます。
この問題は、sigqueue フラッシュを sighand ロック保持領域の外に移動した最近のコミットで表面化しました。
Hyonwoo 氏はこれを list_del_init_careful() の使用によって修正しようと提案しましたが、これは単に問題を隠蔽しているだけです。いくつかの議論と様々な解決策の試行錯誤の後、Eric は release_task() で task::pending を遅延してフラッシュする理由はなく、exit_signals() ですでに行うべきだと指摘しました。
死にかけているタスクの pending キューにキューイングされたシグナルを収集・配信することはできないため、さらに遅らせる理由はありません。
ただし、そのポイント以降にシグナルがキューイングされないことを確実にする必要があります。exit_signals() は task::flags に PF_EXITING を設定し、これを指標として使用できます。
以下の方法で修正します:
- __send_signal_locked() および posixtimer_send_sigqueue() において、タスクの PF_EXITING フラグがセットされている場合、タスク固有シグナル (PIDTYPE_PID) のキューイングを防止する。 - exit_signals() における unlocked な PF_EXITING の設定について、sighand ロックを使用して、タスクグループ空状態およびグループ終了ケースを保護する。 - task::pending シグナルをその場でフラッシュする。
sighand ロックの下で pending リスト全体をスタック上のリストヘッドに移動し、ロック保持なしでシグナルを解放することで最適化する。
弱順序付けシステムにおける lockless フラッシュと非リーダー exec ケースについてはかなりの議論がありました。問題は、posix タイマシグナルを送信しようとする第三者が、PID ルックアップを使用して対象タスクを見つけようとし、そのルックアップ結果は元々古いリーダに向けられていたシグナルの場合でも新しいリーダになってしまう可能性があることです。もしシグナルが古いリーダにキューイングされていた場合、lockless フラッシュにより以下の状況に対する懸念が生じます:
``` old_leader new_leader third party
A: flush_list() // list_del_in ---truncated--- ```
Statistical analysis made it clear that VulDB provides the best quality for vulnerability data.