
CVE-2026-43805 IOKit IODMACommand レースコンディション分析と概念実証
Apple IOKit の IODMACommand::PerformOperation_Impl に欠けていた状態ロックを
macOS 26.5/26.6 カーネルバイナリ diff から見つけ出した分析です。脆弱なビルドでは
PerformOperation が準備状態とメモリディスクリプタを使用している間に
CompleteDMA が同じ状態を解放できます。26.6 のパッチは、両方の経路がすでに
共有していた fDextLock を PerformOperation_Impl にも適用します。
検証状態
バイナリ diff、呼び出し対象、公開 XNU ソースのマッピングと決定論的競合モデルは 検証しました。リポジトリのネイティブ DriverKit トリガーは Linux/WSL 環境で 作成されたため、まだ Xcode ビルドおよび脆弱/パッチ済み macOS の A/B 実行を 行えていません。したがってこのリポジトリは、現時点で実際のカーネルパニックや 制御可能な kernel write を観察したとは主張しません。
Apple はこの問題を IOKit の race condition に分類し、ローカルアプリが予期しない システム終了またはカーネルメモリ書き込みを引き起こす可能性があると説明しています。 報告者は イ・ジェヨンです。Apple macOS Tahoe 26.6 セキュリティ情報、 Apple iOS/iPadOS 26.6 セキュリティ情報
| 製品 | 初回修正バージョン | 分析で使用した脆弱/修正のペア |
|---|---|---|
| iOS / iPadOS | 26.6 | iPhone 11: 26.5.2 (23F84) → 26.6 (23G71) |
| macOS Tahoe | 26.6 | KDK 26.5 (25F71) → KDK 26.6 (25G70) |
| macOS Sequoia | 15.7.8 | 情報で修正バージョンのみ確認 |
| macOS Sonoma | 14.8.8 | 情報で修正バージョンのみ確認 |
| watchOS | 26.6 | 情報で修正バージョンのみ確認 |
Sequoia と Sonoma の修正バージョンはそれぞれ Apple 15.7.8 情報と Apple 14.8.8 情報、watchOS は Apple 26.6 情報で確認できます。
2026-09-27 に CVE ID と PoC、exploit、GitHub、IOKit、報告者名を組み合わせて
検索しましたが、公開された再現コードや原因分析は見つかりませんでした。この文は
検索当時の観察であり、公開 PoC が絶対に存在しないという意味ではありません。
修正バージョンに対応する XNU ソースが公開されていないため、ソースコミット diff の
代わりに KDK カーネルを比較しました。脆弱側は Apple が公開した
xnu-12377.121.6と
正確に対応しますが、分析日時点で修正側の xnu-12377.161.13 タグはありませんでした。
KDK は Apple が提供するカーネルシンボル・デバッグアーティファクトであり、Apple も
バージョンに合った KDK を使用するよう案内しています。Apple KDK ガイド
比較に使用したファイルはリポジトリに含めません。
| 区分 | ファイル | SHA-256 |
|---|---|---|
| 脆弱 KDK DMG | Kernel_Debug_Kit_26.5_build_25F71.dmg | 90ed319cd1ba6e23d1eefcee89fa1b10743f4cf60b85208ae52aa9f45543c7aa |
| 修正 KDK DMG | Kernel_Debug_Kit_26.6_build_25G70.dmg | edaa0b3d431b43ba7dda2f0e82314b19d5476ba4089a7d8c1de18c49b3ad168b |
| 脆弱 x86_64 カーネル | XNU 12377.121.6~2 | f4dcaa7e59ae59f3d945c2332862772596b71e9823cc202983ba4f9d897b5bec |
| 修正 x86_64 カーネル | XNU 12377.161.13~4 | 754db65cb15599183ccebfaa2e4909f673de5d7da42ba4293256cb1184587efd |
| 脆弱 iOS kernelcache | XNU 12377.122.8~1 | 7bd856548a2d170a5f8c962e9a5740290e5a4ba9a6ac39c96c927d4dd400d890 |
| 修正 iOS kernelcache | XNU 12377.162.13~2 | 680fa3925641e2b81541c8455fc6d8d432acad9f059ac44bb48324791049c9d6 |
tools/diff_macho_symbols.pyは Mach-O シンボルと実行
セクションを読み、C++ 名を復元したうえで分岐・呼び出し対象と RIP 相対アドレスを
正規化し、関数ごとの命令を比較します。IOKit 名を持つ変更関数 19 個のうち、診断用の
行番号変化を除けば IODMACommand::PerformOperation_Impl の制御フロー変化が情報の
“improved state handling”と一致しました。
IODMACommand::PerformOperation_Impl(...)
vulnerable: 0xffffff8000a39080, 512 bytes
fixed: 0xffffff8000a3b860, 544 bytes
normalized similarity: 0.621429
修正カーネルには次の呼び出しが新たに追加されます。
mov rax, [r14 + 0x70] ; IODMACommand::reserved
mov rdi, [rax + 0xc0] ; IODMACommandInternal::fDextLock
call IOLockLock
; fActive 確認および readBytes/writeBytes 作業
mov rax, [r14 + 0x70]
mov rdi, [rax + 0xc0]
call IOLockUnlock
脆弱な公開ソースを見ると
PrepareForDMA_Implと
CompleteDMA_Implは
すでに fDextLock を取得しています。一方
PerformOperation_Implは
fActive を検査した後、同じロックなしで fMemory、readBytes、writeBytes を使用します。
sequenceDiagram
participant A as Worker A: PerformOperation
participant C as IODMACommand state
participant B as Worker B: CompleteDMA
A->>C: fActive == true 確認
A->>A: 一時バッファ割り当て/準備
B->>C: fDextLock 取得
B->>C: complete(), fActive 減少
B->>C: clearMemoryDescriptor(), fMemory 解放
B-->>C: fDextLock 解放
A->>C: readBytes/writeBytes で fMemory 使用
Note over A,C: 状態検査と使用の間の TOCTOU
PerformOperation_Impl の検査と使用の間に CompleteDMA_Impl が実行されると、
最初に確認した準備状態がもはや有効ではありません。fMemory は
OSSharedPtr<IOMemoryDescriptor> ですが、この経路は作業全体を保護する別の参照や
ロックを確保しません。その結果、解放または置換された DMA メモリ状態を引き続き
使用する可能性があります。
パッチ後、3 つの DMA RPC は同じロックで状態遷移を直列化します。
flowchart LR
P[PrepareForDMA] -->|fDextLock| S[(fActive / fMemory)]
O[PerformOperation] -->|fDextLock 追加| S
C[CompleteDMA] -->|fDextLock| S
Apple の情報は起こりうる影響として kernel-memory write を明記していますが、 バイナリ diff だけでは安定した書き込みアドレス・値の制御までは立証されません。 ここで確定できるのは、寿命/準備状態の競合とそれを防ぐロックの追加です。
poc/model/race_model.cppはカーネル API を呼び出さず、
問題となるインターリーブを再現します。脆弱な経路では状態確認直後に lifecycle
スレッドがメモリを解放するよう強制し、修正経路では同一ロックが解放を
遅延させるかを確認します。
make model
検証した出力:
vulnerable path: stale memory access = observed
fixed path: stale memory access = not observed
このモデルはパッチ原理を検証する補助 PoC であり、Apple カーネル脆弱性の実際の 衝突結果ではありません。
poc/apple-driverkit-sampleは Apple の公式
DriverKit user-client サンプルを
基にしています。追加した selector 6 は、2 つの独立した DriverKit dispatch queue で
次を繰り返します。
Worker A: IODMACommand::PerformOperation(Zero, ...)
Worker B: IODMACommand::CompleteDMA()
IODMACommand::PrepareForDMA(...)
PerformOperation(Zero)は大きな一時バッファを準備した後に DMA メモリへ書き込むため、
fActive 確認と実際の writeBytes の間の競合ウィンドウを広げます。既定値は 1 MiB、
10,000 回であり、アプリの SwiftRunDMACommandRace() で調整できます。
カーネルパニックの可能性があるため、保存していない作業がない別のテスト Mac または 元に戻せるテストボリュームでのみ実行します。
poc/apple-driverkit-sample/DriverKitUserClientSample/DriverKitUserClientSample.xcodeproj
を開きます。DriverKitSampleApp と NullDriver target の Team と固有の bundle ID を設定します。DriverKitSampleApp scheme をビルド・実行し、Install Dext を押します。log stream --style compact --predicate 'eventMessage contains "CVE-2026-43805"'
修正版の期待結果は、システムが維持され race completed without a kernel panic
ログが出力されることです。脆弱版は競合が的中すればシステムパニック/再起動が
起こりうる失敗シグナルです。反復が終了したという事実だけで脆弱でないと結論づける
ことはできません。スケジューリングに依存する race であるため、複数回実行したり
バッファサイズと反復回数を増やす必要があるかもしれません。
ネイティブハーネスはまだ実際の Apple テストホストで実行していません。成功、 パニック、panic log の backtrace は観察後にのみ結果として記録すべきです。
KDK の Payload を取得し、Python 依存関係をインストールしてから実行します。
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
python tools/extract_pbzx_cpio.py /path/to/25F71/Payload \
--match 'System/Library/Kernels/kernel$' --output /tmp/kdk-25F71
python tools/extract_pbzx_cpio.py /path/to/25G70/Payload \
--match 'System/Library/Kernels/kernel$' --output /tmp/kdk-25G70
python tools/diff_macho_symbols.py \
/tmp/kdk-25F71/System/Library/Kernels/kernel \
/tmp/kdk-25G70/System/Library/Kernels/kernel \
--name '^IODMACommand::PerformOperation_Impl' --json