
Публичное раскрытие для CVE-2026-43655 AppleM2ScalerCSCDriver use-after-free
Публичное техническое описание CVE-2026-43655, use-after-free в AppleM2ScalerCSCDriver / IOSurfaceAccelerator, доступного из стандартной песочницы приложений iOS без специальных привилегий.
Apple исправила эту проблему в iOS 26.5 / iPadOS 26.5 / macOS Tahoe 26.5. Этот репозиторий содержит исходный код доказательства концепции на Objective-C, минимальные привилегии, собранный IPA и техническое описание, необходимое для понимания и воспроизведения проблемы на уязвимом устройстве.
Ошибка представляет собой ошибку времени жизни/разрушения в планировщике скалера. Пользовательский процесс может отправлять асинхронные операции скалера, закрыть соединение IOSurfaceAcceleratorClient, которому принадлежат эти операции, и оставить устаревшие записи в глобальной для драйвера структуре планировщика. Последующий проход планировщика может обработать записи, которые всё ещё указывают на хранилище операций, которое уже было освобождено и повторно использовано.
PoC демонстрирует ошибку времени жизни с двумя различными значениями маркеров:
0xDEAD00010xBEEF0002Соединение-жертва отправляет асинхронные операции, а затем закрывается. После этого открываются соединения-замены и устанавливают свой маркер в 0xBEEF0002. Когда запускается следующий цикл планирования скалера, сбой наблюдает маркер замены (x9 = 0x00000000BEEF0002), а не маркер жертвы. Это доказывает, что планировщик прочитал из слота операции, который был освобождён, а затем перераспределён другому соединению.
com.apple.driver.AppleM2ScalerCSCDriverIOSurfaceAcceleratorClientget-task-allowAppleM2ScalerCSCDriver хранит состояние планировщика, общее для всех клиентов скалера. Соответствующее несоответствие времени жизни:
IOServiceClose.Путь разрушения не удаляет ожидающие записи планировщика закрывающегося клиента из общей кучи планировщика. Планировщик впоследствии читает и записывает поля через эти устаревшие указатели.
Важные поля, наблюдаемые во время анализа:
| Offset | Поведение планировщика |
|---|---|
operation + 0xc94 | читается как значение кредита/маркера, используемое в пути разрешения кредитов |
operation + 0xc1c | записывается путём учёта кредитов планировщика |
operation + 0x1fe4 | записывается путём обновления состояния/флагов планировщика |
Поведение аллокатора операций делает ошибку наблюдаемой: освобождённые слоты операций могут быть повторно использованы последующими операциями из других соединений. Закрывая соединение-жертву и сразу же распыляя новые соединения, PoC может заставить устаревшие записи планировщика указывать на память, теперь принадлежащую соединениям-заменам.
x9 = 0xBEEF0002 доказывает UAFPoC использует различие жертва/замена:
0xDEAD0001.0xBEEF0002.Если бы планировщик всё ещё читал живые объекты, принадлежащие жертве, наблюдаемое значение было бы 0xDEAD0001. Вместо этого воспроизведённый сбой наблюдает 0xBEEF0002 — значение, записанное соединениями-заменами (спрей). Это ключевое доказательство того, что планировщик разыменовывает устаревший указатель на освобождённую и повторно использованную память ядра.
Это также показывает влияние на несколько соединений: запись планировщика была создана одним соединением, но память, к которой она позже обратилась, была переработана для другого соединения. В воспроизведённых запусках конечная активность планировщика могла быть вызвана обычной активностью SpringBoard/композитора/интерфейса, а не исходным процессом PoC.
Публичное уведомление Apple описывает воздействие как: «Приложение может вызвать неожиданное завершение системы или прочитать память ядра».
Важная деталь воспроизведения: после нажатия TEARDOWN UAF устройство не обязательно паникует немедленно. PoC сначала подготавливает устаревшее состояние планировщика. Ошибка срабатывает в следующем цикле планирования скалера, который на практике происходит, когда активность SpringBoard/композитора управляет скалером. В моём воспроизведении на физическом устройстве я вызвал этот цикл планировщика, нажав/взаимодействовав с Dynamic Island после того, как PoC завершил подготовку устаревшего состояния планировщика.
Шаги:
ScalerTeardownUAF.ipa.AppleM2ScalerCSCDriver.IOSurface источника и назначения.0xDEAD0001 на соединении-жертве.IOServiceClose, освобождая объекты операций, принадлежащие жертве, при этом устаревшие записи планировщика остаются.0xBEEF0002.x9 = 0x00000000BEEF0002.Ожидаемое условие доказательства:
x9 = 0x00000000BEEF0002 означает, что планировщик прочитал маркер замены из памяти, которая изначально принадлежала освобождённой операции жертвы.0xBEEF0002 — это не маркер жертвы; это маркер соединения-замены.Включённый исходный код выполняет следующую последовательность:
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
Соответствующий исходный файл: 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
| Файл | Описание |
|---|---|
ScalerTeardownUAF.m | Исходный код PoC на Objective-C, реализующий последовательность закрытия жертвы и повторного использования через спрей. |
ScalerTeardownUAF.ipa | Собранный артефакт IPA для воспроизведения. |
entitlements.plist | Минимальный файл привилегий, содержащий get-task-allow. |
AppleM2ScalerCSCDriver.0xDEAD0001, замена 0xBEEF0002.