
Адаптер GhostLock (CVE-2026-43499) для Android-ядра 4.19.152-perf+
Эксплойт ядра для устройств OnePlus/OPPO/realme с заблокированным загрузчиком. Позволяет получить root и установить KernelSU без разблокировки загрузчика и без изменения boot-образа. Автоопределение версии ядра во время выполнения с таблицей смещений для нескольких устройств.
CVE-2026-43499 — Use-After-Free в futex PI (Priority Inheritance — наследование приоритета)
Затрагивает ядра Linux 2.6.39 ~ 7.1. Исправлена в mainline 7.1 (коммит 3bfdc63936dd). Android GKI 6.12.x остаётся уязвимым.
Системный вызов pselect6 копирует данные fd_set в стек ядра. В сочетании с механизмом waiter в futex PI освобождённый кадр стека может быть переиспользован как структура rt_mutex_waiter. Перебалансировка rb-дерева при обходе цепочки PI затем записывает контролируемые значения в произвольные адреса ядра.
| Устройство | SoC | Ядро | Статус |
|---|---|---|---|
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14275539 | Работает |
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14552068 | Работает |
| OnePlus 15 (CPH2749) | SM8850 | 6.12.23-...-ab14541642 | Работает |
| Xiaomi 17 (pudding) | SM8850 | 6.12.23-...-abogki463945075 | Работает |
| Xiaomi 17 (pudding) | SM8850 | 6.12.69-...-abogki514973465 | Работает (обновление августа 2026) |
| OnePlus 13 (IN2060) | SM8750 | 6.6.89-...-abogki446052083 | Работает (PSELECT_SHIFT=-2) |
| OPPO Pad 4 Pro | SM8750 | 6.6.89-...-ab14358676 | Работает (PSELECT_SHIFT=-2) |
| Устройство | SoC | Ядро | Примечания |
|---|---|---|---|
| OnePlus 15T (PLZ110) | SM8845 | 6.12.38-...-ab14552068 | То же ядро, что у Ace 6T. В QEMU проверено: SP diff=-64. |
Наложение pselect на стек работает только тогда, когда освобождённый rt_mutex_waiter попадает в контролируемую пользователем область буфера stack_fds. Куда попадёт waiter, определяется результатом компиляции (PGO + LTO), а не версией ядра. Подробнее см. Раскладка стека.
| Устройство | SoC | Ядро | Причина |
|---|---|---|---|
| OPPO Find X9 Ultra | SM8750 | 6.12.58-android16-6 | PGO устраняет кадр do_futex → SP diff=+32, waiter word=14. Безопасного сдвига не существует. |
| OPPO Find X7 | — | 6.1.157 | GKI 6.1: waiter на слове 13 (все устройства 6.1 OPLUS/GKI) |
| realme RMX5070 | SM6650 | 6.1.141 | GKI 6.1: waiter на слове 13 |
| realme RMX3852 | SM8635 | 6.1.141 | Та же ветка 6.1, что и RMX5070 |
| OnePlus 13R / Ace 5 | SM8650 | 6.1.x | Та же ветка 6.1 |
| OnePlus 12 | SM8650 | 6.1.141 | GKI 6.1: do_futex инлайнится PGO, waiter word=13/19 |
| OPPO Pad 5 (OPD2502) | MT6878 | 6.1.134 | Та же ветка 6.1 |
| OPPO PKW110 | — | 5.15.180 | Кадр do_futex 0x140 (в 4,5 раза больше нормы) → waiter word=-29, недостижим |
| Motorola Edge 60 Fusion | MT6878 | 6.1.145 | GKI 6.1: waiter на слове 13 (не OPLUS, результат тот же) |
| iQOO Z9 5G | — | 5.15.178 | Кадр do_futex слишком велик, waiter недостижим. Устройство не OPLUS (vivo). |
Два пути получения root, выбираемые автоматически на основе возможностей устройства:
Требует off_ashmem_misc_fops != 0 (C ashmem со статическим miscdevice в BSS).
PI write (mode=4) → redirect miscdevice fops to fake fops (via W0 pi_tree)
configfs r/w established
→ pipe physrw (1-byte precise kernel r/w)
→ SELinux enforcing = 0 (single byte, no policycap corruption)
→ UMH: inject work_struct into system_unbound_wq
kernel executes /data/local/tmp/a/e --umh as UID 0
→ root script → ksud late-load → KSU installed
Преимущества перед путём B:
selinux_state.policycap (устраняет проблемы с сетью на OnePlus 13)В настоящее время доступен на: OnePlus 13 (ядро 6.6, C ashmem). Недоступен на устройствах с Rust ashmem (6.12 GKI) — miscdevice выделяется в куче, адрес непредсказуем на этапе компиляции.
Используется, когда смещения UMH или C ashmem misc_fops недоступны.
Write 1 (mode=1) → SELinux enforcing = 0
(low byte of kernel ptr = 0x00, 8-byte write)
Write 2 (mode=2) → task->cred = init_cred
(uid=0, all capabilities)
Root shell → ksud late-load (KernelSU LKM)
→ su -c load_policy (fix SELinux policycap)
→ dynamic manager registration
App (seccomp) → Write 1 (no perf needed)
→ mini-adb connect TCP (port from /data/local/tmp/a/adb_port, default 5555)
→ adb shell: full exploit (perf works, no seccomp)
→ root → KSU → network fix
BOOT_COMPLETED → BootCompletedReceiver
├─ su available → skip (soft reboot / already rooted)
└─ no root → GhostlockService → setsid exploit --bootstrap
При NFDS=320 функция ядра core_sys_select выделяет 256-байтовый буфер stack_fds:
stack_fds: 0 5 10 14 | 15 20 25 29
├─in─┤─out─┤─ex──┤ ├res_in┤res_out┤res_ex┤
◄── USER CONTROLLED ──►│◄── KERNEL ZEROED ──►
Эксплойт записывает поддельные поля waiter (task, lock) во входные битовые карты fd_set. Чтобы это сработало, поля task и lock waiter должны попасть в контролируемую зону (слова 0–14).
Ace 6T ✅ (waiter at word 2):
░░████████████████░░│░░░░░░░░░░░░░░░░░░
▲waiter t l │
task/lock controllable
RMX5070 ❌ (waiter at word 13):
░░░░░░░░░░░░░████│██████████████░░░░░░
▲ │ t l
waiter task/lock ZEROED
Правило осуществимости: слово waiter + 11 (смещение lock в rt_waiter_node) должно быть ≤ 14. Максимально допустимое слово waiter — 3.
Положение waiter определяется раскладкой стекового кадра компилятора (профили оптимизации PGO + LTO + BOLT), которая различается для каждой ветки SoC. Одна и та же версия ядра может иметь разную раскладку на разных SoC.
Все записи в память ядра идут через псевдоним линейного отображения образа:
data_addr(x) = PAGE_OFFSET + (kernel_phys_load - PHYS_OFFSET) + (x - KIMAGE_TEXT_BASE)
Загрузчик выбирает kernel_phys_load, поэтому оно различается для каждого SoC и отсутствует в boot.img и в DT. Поле для конкретного устройства в struct kernel_offsets; 0 = использовать значение по умолчанию из target.h.
| SoC | kernel_phys_load |
|---|---|
| SM8845 (Ace 6T, 15T) | 0xa8000000 |
| SM8750 (OnePlus 13, OPPO Pad 4 Pro) | 0xa8000000 |
| SM8850 (OnePlus 15, Xiaomi 17) | 0xc7800000 |
Неверное значение даёт незаметный сбой — запись всё равно попадает в отображённую RAM, поэтому нет ни краха, ни эффекта. Не путайте это с проблемой PSELECT_SHIFT. Считайте значение на рутованном устройстве той же модели (Kernel code начинается с _stext; _text находится на 0x10000 ниже):
su -c 'grep -i "Kernel code" /proc/iomem' # c7810000-... -> 0xc7800000