
Эксплойт ядра Android GKI 6.12 для CVE-2026-43499, объединяющий ошибку отката rt_mutex с перезаписью стека pselect для получения root на устройствах Samsung и Pixel.
Эксплойт ядра Linux для линейки Android GKI 6.12 (устройства Samsung и Pixel),
нацеленный на CVE-2026-43499: ошибочный откат remove_waiter() в
rt_mutex_start_proxy_lock(), который использует current вместо waiter::task,
оставляя pi_blocked_on ожидающего указывающим на его (позже извлечённый из стека)
rt_mutex_waiter в стеке ядра. В сочетании с перезаписью стека fd_set через
pselect() и обходом поддельного rb_tree, управляемым потребителем через
sched_setattr, это даёт детерминированный примитив записи в ядро и полный root.
Эксплойт работает как разделяемая библиотека LD_PRELOAD (preload.so) и
устанавливает root-демон su плюс обои как пост-root артефакты.
Статус: активная разработка. Цель m1q (ZF1) — основной фокус bring-up. pipei tmp_page-uname bootstrap — текущий активный маршрут: обход доказанно чист на устройстве (сборка #33: посеянные rt_mutex-регионы для каждого потомка), и полный перебор 168 кандидатов в процессе. Маршрут configfs CFI — тупик на m1q (Rust ashmem — нет инжектируемого слота fops) и остаётся маршрутом для целей C-ashmem. Смещения для каждой цели различаются по устройству — проверяйте их по фактическому бинарнику ядра, прежде чем доверять им.
Когда FUTEX_CMP_REQUEUE_PI переставляет ожидающего в очередь и обход цепочки
обнаружения взаимоблокировок при перестановке возвращает -EDEADLK,
__rt_mutex_start_proxy_lock() выполняет откат через remove_waiter()
(rtmutex.c:1535). Откат корректно удаляет ожидающего из дерева ожидания, но
очищает pi_blocked_on вызывающего перестановку, а не
waiter->task->pi_blocked_on. pi_blocked_on ОЖИДАЮЩЕГО остаётся висящим на его
стековом rt_mutex_waiter, который извлекается из стека, как только futex
истекает по таймауту.
После таймаута область стека ядра ожидающего переиспользуется: core_sys_select()
копирует три fd_set в этот стековый буфер (путь стека nfds < 344 на ZF1).
Специально сформированный массив слов fd_set воссоздаёт поддельный
rt_mutex_waiter / поддельную задачу / поддельный rt_mutex (с корнем rb_tree,
указатели которого контролируются). Затем поток-потребитель вызывает
sched_setattr_tid(waiter) → rt_mutex_adjust_pi() → rb_erase_cached, что
производит запись контролируемого значения по произвольному адресу.
Весь примитив требует цикла в PI-цепочке: владелец удерживает f_pi_target
и также блокируется на f_pi_chain (удерживаемом ожидающим), поэтому обход
цепочки попадает в owner → chain → waiter → target → owner и завершается
ошибкой -EDEADLK. Удаление цикла заставляет перестановку вернуть успех с
нулевым эффектом в ядре.
sched_blocked_reason (основной на m1q, доказан на устройстве):
сохранённый PC возврата заблокированного kworker (stack_trace_save_tsk)
читается из кольцевого буфера и сравнивается со вкомпилированным смещением
worker_thread. Выполняется до использования любых слов маршрута boot_id,
чтобы цели записи через data-alias (data_addr() = p0 alias +
slide_p0_offset) оставались корректными при ненулевом slide.SLIDE_LOGGERS_0_1 в данные sysctl boot_id через alias линейного
отображения; утёкшее значение восстанавливает stext. Отложен на m1q:
его цель W1 (SLIDE_RANDOM_BOOT_ID_DATA_OFF, младшая физическая RAM) имеет
зависящую от страницы записываемость — slide перемещает цель через границы
страниц при каждой загрузке, поэтому ~6/7 запусков дают сбой. Используется
только явно через SLIDE_FORCE_BOOTID (тест механизма) с перенаправлением
tree_pc/tree_left на заспреенную страницу.mm_struct и использование
коллизии futex-хэша для обнаружения mm_struct в куче, что утекает адрес
страницы кучи ядра, используемый как база спрея поддельных объектов.
Ожидающие отсоединяются (не присоединяются) при очистке, чтобы избежать OOM
стека ядра, который приводил к краху сборок до #26.write_iter, а кучевый объект ASHMEM_SET_NAME — это KVec,
чья раскладка не совпадает с private_data configfs_bin_write_iter. m1q
переключается на запись в kmalloc: возвращает утёкший mm-блок порядка 3 как
объекты pipe_inode_info и использует запись pselect W1 (*(tree_left) = tree_pc) для перезаписи слота tmp_page кандидата (+0x90) страницей
пространства имён UTS (init_uts_ns). Веерная запись помещает маркерное имя
по смещению sysname, и uname() сообщает "CatOS" — проверяемая запись в
ядро без зависимости от статических объектов. Цели C-ashmem сохраняют путь
configfs.preload.c устанавливает встроенный демон su (смонтированный через tmpfs в
/apex/com.android.virt/bin, плюс варианты в пространстве имён adbd и
локальные) и меняет обои.Слова fd_set pselect воссоздают поддельный rt_mutex_waiter в стеке ядра
ожидающего; sched_setattr_tid(waiter) потока-потребителя обходит его через
rt_mutex_adjust_pi(). Карта слов ZF1 (проверено по дизассемблеру):
PSELECT_WAITER_WORD_SHIFT = 0, слово 12 = task (@+0x50), слово 13 = lock
(@+0x58), слово 14 = wake_state (@+0x60 = 3). С посеянным поддельным
rt_mutex обход завершается чисто: rb_erase W1 *(tree_left)=tree_pc / W2
*(tree_pc&~3+8)=tree_left из [7] — единственные записи в ядро; [11] (setprio /
dequeue_pi) и пробуждение [9] оба пропускаются (локальный поддельный ожидающий с
приоритетом 100 удерживает стековый узел вне top-waiter). Каждое завершение
обхода, входившее в него, начиная со сборки #19 выживает на устройстве;
детерминированные крахи до этого были ошибкой размещения 8-байтовой полезной
нагрузки (SKB_DATA_DELTA), а не сбоем тела обхода.
Опциональная фаза STAGE3=1, которая форкает дочерний мост, подменяет его
mm->pgd на подготовленную поддельную таблицу страниц (3 уровня: PGD→L1→L2,
RX-лист цикла + RW-лист стека) и позволяет потомку выполнить ассемблерный блоб
только из регистров, который патчит его собственный cred через окно физического
сканирования 2MB — физическое чтение/запись всей RAM без примитивов
configfs/pipe. В настоящее время подключено только в цель m1q и не
запускалось на устройстве.
41 цель в src/targets/<codename>-<build>/, каждая требует как минимум
target.h (смещения символов ядра, KIMAGE_TEXT_BASE, база прямого отображения,
смещения структур). Цели Pixel (comet, tokay, tegu, caiman, komodo,
frankel, mustang, rango, stallion, blazer) переопределяют общие
исходники; цели Samsung (m1q-*) добавляют специфичную для устройства логику.
make list-projects # полный список
Раскладки структур ядра различаются по устройству — смещения должны быть проверены по фактическому бинарнику ядра / kallsyms для каждой цели.
Выход: build/<PROJECT>/bin/preload.so (разделяемая библиотека, загружаемая
через LD_PRELOAD).
# Проект по умолчанию
CC=clang make
# Конкретная цель устройства (по умолчанию: blazer-CP2A.260605.012)
CC=clang make PROJECT=m1q-BP4A.251205.006
# Без CC=clang: использует NDK, если найден ($NDK_ROOT / $ANDROID_NDK_HOME /
# $ANDROID_NDK_ROOT), иначе host clang + Android sysroot
make PROJECT=m1q-BP4A.251205.006