
CVE-2026-43805 IOKit IODMACommand race analysis and proof of concept
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 메모리 상태를 계속
사용할 수 있습니다.
패치 후 세 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은 두 개의 독립된 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
python tools/disassemble_macho_symbol.py \
/tmp/kdk-25G70/System/Library/Kernels/kernel \
PerformOperation_Impl