Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-64468 — Непривилегированный proof-of-concept и локальное повышение привилегий для x86_64 при use-after-free в Binder ядра Linux (CVE-2026-64468), с лабораторией KASAN и дифференциальным сравнением уязвимой/исправленной версий. | Kitploit
Инструменты/GitHubGitHub/aramosf/cve-2026-64468
Повышение привилегийФреймворки для эксплойтовАнализ уязвимостейЭксплуатацияОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubaramosf/cve-2026-64468

CVE-2026-64468

Непривилегированный proof-of-concept и локальное повышение привилегий для x86_64 при use-after-free в Binder ядра Linux (CVE-2026-64468), с лабораторией KASAN и дифференциальным сравнением уязвимой/исправленной версий.

Репозиторий
115 дней назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2026-64468 — использование-после-освобождения на протяжении жизни процесса в Binder ядра Linux (binder_free_transaction())

Живой запуск QEMU/KVM: незапатченное уязвимое ядро сообщает об использовании-после-освобождения, исправленное ядро — нет

Этот репозиторий содержит, для использования-после-освобождения в Binder ядра Linux, исправленного коммитом из основной ветки f223d27a546c1e1f48d38fd67760e78f068fe8c4:

  • binder_chain_64468.c — непривилегированное доказательство концепции, которое достигает ошибки и позволяет ядру доказать её, с лабораторией KASAN и дифференциальным тестом уязвимое/исправленное (lab/, run.sh, verify.sh).
  • exploit.c — автономное локальное повышение привилегий для x86_64. Компилируется с помощью gcc -O2 -pthread -o exploit exploit.c, запускается от обычного пользователя и завершается root-оболочкой.
  • demo/ — лаборатория, которая загружает реальное пользовательское окружение Debian 13 на незапатченном ядре, чтобы эксплойт можно было скомпилировать собственным gcc цели и запустить на машине, которой он затем овладевает.

Всё запускается от обычного пользователя (uid/gid 1000, без capabilities, без namespaces) против стандартных ядер из основной ветки без каких-либо патчей.

Предупреждение

Этот код намеренно гоняется за временем жизни объектов ядра, а затем перехватывает поток управления ядра. Проигранная гонка повреждает состояние кучи ядра и может вызвать панику или зависание машины. Запускайте его только в изолированной, одноразовой виртуальной машине, которой вы владеете. Не запускайте его на хосте.

Статус и охват

Уязвимость

binder_free_transaction() считывает целевой процесс из транзакции под t->lock, снимает эту блокировку, а затем захватывает внутреннюю блокировку цели:```c spin_lock(&t->lock); target_proc = t->to_proc; spin_unlock(&t->lock);

root@kitploit:~
if (target_proc) {
	binder_inner_proc_lock(target_proc);   /* use after free */
root@kitploit:~
Nothing keeps `target_proc` alive across that gap. A process that is being torn
down in parallel can reach `binder_proc_dec_tmpref() -> kfree()` in between, so
the lock is taken on freed memory. The upstream fix pins `t->to_thread` while
`t->lock` is still held, which keeps the owning process alive until the inner
lock has been used and released.

The vulnerability was reported by **Alice Ryhl** and fixed by **Carlos
Llamas**, both of Google. The
[original report](https://lore.kernel.org/all/[email protected]/)
carries the reference KASAN trace.

### Reaching the vulnerable access

The only caller that can reach a *foreign* `to_proc` is
`binder_send_failed_reply()`, and it only walks to `t->from_parent` when
`t->from` is `NULL`:```c
	target_thread = binder_get_txn_from_and_acq_inner(t);
	if (target_thread) { ...; binder_free_transaction(t); return; }
	next = t->from_parent;
	binder_free_transaction(t);
	t = next;

from_parent назначается ровно в одном месте, и это присваивание защищено несколькими строками выше проверкой плохого стека транзакций в binder: поток может отправлять синхронную транзакцию только тогда, когда вершина его стека — это транзакция, которую он получает. Следовательно, для каждого звена этой цепочки отправитель дочернего потока и получатель родительского потока — это один и тот же поток.

Это имеет важное следствие. binder_thread_release() обходит стек умирающего потока, удерживая proc->inner_lock, и записывает оба``` iteration j : [holds child->lock ] child->from = NULL ; unlock child->lock spin_lock(parent->lock) iteration j+1 : [holds parent->lock] parent->to_proc = NULL

root@kitploit:~
в **соседних итерациях одного обхода**, каждая под своим
`t->lock`. Обходчик узнаёт, что `child->from == NULL`, только после того, как обход
освободил `child->lock`, и ему нужен `parent->lock` для собственного снимка. Так что вся
возможность — это промежуток между `spin_unlock(&child->lock)` этого обхода и его
`spin_lock(&parent->lock)` — пара инструкций. Освобождающий обход удерживает
спинлок и не может быть вытеснен там; только прерывание может его задержать.

Именно поэтому гонка узкая и почему и proof of concept, и
эксплойт являются вероятностными.

### Что строит proof of concept

`binder_chain_64468.c` строит кратчайшую цепочку, которая достигает
уязвимого доступа, чтобы работа обходчика внутри этого промежутка была как можно меньше, насколько позволяет binder — два захвата `t->lock` и один `kfree()`, без постороннего
`inner_proc_lock`, без `wake_up` и без доставки ответа:```
  B thread i   --e2 (sync, code 0x4442414b)-->  P thread Y_i
  P thread Y_i --e1 (sync, code 0x54414c4c)-->  B thread i   (nested target)

  B thread i   stack: [ e2 outgoing , e1 incoming (top) ]
  P thread Y_i stack: [ e2 incoming , e1 outgoing (top) ]

P затем закрывает свой binder fd, поэтому binder_deferred_release() освобождает Y_1..Y_K и в итоге освобождает binder_proc, в то время как каждый поток B одновременно выполняет BINDER_THREAD_EXIT:``` binder_thread_release(B_i) nulls e1->to_proc (own proc) and e2->from binder_send_failed_reply(e1) e1->from == NULL once Y_i was released -> binder_free_transaction(e1) target_proc already NULL, kfree(e1) -> binder_free_transaction(e2) target_proc == P <-- vulnerable access

root@kitploit:~
Третий процесс — это контекст-менеджер binder, используемый только для раздачи
дескрипторов, которые нужны двум другим. `exploit.c` повторно использует именно эту конструкцию.

### Два независимых условия

Для отчёта KASAN необходимы оба:

* **уязвимый доступ** — обходчик должен захватить `parent->lock` внутри
  узкого окна, описанного выше, чтобы он зафиксировал ещё живой `to_proc`; и
* **приземление освобождения внутри окна** — обходчик должен затем потерять свой CPU
  между снятием `t->lock` и захватом внутренней блокировки жертвы, и оставаться вне его,
  пока отложенное освобождение не завершится и не освободит `binder_proc`.

Первое условие имеет собственный оракул, которому не нужен KASAN: binder выводит```
binder: binder_free_proc: Unexpected outstanding_txns -1

whenever it happens, because the walker and binder_thread_release() then both decrement the same counter for one transaction. Note this is not a vulnerable/fixed differential by itself — the fix stops the free, not the second decrement — so it appears on both kernels. It is used here only to show the vulnerable code path being exercised.

От use-after-free к root

The proof of concept stops at a 4-byte decrement of freed memory. Turning that into uid 0 needs four things, and none of them come from the bug itself: the bug leaks nothing.

1. Общий кэш

struct binder_proc is 648 bytes and is allocated with a plain GFP_KERNEL kzalloc — not __GFP_ACCOUNT. It therefore lands in kmalloc-1k, together with every other unaccounted allocation of that size, and is not isolated behind kmalloc-cg-*. That single fact is what makes the object reclaimable at all.

2. Насос для повторного использования, который не отстаёт

The moment of the kfree() is not observable from userspace, and neither is the moment the walker touches the object again, so there is nothing to time against. The spray therefore runs as a pump: it allocates and frees kmalloc-1k objects continuously, from the CPU that ran the deferred release, for as long as the walkers are running.

System V messages are used for it. alloc_msg() is a plain unaccounted kmalloc of a 48-byte header plus payload, so a 976-byte message is a 1024-byte allocation; the quota is per queue rather than per uid; and msgrcv() frees synchronously. Measured throughput: ~198,000 allocations per second, zero failures.

add_key/user_key_payload was tried first and is a trap. Its payload is charged against a per-uid byte quota (kernel.keys.maxbytes, 20000 by default) which is released only when the key garbage collector destroys the key, so a tight allocate/free loop exhausts it in milliseconds: measured 27,151 successful allocations against 2,121,009 failures — 98.7% of the spray silently doing nothing, which looks exactly like a spray that never wins the slot. KEYCTL_INVALIDATE made it worse (4,775 successes), because it queues GC work.

3. Что должен содержать повторно используемый объект

The walker touches four fields of the freed binder_proc (offsets measured with pahole on the target build):

With outstanding_txns == 1 and is_frozen == 1, the decrement reaches zero and the walker calls wake_up_interruptible_all(&proc->freeze_wait). __wake_up_common then computes curr = head.next - 24 and calls *(head.next - 8): a function pointer read from wherever head.next points, which is a value the reclaimed object supplies.

4. Два адреса из побочного канала

That pointer has to reach memory the attacker controls, at a kernel address, and the bug leaks nothing. Both addresses come from prefetch timing instead — the same channel as KASLD (Brendan Coles, MIT), whose implementation this derives from:

  • Текст ядра. A prefetch of a mapped kernel address resolves in the page table walk and retires measurably faster than one of an unmapped address, even though the access never becomes architecturally visible. Scanning the 2 MiB slots of the text range shows the image as a run of fast slots; its first slot is _text.

  • Прямое отображение. With CONFIG_RANDOMIZE_MEMORY the direct map is randomised in 1 GiB units, so it has to be located too. Unlike the text it covers all of RAM, so it is the longest contiguous run of mapped slots. Two refinements were needed to make this usable:

    • a single prefetch separates mapped from unmapped by only ~4 cycles under KVM, which does not survive the noise, so each sample times a batch of 400 prefetches (measured 311 vs 523 cycles — separable);
    • a single scan is not trustworthy on a contended host — 10/10 correct with one guest at a time, 3/5 with five guests at once — so the scan is run five times and a majority is required. Without consensus the exploit reports failure and stops rather than firing at an unverified address.

    What the run reliably starts at is not page_offset_base itself but the first slot the kernel could map with a 1 GiB page — the one covering physical 4 GiB. Below that, the PCI hole and firmware reservations force 2 MiB pages whose longer walk is not separable from unmapped here. That slot is exactly what the spray needs, so it is what is used.

Then ~60% of physical memory is filled with copies of one crafted 4 KiB page, so a fixed offset from that anchor is backed by the crafted page whatever the layout turns out to be.

Цепочка```

freeze_wait.head.next -> entry1 (in the sprayed page)

entry1.func = mov 0x28(%rdi),%rdi ; mov 0x18(%rdi),%rax ; jmp *0x58(%rax) RDI <- entry1+0x28 = &cred RAX <- cred+0x18 jmp *(RAX+0x58) = commit_creds -> commit_creds(cred)

entry2.func = mov $-1,%rax ; ret __wake_up_common breaks out on ret < 0

root@kitploit:~
Нет переключения стека и нет `iretq`: захваченный поток возвращается из своего `ioctl()`
обычным образом и просто оказывается обратно в userspace, с правами root.

### Подделанный cred и баг, который мог бы его загубить

Диспетчерский гаджет берёт свой `RAX` из `cred+0x18`, а `cred+0x18` — это
`euid`/`egid`, так что сразу после цепочки `euid` — это младшая половина
указателя ядра. Это косметика. Что не косметика, так это то, что `prepare_creds()` —
которую **вызывает каждый последующий `fork()` и `execve()`** — разыменовывает три поля
без проверки на NULL:```c
	get_group_info(new->group_info);        /* refcount_inc(&gi->usage) */
	get_uid(new->user);                     /* refcount_inc(&u->__count) */
	new->ucounts = get_ucounts(new->ucounts);

Подделанные учётные данные, оставленные как NULL, дают uid 0, а затем вызывают панику машины при первом же execve — то есть эксплойт сообщил бы об успехе и немедленно уничтожил бы систему. Поэтому цепочка указывает user, ucounts и group_info на реальные глобальные объекты ядра root_user, init_ucounts и init_groups, адреса которых берутся из той же базы _text. С установленными этими значениями рутированный поток получает CAP_SETUID в init_user_ns, поэтому он вызывает setresuid(0,0,0), и ядро устанавливает чистые, выделенные ядром root-учётные данные поверх подделанных. Только после этого выполняется что-либо ещё.

Привилегии передаются родительскому процессу через setuid-root копию бинарника эксплойта, которая удаляет саму себя перед запуском оболочки через exec, поэтому root-оболочка работает в главном процессе на чистом терминале, и ничего с setuid не остаётся.

Какие системы затронуты

uname -r недостаточно. Вендорные ядра регулярно переносят исправления binder без изменения соответствующей версии mainline; проверяйте исходный код или журнал изменений пакета.

Исправление помечено тегом Cc: stable, поэтому стабильные и вендорные ветки получают бэкпорты.

Где находится ошибка и откуда она доступна

Это разные вопросы, и второй определяет степень воздействия.

Уязвимый код компилируется везде, где установлен CONFIG_ANDROID_BINDER_IPC. Это включает дистрибутивы общего назначения — но на всех проверенных из них драйвер является модулем, который не загружается по умолчанию, и даже при его загрузке init_binder_device() регистрирует misc-устройство без установки miscdev.mode, поэтому devtmpfs создаёт /dev/binder как 0600 root:root. На Android именно ueventd открывает его как 0666, что и объясняет, почему ошибка там имеет значение, а здесь — в основном нет.

Конфигурации, прочитанные из собственных поставляемых пакетов ядра дистрибутивов:

Практическая подверженность на настольном дистрибутиве, таким образом, косвенная: всё, что загружает binder и открывает его — Waydroid, Anbox, эмулятор Android или контейнерная среда выполнения — воспроизводит именно Android-доступность на машине, чьё ядро всё ещё содержит ошибку.

Что каждая опция усиления стоит эксплойту

Они не влияют на уязвимость; они влияют на этот эксплойт.

Подтверждённые цели

Оба ядра — стандартные деревья upstream. Ничего не пропатчено.

CONFIG_KASAN_GENERIC в первой лаборатории — это детектор, а не ускоритель: гонка идентична и без него. CONFIG_PREEMPT — реальное предусловие, и именно его поставляет Android.

Архитектура не является фактором для ошибки — это ошибка времени жизни в архитектурно-независимом C. Она очень важна для эксплойта: гаджеты, канал prefetch и раскладка direct-map — всё это x86_64.

Сборка и запуск

Требования: clang, lld, make, cpio, gzip, qemu-system-x86_64, docker (только для сборки rootfs Debian), локальный клон git-дерева Linux и gcc.

Эксплойт на уже уязвимой системе```sh

gcc -O2 -pthread -o exploit exploit.c ./exploit

root@kitploit:~
Для ядра, отличного от того, что в `demo/`, сначала извлеките его смещения:```sh
./mkoffsets.sh /path/to/vmlinux > offsets.h
gcc -O2 -pthread -DEXPLOIT_OFFSETS='"offsets.h"' -o exploit exploit.c

Настраиваемые параметры, все необязательные, все считываются из окружения: CVE64468_SECONDS, CVE64468_THREADS, CVE64468_SPRAY_PERCENT, CVE64468_CALL_OFFSET_MB, CVE64468_KASLR_ATTEMPTS, CVE64468_DELAY_MAX_US, CVE64468_DELAY_STEP_US, CVE64468_STAGGER_US, CVE64468_VERBOSE, CVE64468_SHELL.

Лаборатория безопасности памяти```sh

LINUX_GIT=/path/to/linux ./lab/build.sh # x86_64 LINUX_GIT=/path/to/linux TARGET_ARCH=arm64 ./lab/build.sh # arm64 ./run.sh vulnerable ./verify.sh 600 16

root@kitploit:~
Скрипт создаёт два отсоединённых рабочих дерева на указанных выше коммитах, отказывается запускаться,
если какое-либо рабочее дерево содержит незакоммиченные изменения, проверяет, что уязвимое дерево не содержит исправления, а
исправленное дерево его содержит, собирает оба ядра и упаковывает proof of concept
в initramfs.

### Лаборатория эксплуатации```sh
./demo/build-kernel.sh     # vulnerable kernel, KASAN off, hardening on
./demo/build-rootfs.sh     # Debian 13 userland + gcc, as an initramfs
./demo/run-demo.sh         # boot it; this is what the recording shows
./demo/verify.sh logs/     # unattended reliability run, one guest per trial

demo/run-demo.sh загружает гостевую систему и передаёт консоль пользователю с uid 1000, который компилирует exploit.c с помощью собственного gcc гостевой системы и запускает его.

Образы ядра, рабочие деревья, деревья корневой файловой системы и initramfs являются лабораторными артефактами и не хранятся в системе контроля версий.

Реальные запуски

Безопасность памяти: уязвимая версия против исправленной

docs/example-output.txt — это реальная запись работы уязвимого ядра, включая отчёт KASAN; docs/patched-negative-output.txt — контрольный запуск исправленного ядра; docs/e2e-results.json — результат в машиночитаемом формате.

Сообщаемая цепочка вызовов точно совпадает с отчётом из апстрима:``` BUG: KASAN: slab-use-after-free in queued_spin_lock_slowpath+0x62/0x6a0 Read of size 4 at addr ffff888100282270 by task cve64468-B/91 CPU: 6 UID: 1000 PID: 91 Comm: cve64468-B Not tainted 7.2.0-rc1+ #2 PREEMPT _raw_spin_lock+0x55/0x60 binder_free_transaction+0x80/0x1f0 binder_send_failed_reply+0x98/0x340 binder_thread_release+0x528/0x5e0 binder_ioctl+0x1ca/0xeb0 __x64_sys_ioctl+0x8c9/0xcf0

Allocated by task 93: binder_open+0xb5/0x7b0

Freed by task 9: kfree+0x113/0x310 binder_deferred_func+0x104b/0x1180 process_scheduled_works+0x69f/0xbb0

root@kitploit:~
`UID: 1000` — это непривилегированная идентичность самого proof-of-concept: жертва
`binder_proc` выделяется через `binder_open()`, освобождается отложенной
workqueue binder, а после освобождения читается обходчиком.

Зафиксированный прогон, 2026-08-16, `./verify.sh 1200 16 3` — три одновременных гостя QEMU/KVM
на вариант, по 10 vCPU на каждого, 16 потоков, 1200 с на вариант, **без патчей ядра
на любой стороне**:

| | Уязвимый `114a116aaa5f` | Исправленный `f223d27a546c` |
| --- | --- | --- |
| Попытки | 158 384 | 158 471 |
| Обходы | 2 534 144 | 2 535 536 |
| Сбои настройки | 0 | 0 |
| Уязвимый доступ (`Unexpected outstanding_txns -1`) | 1 127 | 934 |
| **KASAN `slab-use-after-free`** | **8** | **0** |

Вот и разница: та же нагрузка, то же число попыток с точностью до
0,06%, и use-after-free только на незапатченном уязвимом ядре.

Уязвимый доступ появляется на *обоих* ядрах, и это ожидаемо — исправление
предотвращает освобождение процесса внутри окна, а не второе
уменьшение `outstanding_txns`. Именно поэтому эта строка используется только как дешёвый
оракул и никогда как дифференциальный признак.

### Повышение привилегий

![Живой прогон QEMU/KVM: гость Debian 13, непривилегированный пользователь компилирует exploit.c собственным gcc гостя и завершает работу в приглашении root](https://assets.kitploit.com/production/public/readmes/54229/da1e333d7516671ffab64e10d89604ee2a8043de1c48306abc03aaeee88d0d62/205a1253ab216ae4b62a65f761fffb9f0329bcbf9e4feb38563647f68ad2c5e0-display-v1.webp)

`docs/lpe-output.txt` — это реальная расшифровка демонстрационного гостя, а
`docs/lpe-demo.cast` — полная, неотредактированная запись Asciinema, из которой
была отрендерена анимация выше (`asciinema play docs/lpe-demo.cast` воспроизводит её
целиком). Анимация опускает длинную гоночную середину этой записи —
последовательная консоль гостя транслирует отладку binder на протяжении всей гонки ~24 минут, что
дало бы десятки мегабайт прокручиваемого лога, — сохраняя преамбулу Debian
и оболочку root; строка самого эксплойта `hit after 26661 attempts ... in 1437s`
точно указывает, что было опущено. Оба — одиночные прогоны: гость, который не выигрывает
в пределах своего бюджета, выключается, а запись просто повторяется, а не
редактируется до победы.

См. *Надёжность* ниже для измеренной частоты успеха.

## Надёжность

Гонка вероятностна по обоим пунктам, поэтому неудачный прогон — это ожидаемое
поведение в части случаев, а не сломанный эксплойт.

### Безопасность памяти

Вычисленные частоты на уязвимом ядре из прогона выше:

| Величина | Значение |
| --- | --- |
| Частота попыток | ~44 попыток/с на гостя, ~132/с на трёх |
| Уязвимый доступ | 7,1e-3 на попытку |
| Освобождение, попадающее в окно, при данном доступе | 7,1e-3 |
| Отчёт KASAN | ~1 на 20 000 попыток, т.е. примерно один каждые 2,5 минуты при такой частоте |

### Повышение привилегий

Каждый гость — это независимое испытание: свой KASLR, своя рандомизация
direct-map, и гость, который выигрывает, прекращает гонку. Поэтому цифра — это
**частота успеха на загрузку**, а не на попытку.

Зафиксированная кампания, 2026-08-16 23:32 UTC, `GUESTS=6 CPUS=8 MEMORY_MB=9216 ./demo/verify.sh`
— шесть одновременных гостей QEMU/KVM, незапатченный `114a116aaa5f`, пользовательское окружение Debian 13,
бюджет 60 минут на каждого, эксплойт скомпилирован в госте, запущен как uid 1000:

| | |
| --- | --- |
| Гостей, достигших uid 0 | **2 из 6** |
| Время до root | 623 с и 1 344 с |
| Попыток в выигравшей гонке | 13 839 и 31 125 |
| Попыток каждого гостя, который не выиграл | ~95 000 за полные 3 600 с |
| Всего попыток за кампанию | 427 676 (6,8 млн обходов стека) |
| Наблюдаемых уязвимых доступов (`Unexpected outstanding_txns -1`) | 256 |
| **Сбоев ядра, oops или паник** | **0** за 9 гостевых часов |

Из этой таблицы стоит вынести две вещи.

**Практически каждая выигранная гонка стала root.** Лаборатория KASAN измеряет
вероятность того, что освобождение попадает в окно при данном уязвимом доступе,
как 7,1e-3. Применённая к 256 доступам, наблюдавшимся здесь, это предсказывает ~1,8
use-after-free за кампанию — и было получено 2 root. Перехват, обнаружение
адреса и цепочка — не узкое место; узкое место — сама гонка.

**Ничего не упало.** Ни один гость не получил oops за девять гостевых часов, включая
четыре, которые так и не выиграли. Либо цепочка срабатывает против корректно расположенной, заспреенной
страницы, либо вообще не срабатывает — именно для этого и существует отказ по
большинству голосов на этапе 2.

Этот отказ действительно срабатывает на практике. Загрузка шести гостей одновременно на уже
нагруженном хосте дала одного, который сдался на этапе 2 с```
[*] direct map not found; refusing to fire at an unverified address

и завершился без гонки. Это ожидаемое поведение: потраченная впустую загрузка — это правильный исход, когда канал синхронизации не может достичь консенсуса, и это гораздо лучше, чем альтернатива запуска цепочки по адресу, который так и не был подтверждён.

docs/lpe-results.json содержит машиночитаемую форму, включая SHA-256 точного ядра и initramfs, которые использовались.

Зачем несколько гостевых систем и почему перегруженный хост

Одновременный запуск гостевых систем нужен не только для параллелизма. На загруженном хосте KVM вытесняет гостевые vCPU, и именно эта задержка нужна второму условию: обходчик должен потерять свой CPU между снятием t->lock и захватом внутренней блокировки жертвы. Один гость на простаивающем хосте показал примерно одну двадцатую скорости уязвимого доступа по сравнению с тремя одновременными гостями.

Однако есть верхняя граница. При восьми гостях по 8 vCPU на хосте с 32 потоками и ~5 ГиБ прямого маппинга на каждого гостя хост ушёл в своп, и три из восьми гостей вообще не продвинулись. Шесть — это значение по умолчанию в поставке.

Необязательные вспомогательные потоки вытеснения внутри гостя, по измерениям, стоили примерно в три раза больше скорости попыток без улучшения частоты попаданий, и они не используются.

Один фактор окружающей среды оказался важнее, чем ожидалось: собственный отладочный вывод binder. При binder.debug_mask по умолчанию драйвер генерирует большой объём ограниченного по частоте трафика pr_info во время гонки, и давление, создаваемое printk и блокировкой консоли, удлиняет именно то окно вытеснения, которое нужно второму условию. Отключение его с помощью binder.debug_mask=0 для более чистой консоли — очевидное действие для записи — заметно снизило частоту попаданий в тестах: гости работали далеко за пределами счётчиков попыток обоих победителей без единого попадания. Поэтому demo/run-demo.sh оставляет отладку binder по умолчанию, а тихая консоль — это явное согласие. Это свойство лаборатории, а не эксплойта — но это хорошая иллюстрация того, насколько эта гонка зависит от системного джиттера синхронизации, а не от чего-то, что контролирует сам эксплойт.

Безопасность

  • Обе гостевые системы — одноразовые образы initramfs. Ничего не записывается на диск гостя или хоста.
  • Доказательство концепции только открывает /dev/binder, отправляет транзакции binder и завершает потоки binder. Оно ничего не устанавливает и ничего не оставляет после себя.
  • Эксплойт записывает один файл: копию самого себя с setuid-root, используемую для передачи привилегий от выигравшего потока родительскому процессу. Он удаляет эту копию перед exec шелла, так что ничего setuid не переживает запуск.
  • Проигранная гонка может вызвать панику или зависание гостя; лаборатория проверки безопасности памяти загружается с panic=1 oops=panic, чтобы запуск завершался, а не продолжался с повреждённым состоянием. Всегда перезапускайте с чистой загрузки.

Авторы

  • Автор эксплойта: A. Ramos <[email protected]> (Twitter: @aramosf)
  • Обнаружение уязвимости и отчёт: Alice Ryhl, Google
  • Исправление в апстриме: Carlos Llamas, Google
  • Prefetch KASLR side channel: производное от KASLD, Copyright (c) 2019 Brendan Coles, лицензия MIT. Вариант с прямым маппингом — новая работа здесь.

Поиск публичных эксплойтов

Проверено 2026-08-16 с помощью SearchSploit (локальная копия Exploit-DB) и веб-поиска по CVE-2026-64468, binder_free_transaction и f223d27a546c. Публичных эксплойтов или доказательств концепции для этого CVE не найдено; SearchSploit возвращает только несвязанные, более старые записи Android binder. Это проверка на конкретный момент времени, а не постоянная гарантия.

Скачать инструмент
СостояниеУтверждение
ПодтвержденоУязвимость реальна, достижима из непривилегированного процесса, и исправление из основной ветки устраняет её.
ПродемонстрированоУязвимое разыменование умирающего binder_proc достигается естественным образом и многократно на незапатченном ядре и никогда — на исправленном.
ПродемонстрированоПолное использование-после-освобождения, о котором сообщает KASAN, на незапатченном ядре.
ПродемонстрированоПерехват освобождённого binder_proc байтами, контролируемыми атакующим, перехват потока управления ядра и повышение привилегий до uid 0 от непривилегированного пользователя на x86_64.
Не заявляетсяКакие-либо результаты на конкретном устройстве вендора или Android. Тестировались только перечисленные ниже ядра из основной ветки, на x86_64.
Не заявляетсяЧто поставляемый эксплойт работает без изменений против дистрибутивного ядра. См. Какие системы затронуты: ему нужны смещения для каждого ядра, и на каждом рассмотренном дистрибутиве общего назначения устройство binder в первую очередь недоступно непривилегированному пользователю.
OffsetFieldЧто делает walker
108int outstanding_txnsdecrements it
113bool is_frozenreads it
120wait_queue_head_t freeze_waitwalks it if outstanding_txns == 0 && is_frozen
624spinlock_t inner_locktakes and releases it
СостояниеКоммитПримечания
Исходная линияa370003cc301Указан тегом Fixes: в upstream
Подтверждено уязвимо114a116aaa5fПрямой родитель исправления; содержит соседнее исправление CVE-2026-64469, поэтому пара изолирует только CVE-2026-64468
Исправлено в mainlinef223d27a546cИсправление, проходящее тестирование
ДистрибутивЯдроANDROID_BINDER_IPCУстройствоSLAB_BUCKETSRANDOM_KMALLOC_CACHESДоступно непривилегированно?
Debian 13 (trixie)6.12.101mANDROID_BINDER_DEVICES="binder", BINDERFS выкл.yвыкл.Нет — модуль не загружен; /dev/binder имеет режим 0600
Debian 12 (bookworm)6.1.0mANDROID_BINDER_DEVICES="binder", BINDERFS выкл.н/д (до 6.11)н/д (до 6.6)Нет — то же самое
Ubuntu 24.04 LTS6.8.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""н/д (до 6.11)yНет — нужен root для mount -t binder
Ubuntu 22.04 LTS5.15.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""н/дн/дНет — то же самое
Android (AOSP / вендор)6.1, 6.6, 6.12 GKIy/dev/binder, /dev/hwbinder, /dev/vndbinder——Да — драйвер является платформенным IPC и доступен всем
ОпцияЭффект здесь
CONFIG_SLAB_BUCKETS (6.11+)Фатальна для этого reclaim. Изолирует msg_msg в собственные kmalloc-бакеты, поэтому насос никогда не сможет попасть в слот binder_proc. Debian 13 устанавливает её. Пришлось бы найти другое неучтённое выделение 1 КиБ. Ядро 6.6, на котором работают интересующие устройства Android, вообще предшествует этой опции.
CONFIG_RANDOM_KMALLOC_CACHES (6.6+)Разделяет kmalloc-1k на несколько кэшей по месту вызова, поэтому насосу приходится попадать в тот же самый; налог 1 к 16 на reclaim, а не стена. Ubuntu устанавливает её, Debian — нет.
Изоляция таблиц страниц (nopti не используется)Фатальна для обнаружения адресов. Prefetch не может видеть текст ядра при активном PTI, и эксплойт обнаруживает это и останавливается. PTI скомпилирован во всех дистрибутивах выше, но решение об активности принимает CPU: он выключен на оборудовании, не затронутом Meltdown, где и были измерены эти результаты.
CONFIG_SLAB_FREELIST_RANDOM, ..._HARDENEDВключены в лаборатории, как их поставляют дистрибутивы. Измеримого эффекта нет: насос не предсказывает порядок freelist, он просто выделяет огромное количество объектов.
KASLR (RANDOMIZE_BASE, RANDOMIZE_MEMORY)Включён. Побеждён стадиями prefetch; без nokaslr.
Смещения для каждого ядраЦепочке нужны commit_creds, три связанных с учётными данными глобала и два гаджета как смещения от _text. mkoffsets.sh извлекает их из целевого vmlinux; без них эксплойт срабатывает по неверным адресам. Это свойство любой эксплойта для ядра, зависящее от сборки, а не защита.
Лаборатория безопасности памяти (lab/)Лаборатория эксплуатации (demo/)
Версия ядра7.2.0-rc1+7.2.0-rc1+
Уязвимый коммит114a116aaa5f0295376cdf12da743c5bce3b20ceтот же
Исправленный коммитf223d27a546c1e1f48d38fd67760e78f068fe8c4— (эксплуатация измеряется только на уязвимом ядре)
Архитектураx86_64 (KVM) и arm64 (TCG)x86_64 (KVM)
КомпиляторUbuntu clang 21.1.8 / LLD 21.1.8тот же
KASANвкл. — это детекторвыкл. — он меняет раскладку slab и сделал бы любой reclaim непоказательным
Усиление slab—SLAB_FREELIST_RANDOM, SLAB_FREELIST_HARDENED вкл.; SLAB_BUCKETS, RANDOM_KMALLOC_CACHES выкл.
KASLR—RANDOMIZE_BASE, RANDOMIZE_MEMORY вкл.
Пользовательское окружениеминимальный initramfsDebian GNU/Linux 13 (trixie) с собственным gcc дистрибутива
Начальная идентичностьuid 1000, gid 1000, без capabilities, без namespaceто же
Параметры загрузкиconsole=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1console=ttyS0 loglevel=4 rdinit=/init — без nopti, без nokaslr, без mitigations=off