
Public disclosure for CVE-2026-43655 AppleM2ScalerCSCDriver use-after-free
Public technical disclosure for CVE-2026-43655, an AppleM2ScalerCSCDriver / IOSurfaceAccelerator use-after-free reachable from the default iOS app sandbox with no special entitlements.
Apple addressed this issue in iOS 26.5 / iPadOS 26.5 / macOS Tahoe 26.5. This repository contains the Objective-C proof-of-concept source, minimal entitlements, a built IPA, and the technical write-up needed to understand and reproduce the issue on an affected device.
The bug is a teardown/lifetime error in the scaler scheduler. A user process can submit asynchronous scaler operations, close the IOSurfaceAcceleratorClient connection that owns those operations, and leave stale entries behind in a driver-global scheduler structure. A later scheduler pass can then process entries that still point at operation storage that was already freed and reused.
The PoC demonstrates the lifetime bug with two distinct marker values:
0xDEAD00010xBEEF0002The victim connection submits async operations and is then closed. Replacement connections are opened afterward and set their own marker to 0xBEEF0002. When the next scaler scheduling cycle runs, the fault observes the replacement marker (x9 = 0x00000000BEEF0002), not the victim marker. That proves the scheduler read from an operation slot that had been freed and then reallocated to a different connection.
com.apple.driver.AppleM2ScalerCSCDriverIOSurfaceAcceleratorClientget-task-allow onlyAppleM2ScalerCSCDriver keeps scheduler state shared across scaler clients. The relevant lifetime mismatch is:
IOServiceClose.The teardown path does not remove the closing client's pending scheduler entries from the shared scheduler heap. The scheduler later reads and writes fields through those stale pointers.
Important fields observed during analysis:
| Offset | Scheduler behavior |
|---|---|
operation + 0xc94 | read as the credit/marker value used by the credit-resolution path |
operation + 0xc1c | written by the scheduler credit accounting path |
operation + 0x1fe4 | written by the scheduler state/flag update path |
The operation allocator behavior makes the bug observable: freed operation slots can be reused by later operations from different connections. By closing a victim connection and immediately spraying new connections, the PoC can cause stale scheduler entries to point at memory now owned by the spray connections.
x9 = 0xBEEF0002 proves UAFThe PoC uses a victim/spray distinction:
0xDEAD0001.0xBEEF0002.If the scheduler were still reading live victim-owned objects, the observed value would be 0xDEAD0001. Instead, the reproduced fault observes 0xBEEF0002, the value written by the replacement spray connections. That is the key proof that the scheduler is dereferencing a stale pointer into freed-and-reused kernel memory.
This also shows cross-connection impact: the scheduler entry was created by one connection, but the memory it later touched had been recycled for another connection. In reproduced runs, the final scheduler activity could be driven by normal SpringBoard/compositor/UI activity rather than the original PoC process.
The public Apple advisory describes the impact as: “An app may be able to cause unexpected system termination or read kernel memory.”
Important reproduction detail: after tapping TEARDOWN UAF, the device does not necessarily panic immediately. The PoC first primes stale scheduler state. The bug triggers on the next scaler scheduling cycle, which in practice occurs when SpringBoard/compositor activity drives the scaler. In my physical-device reproduction, I triggered that scheduler cycle by tapping/interacting with the Dynamic Island after the PoC finished priming the stale scheduler state.
Steps:
ScalerTeardownUAF.ipa.AppleM2ScalerCSCDriver connection.IOSurface objects.0xDEAD0001 on the victim connection.IOServiceClose, freeing victim-owned operation objects while stale scheduler entries remain.0xBEEF0002.x9 = 0x00000000BEEF0002.Expected proof condition:
x9 = 0x00000000BEEF0002 means the scheduler read the spray marker from memory that originally belonged to the freed victim operation.0xBEEF0002 is not the victim marker; it is the replacement connection marker.The included source performs the following sequence:
open victim connection
create IOSurface source/destination pair
submit sync baseline scaler request
set victim marker = 0xDEAD0001 through selector 10
submit 50 async scaler operations
close victim connection
open 50 spray connections
set spray marker = 0xBEEF0002 through selector 10
submit repeated async scaler operations on spray connections
wait for SpringBoard/compositor scheduler trigger
The relevant source file is ScalerTeardownUAF.m.
xcrun -sdk iphoneos clang -framework Foundation -framework UIKit -framework IOKit \
-framework IOSurface -isysroot $(xcrun --sdk iphoneos --show-sdk-path) \
-arch arm64 -arch arm64e -miphoneos-version-min=16.0 -fobjc-arc \
-o iPhoneProbe.app/iPhoneProbe ScalerTeardownUAF.m
ldid -S entitlements.plist iPhoneProbe.app/iPhoneProbe
mkdir -p /tmp/pkg/Payload
cp -r iPhoneProbe.app /tmp/pkg/Payload/
cd /tmp/pkg && zip -qr ScalerTeardownUAF.ipa Payload
AppleM2ScalerCSCDriver behavior.0xDEAD0001, spray 0xBEEF0002.| File | Description |
|---|
ScalerTeardownUAF.m | Objective-C PoC source implementing the victim close + spray reuse sequence. |
ScalerTeardownUAF.ipa | Built IPA reproduction artifact. |
entitlements.plist | Minimal entitlement file containing get-task-allow. |