
Proof-of-concept for CVE-2026-64788, a use-after-free in IOGPUFamily kernel extension on iOS 26.6, demonstrating exploitation via Metal texture creation and heap spraying.
Component: IOGPUFamily kernel extension (com.apple.iokit.IOGPUFamily)
Affected: iOS / iPadOS 26.6 (23G71) and earlier
Fixed in: iOS / iPadOS 26.6.1 (23G83)
Type: Use-After-Free in IOGPUDevice::create_resource_iosurface
Impact: Kernel memory corruption; UAF dereference confirmed on A14 (iPhone 12)
Discovered by: f00l (@PPPF00L), 3ndy1 (@_3ndy1), Minghao Lin (@Y1nkoc), 云散花折, Arjanit Isufi
(per Apple Security Advisory — iOS 26.6.1)
IOGPUDevice::create_resource_iosurface allocates an IOGPUSysMemory object (~0x100 bytes, kalloc.256 zone) and registers it in the device's IOSurface resource table. Before returning, a dimension overflow check fires via ADDS width+height → b.hs. The overflow path branches to an error return without releasing the object, leaving a live entry in the resource table that points to freed memory.
create_resource_iosurface:
kalloc(0x100) → obj ; IOGPUSysMemory allocated
table[new_id] = obj ; registered with a reference
ADDS w_result, w_width, w_height
b.hs error_path ; ← overflow: jumps here
error_path:
; object NOT released, table entry NOT cleared
return kIOReturnInvalid
The stale table entry persists until the same resource ID is reused or the device is closed.
IOGPUDevice::set_resource_purgeable(id) looks up the stale ID via get_resource_by_id(), finds the dangling entry, and accesses it non-virtually:
The PoC triggers the UAF using Metal's newTextureWithDescriptor:iosurface:plane: with crafted overflow dimensions. Before the UAF dereference:
kalloc.256 slot[+0x10]=0, [+0x24]=0, [+0x28]=0 → Call2 and Call3 are safely skippedTrigger detection: set_resource_purgeable on a nonexistent resource returns 0xe00002c2. After the UAF + spray reclaims the slot, the same call returns 0xe0002be (resource found, Call1 fired) — confirming the stale entry is being processed with our spray data.
Zone sequestration in iOS 26 places freed IOGPUSysMemory objects back into a type-stable zone free list. Both [obj+0x10] (IOMemoryDescriptor) and [obj+0x18] (IOGPUDevice) hold external retain counts and remain live after the object is freed. As a result, completeMemory() always executes on valid ivar data — the UAF dereference is confirmed but a kernel r/w primitive via this path alone is blocked on iOS 26 without a secondary primitive to break zone sequestration.
clang -arch arm64 -framework Metal -framework IOSurface \
-framework IOKit -framework Foundation \
-o poc poc/poc_iogpu_uaf.m
Or open in Xcode, set a valid signing team, and run on device.
| Date | Event |
|---|---|
| 2026-08-17 | iOS 26.6.1 released with fix |
| 2026-08-17 | Apple credits published in security advisory |
| Offset | Operation | Description |
|---|
+0x010c | ldaddl w9(-4), w8, [obj+0x24] | Atomic decrement of [obj+0x24]; old value → w8 |
+0x0128 | cbz w8, skip; ldr x1, [obj+0x28] | If old [+0x24] != 0: load [obj+0x28] as arg to IOGPUMemory::setAllocation |
+0x0134 | ldr x0, [obj+0x10] | Load [obj+0x10] as IOCommandGate arg; cbz → skip if 0 |