
Грязная эксплуатация таблицы страниц для CVE-2025-8045
Эксплойт для локального повышения привилегий, использующий CVE-2025-8045 — use-after-free в
драйвере графического ядра Arm Mali GPU (kbase). Непривилегированный локальный процесс гоняется с
путём дампа CSF KCPU queue, чтобы освободить и переиспользовать cpu_queue.buffer, превращает
"висящее" выделение в таблицу страниц, которой он владеет ("dirty pagetable"), и использует этот
примитив для получения произвольного физического чтения/записи в ядре — в итоге получая root-шелл
с SELinux в режиме permissive.
Эксплуатируется на Pixel 7 Pro (сборка
BP3A.250905.014). Жёстко заданные смещения вexploit.cоткалиброваны под эту цель; для других устройств/сборок смещения нужно выводить заново (см. Настройка и переносимость).
CVE-2025-8045 — это use-after-free в драйверах ядра Arm Valhall и GPU 5-го
поколения (r53p0–r54p1). Локальный непривилегированный процесс может
направлять операции обработки GPU таким образом, что драйвер продолжает работать с
уже освобождённой памятью, открывая путь к раскрытию памяти ядра, её повреждению
и выполнению кода.
Этот эксплойт достигает ошибки через интерфейс CSF KCPU queue, предоставляемый
/dev/mali0:
CQS_WAIT, за которым следует
FENCE_SIGNAL. Ожидание останавливает очередь, так что таймер fence срабатывает позже, на
предсказуемой границе bucket'а колеса таймера.KBASE_IOCTL_CS_CPU_QUEUE_DUMP приводит к kbase_csf_cpu_queue_dump_buffer().
Гонка перехода состояния очереди (ISSUED → COMPLETE/PENDING) с путём дампа
освобождает cpu_queue.buffer, пока на него всё ещё есть ссылки, — это и есть UAF.Эксплойт является временны́м: он сначала калибрует дедлайн, затем предсказывает его в каждом раунде и запускает "волну" безвредных вызовов дампа, чтобы расширить окно гонки вокруг момента освобождения.
Поскольку одна страница оказывается одновременно и выделением Mali, и живой CPU page table, процесс паркуется навсегда вместо выхода — освобождение было бы double-free и вызвало бы панику ядра.
# Android / arm64 (NDK)
aarch64-linux-android31-clang -O2 -o exploit exploit.c
./exploit
При успехе вы получаете root-шелл (uid=0) с SELinux permissive. Примерный вывод:
[*] pid=... cpus=8 wave=64 harvest cpu=7 rounds=256
[*] round 1 calibrate deadline t0+... ms phase ... ms
[*] round N read +... us
[+] UAF page: spray region R page P @ ..., 1 live descriptor
[+] entry 0 = ... -- a CPU page table, writable through the GPU
[+] alias: dirty pagetable
[+] selinux_state.enforcing = 0 @ phys ...
[+] sel_read_enforce patched @ phys ...
[+] root shell: uid=0 euid=0
Если все раунды проходят без победы, cpu_queue.c:71 в dmesg указывает, что
гонка срабатывает, но переиспользование не удалось, — настройте параметры переиспользования/волны.
Значения в exploit.c откалиброваны для Pixel 7 Pro, сборки
BP3A.250905.014. Смещения и константы специфичны для устройства/ядра и должны
быть выведены заново для любой другой цели:
KERNEL_PA_BASE, SEL_READ_ENFORCE, INIT_CRED, COMMIT_CREDS,
SELINUX_STATE — смещения символов ядра/физические смещения.ARM64_PTE_FLAGS — шаблон дескриптора последнего уровня для конфигурации MMU цели.WAVE_*, SWEEP_*, BUCKET_NS, WAIT_NS, размеры распыления — параметры
тайминга гонки и подготовки кучи.Для устранения уязвимости обновитесь до исправленной версии драйвера от Arm.
Этот код публикуется строго для оборонительных исследований безопасности, разработки средств обнаружения и обучения. Не запускайте его против систем, которыми вы не владеете или на тестирование которых у вас нет явного письменного разрешения. Автор не несёт ответственности за неправомерное использование или любой причинённый ущерб. Использование его против систем без разрешения незаконно.
| Этап | Что происходит |
|---|
| Калибровка | Раунд 1 измеряет границу ISSUED→COMPLETE, чтобы узнать фазу bucket'а колеса таймера. |
| Предсказание + зачистка | Последующие раунды предсказывают дедлайн и заметают смещение чтения ±мс, чтобы точно попасть в момент освобождения. |
| Волна | 64 потока наваливают вызовы CS_CPU_QUEUE_DUMP на csf.lock, чтобы обрамить дедлайн и растянуть окно. |
| Переиспользование | Предварительно опустошаются списки свободных страниц UNMOVABLE, затем распыляются размеченные страницы GPU, чтобы освобождённый блок вернулся под наш контроль. |
| Dirty pagetable | Повторное освобождение и немедленное распыление CPU таблиц страниц в ту же страницу; выделение GPU теперь алиасит живую таблицу последнего уровня. |
| Произвольное R/W | Никогда не переиспользуемый PTE окна направляется на физические страницы ядра; TLB принудительно сбрасывается через round-trip с PROT_NONE на большом черновом маппинге. |
| Root | Обнуляется selinux_state.enforcing, патчится sel_read_enforce с помощью commit_creds(init_cred), отпускается припаркованный дочерний процесс, который запускает root-шелл. |