Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-64468 — Sin privilegios, prueba de concepto y escalada local de privilegios x86_64 para un use-after-free del kernel de Linux en Binder (CVE-2026-64468), con laboratorio KASAN y diferencial vulnerable/corregido. | Kitploit
Herramientas/GitHubGitHub/aramosf/cve-2026-64468
Escalada de PrivilegiosFrameworks de ExploitsAnálisis de VulnerabilidadesExplotaciónAprendizaje y EducaciónExplotación de Binarios
GitHubaramosf/cve-2026-64468

CVE-2026-64468

Sin privilegios, prueba de concepto y escalada local de privilegios x86_64 para un use-after-free del kernel de Linux en Binder (CVE-2026-64468), con laboratorio KASAN y diferencial vulnerable/corregido.

Ver Repositorio
1hace 15 díasAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-64468 — Use-after-free de por vida del proceso en binder_free_transaction() del kernel Linux

Ejecución en vivo con QEMU/KVM: el kernel vulnerable sin parche informa del use-after-free, el kernel parcheado no

Este repositorio contiene, para el use-after-free de Binder del kernel Linux corregido por el commit upstream f223d27a546c1e1f48d38fd67760e78f068fe8c4:

  • binder_chain_64468.c — una prueba de concepto sin privilegios que alcanza el fallo y permite que el kernel lo demuestre, con un laboratorio KASAN y una comparativa vulnerable/corregido (lab/, run.sh, verify.sh).
  • exploit.c — una escalada de privilegios local autónoma para x86_64. Se compila con gcc -O2 -pthread -o exploit exploit.c, se ejecuta como un usuario normal y termina en una shell de root.
  • demo/ — un laboratorio que arranca un userland real de Debian 13 en un kernel sin parche, de modo que el exploit pueda compilarse con el propio gcc del objetivo y ejecutarse en la máquina que luego toma el control.

Todo se ejecuta como un usuario normal (uid/gid 1000, sin capacidades, sin namespaces) contra kernels upstream estándar sin parches de ningún tipo.

Advertencia

Este código deliberadamente compite por las vidas útiles de los objetos del kernel y luego secuestra el flujo de control del kernel. Una carrera perdida corrompe el estado del heap del kernel y puede provocar un pánico o un cuelgue de la máquina. Ejecútalo solo en una VM aislada y desechable que sea tuya. No lo ejecutes en un host.

Estado y alcance

Vulnerabilidad

binder_free_transaction() lee el proceso objetivo de la transacción bajo t->lock, libera ese bloqueo y luego adquiere el bloqueo interno del objetivo:```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.

### Alcanzando el acceso vulnerable

El único llamador que puede alcanzar un `to_proc` *externo* es
`binder_send_failed_reply()`, y solo recorre hasta `t->from_parent` cuando
`t->from` es `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 se asigna exactamente en un solo lugar, y la asignación está protegida unas líneas antes por la comprobación de pila de transacciones incorrecta de binder: un hilo solo puede enviar una transacción síncrona mientras la parte superior de su pila sea una transacción que está recibiendo. Por lo tanto, para cada eslabón de esa cadena, el remitente del hijo y el receptor del padre son el mismo hilo.

Eso tiene una consecuencia contundente. binder_thread_release() recorre la pila del hilo moribundo con proc->inner_lock retenido y escribe ambos``` 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:~
en **iteraciones adyacentes de un único recorrido**, cada una bajo su
respectivo `t->lock`. Un caminante solo descubre que `child->from == NULL` una vez que el recorrido ha liberado
`child->lock`, y necesita `parent->lock` para su propia instantánea. Así que toda la
oportunidad es el hueco entre el `spin_unlock(&child->lock)` de ese recorrido y su
`spin_lock(&parent->lock)` — un par de instrucciones. El recorrido de liberación mantiene un
spinlock y no puede ser adelantado allí; solo una interrupción puede retrasarlo.

Por eso la condición de carrera es estrecha y por qué tanto la prueba de concepto como el
exploit son probabilísticos.

### Qué construye la prueba de concepto

`binder_chain_64468.c` construye la cadena más corta que alcanza el
acceso vulnerable, de modo que el trabajo del caminante dentro de ese hueco sea lo más pequeño que binder
permite — dos adquisiciones de `t->lock` y un `kfree()`, sin `inner_proc_lock` externo,
sin `wake_up` y sin entrega de respuesta:```
  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 then drops its binder fd, so binder_deferred_release() releases Y_1..Y_K and finally frees the binder_proc, while every B thread concurrently issues 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:~
Un tercer proceso es el gestor de contexto de binder, usado únicamente para repartir los
handles que los otros dos necesitan. `exploit.c` reutiliza exactamente esta construcción.

### Dos condiciones independientes

Un informe de KASAN necesita ambas:

* **el acceso vulnerable** — el walker debe tomar `parent->lock` dentro de la
  estrecha ventana anterior, de modo que capture una instantánea de un `to_proc` aún vivo; y
* **el aterrizaje del free dentro de la ventana** — el walker debe entonces perder su CPU
  entre soltar `t->lock` y tomar el lock interno de la víctima, y permanecer fuera de ella
  hasta que la liberación diferida haya terminado y haya liberado el `binder_proc`.

La primera condición tiene su propio oráculo que no necesita KASAN: binder imprime```
binder: binder_free_proc: Unexpected outstanding_txns -1

cuando ocurre, porque el walker y binder_thread_release() entonces ambos decrementan el mismo contador para una transacción. Ten en cuenta que esto no es una diferencia vulnerable/corregida por sí misma — el fix detiene el free, no el segundo decremento — por lo que aparece en ambos kernels. Se usa aquí solo para mostrar la ruta de código vulnerable siendo ejercitada.

De use-after-free a root

La prueba de concepto se detiene en un decremento de 4 bytes de memoria liberada. Convertir eso en uid 0 necesita cuatro cosas, y ninguna de ellas proviene del bug en sí: el bug no filtra nada.

1. Una caché compartida

struct binder_proc tiene 648 bytes y se asigna con un GFP_KERNEL kzalloc simple — no __GFP_ACCOUNT. Por lo tanto, aterriza en kmalloc-1k, junto con cualquier otra asignación no contabilizada de ese tamaño, y no está aislada detrás de kmalloc-cg-*. Ese único hecho es lo que hace que el objeto sea reclamable en absoluto.

2. Una bomba de reclamación que mantiene el ritmo

El momento del kfree() no es observable desde userspace, y tampoco lo es el momento en que el walker toca el objeto de nuevo, por lo que no hay nada contra lo que sincronizarse. El spray, por tanto, funciona como una bomba: asigna y libera objetos kmalloc-1k continuamente, desde la CPU que ejecutó el release diferido, durante el tiempo que los walkers estén en ejecución.

Se usan mensajes System V para ello. alloc_msg() es un kmalloc no contabilizado simple de un encabezado de 48 bytes más el payload, por lo que un mensaje de 976 bytes es una asignación de 1024 bytes; la cuota es por cola en lugar de por uid; y msgrcv() libera de forma síncrona. Rendimiento medido: ~198,000 asignaciones por segundo, cero fallos.

Se probó primero add_key/user_key_payload y es una trampa. Su payload se carga contra una cuota de bytes por uid (kernel.keys.maxbytes, 20000 por defecto) que solo se libera cuando el recolector de basura de claves destruye la clave, por lo que un bucle ajustado de asignar/liberar lo agota en milisegundos: medido 27,151 asignaciones exitosas frente a 2,121,009 fallos — el 98.7% del spray haciendo silenciosamente nada, lo que parece exactamente un spray que nunca gana el slot. KEYCTL_INVALIDATE lo empeoró (4,775 éxitos), porque pone en cola trabajo de GC.

3. Lo que el objeto reclamado tiene que contener

El walker toca cuatro campos del binder_proc liberado (offsets medidos con pahole en la build objetivo):

Con outstanding_txns == 1 e is_frozen == 1, el decremento llega a cero y el walker llama a wake_up_interruptible_all(&proc->freeze_wait). __wake_up_common entonces calcula curr = head.next - 24 y llama a *(head.next - 8): un puntero a función leído desde donde apunte head.next, que es un valor que el objeto reclamado proporciona.

4. Dos direcciones, desde un canal lateral

Ese puntero tiene que llegar a memoria que el atacante controla, en una dirección de kernel, y el bug no filtra nada. Ambas direcciones provienen en su lugar del timing de prefetch — el mismo canal que KASLD (Brendan Coles, MIT), cuya implementación deriva de esto:

  • Texto del kernel. Un prefetch de una dirección de kernel mapeada se resuelve en el recorrido de la tabla de páginas y se retira de forma mediblemente más rápida que uno de una dirección no mapeada, incluso aunque el acceso nunca se vuelva arquitectónicamente visible. Escanear los slots de 2 MiB del rango de texto muestra la imagen como una secuencia de slots rápidos; su primer slot es _text.

  • El mapa directo. Con CONFIG_RANDOMIZE_MEMORY el mapa directo está aleatorizado en unidades de 1 GiB, por lo que también hay que localizarlo. A diferencia del texto, cubre toda la RAM, por lo que es la secuencia contigua más larga de slots mapeados. Se necesitaron dos refinamientos para hacerlo utilizable:

    • un solo prefetch separa lo mapeado de lo no mapeado por solo ~4 ciclos bajo KVM, lo que no sobrevive al ruido, por lo que cada muestra mide un lote de 400 prefetches (medido 311 vs 523 ciclos — separable);
    • un solo escaneo no es fiable en un host con contención — 10/10 correctos con un guest a la vez, 3/5 con cinco guests a la vez — por lo que el escaneo se ejecuta cinco veces y se requiere una mayoría. Sin consenso, el exploit informa del fallo y se detiene en lugar de disparar contra una dirección no verificada.

    Lo que la ejecución comienza de forma fiable no es page_offset_base en sí, sino el primer slot que el kernel podría mapear con una página de 1 GiB — el que cubre los 4 GiB físicos. Por debajo de eso, el agujero PCI y las reservas de firmware fuerzan páginas de 2 MiB cuyo recorrido más largo no es separable de lo no mapeado aquí. Ese slot es exactamente lo que el spray necesita, por lo que es lo que se usa.

Entonces ~60% de la memoria física se llena con copias de una página de 4 KiB manipulada, por lo que un offset fijo desde ese ancla está respaldado por la página manipulada sea cual sea el layout que resulte.

La cadena```

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:~
No stack pivot and no `iretq`: the hijacked thread returns out of its `ioctl()`
normally and is simply back in userspace, as root.

### The forged cred, and the bug that would have wasted it

The dispatcher gadget takes its `RAX` from `cred+0x18`, and `cred+0x18` is
`euid`/`egid`, so immediately after the chain `euid` is the low half of a kernel
pointer. That is cosmetic. What is not cosmetic is that `prepare_creds()` —
which **every later `fork()` and `execve()` calls** — dereferences three fields
with no NULL check:```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);

Una credencial falsificada que deja user, ucounts y group_info en NULL otorga uid 0 y luego provoca un pánico en la máquina en el primer execve — es decir, el exploit reportaría éxito e inmediatamente destruiría el sistema. Por lo tanto, la cadena apunta user, ucounts y group_info a los globales reales del kernel root_user, init_ucounts e init_groups, cuyas direcciones provienen de la misma base _text. Con esos establecidos, el hilo con privilegios elevados posee CAP_SETUID sobre init_user_ns, por lo que llama a setresuid(0,0,0) y el kernel instala una credencial root limpia y asignada por el kernel sobre la falsificada. Solo entonces se realiza cualquier otra acción.

El privilegio se transfiere al proceso padre mediante una copia del binario del exploit con setuid-root, que se desvincula a sí mismo antes de ejecutar el shell, de modo que el shell root se ejecuta en el proceso principal en una terminal limpia y no queda nada con setuid atrás.

Qué sistemas están afectados

uname -r no es suficiente. Los kernels de los proveedores suelen aplicar backports de correcciones de binder sin cambiar a la versión mainline correspondiente; inspecciona el código fuente o el changelog del paquete.

La corrección está etiquetada como Cc: stable, por lo que las ramas stable y de proveedores reciben backports.

Dónde está el bug y dónde es alcanzable

Son preguntas diferentes, y la segunda es la que determina el impacto.

El código vulnerable se compila dondequiera que CONFIG_ANDROID_BINDER_IPC esté establecido. Eso incluye las distribuciones de propósito general — pero en todas las encuestadas, el driver es un módulo que no se carga por defecto, e incluso cuando se carga, init_binder_device() registra el dispositivo misc sin establecer miscdev.mode, por lo que devtmpfs crea /dev/binder como 0600 root:root. En Android es ueventd quien lo abre a 0666, que es exactamente por qué el bug importa allí y en su mayoría no aquí.

Configuraciones leídas de los paquetes de kernel propios de las distribuciones:

La exposición práctica en una distribución de escritorio es, por tanto, indirecta: cualquier cosa que cargue binder y lo abra — Waydroid, Anbox, un emulador de Android o un runtime de contenedores — reintroduce exactamente la alcanzabilidad de Android en una máquina cuyo kernel aún tiene el bug.

Qué le cuesta cada opción de endurecimiento al exploit

Estas no afectan a la vulnerabilidad; afectan a este exploit.

Objetivos validados

Ambos kernels son árboles upstream estándar. Nada está parcheado.

CONFIG_KASAN_GENERIC en el primer laboratorio es un detector, no un habilitador: la condición de carrera es idéntica sin él. CONFIG_PREEMPT es un requisito previo real, y es lo que Android incluye.

La arquitectura no es un factor para el bug — es un error de ciclo de vida en C independiente de la arquitectura. Es un factor muy importante para el exploit: los gadgets, el canal de prefetch y el diseño del mapa directo son todos x86_64.

Compilación y ejecución

Requisitos: clang, lld, make, cpio, gzip, qemu-system-x86_64, docker (solo para ensamblar el rootfs de Debian), un clon local del árbol git de Linux y gcc.

El exploit, en un sistema que ya es vulnerable```sh

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

root@kitploit:~
Para un kernel distinto al de `demo/`, extrae primero sus offsets:```sh
./mkoffsets.sh /path/to/vmlinux > offsets.h
gcc -O2 -pthread -DEXPLOIT_OFFSETS='"offsets.h"' -o exploit exploit.c

Tunables, todos opcionales, todos leídos del entorno: 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.

El laboratorio de seguridad de memoria```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:~
El script crea dos worktrees separados en los commits anteriores, se niega a ejecutarse
si cualquiera de los worktrees está sucio, verifica que el árbol vulnerable carece del fix y
que el árbol corregido lo contiene, compila ambos kernels y empaqueta la prueba de concepto
en un initramfs.

### El laboratorio de explotación```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 arranca la máquina invitada y entrega la consola al uid 1000, que compila exploit.c con el propio gcc de la máquina invitada y lo ejecuta.

Las imágenes del kernel, los árboles de trabajo, los árboles del sistema de archivos raíz y los initramfs son artefactos de laboratorio y no están versionados.

Ejecuciones reales

Seguridad de memoria, vulnerable frente a corregido

docs/example-output.txt es la transcripción real del kernel vulnerable, incluido el informe KASAN; docs/patched-negative-output.txt es el control del kernel corregido; docs/e2e-results.json es el resultado legible por máquina.

La cadena de llamadas reportada coincide exactamente con el informe ascendente:``` 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` es la identidad no privilegiada del propio proof of concept: la víctima
`binder_proc` se asigna con `binder_open()`, se libera mediante la workqueue
diferida de binder, y el walker la lee después de la liberación.

Ejecución registrada, 2026-08-16, `./verify.sh 1200 16 3` — tres invitados QEMU/KVM
concurrentes por variante, 10 vCPUs cada uno, 16 hilos, 1200 s por variante, **sin
parches de kernel en ninguno de los dos lados**:

| | Vulnerable `114a116aaa5f` | Corregido `f223d27a546c` |
| --- | --- | --- |
| Intentos | 158,384 | 158,471 |
| Walkers | 2,534,144 | 2,535,536 |
| Fallos de configuración | 0 | 0 |
| Acceso vulnerable (`Unexpected outstanding_txns -1`) | 1,127 | 934 |
| **KASAN `slab-use-after-free`** | **8** | **0** |

Ese es el diferencial: la misma carga de trabajo, el mismo número de intentos con
una diferencia del 0.06%, y el use-after-free solo en el kernel vulnerable sin
parchear.

El acceso vulnerable aparece en *ambos* kernels, y eso es esperado — el parche
evita que el proceso se libere dentro de la ventana, no el segundo decremento de
`outstanding_txns`. Por eso esa línea se usa solo como un oráculo barato y nunca
como el diferencial.

### Escalada de privilegios

![Ejecución QEMU/KVM en vivo: invitado Debian 13, usuario no privilegiado compila exploit.c con el gcc del propio invitado y termina en un prompt de root](https://assets.kitploit.com/production/public/readmes/54229/da1e333d7516671ffab64e10d89604ee2a8043de1c48306abc03aaeee88d0d62/205a1253ab216ae4b62a65f761fffb9f0329bcbf9e4feb38563647f68ad2c5e0-display-v1.webp)

`docs/lpe-output.txt` es una transcripción real de un invitado de demostración, y
`docs/lpe-demo.cast` es la grabación Asciinema completa y sin editar a partir de la
cual se renderizó la animación anterior (`asciinema play docs/lpe-demo.cast` la
reproduce en su totalidad). La animación omite la larga parte central de la carrera
de esa grabación — la consola serie del invitado transmite el debug de binder
durante toda la carrera de ~24 minutos, lo que se renderizaría en decenas de
megabytes de registro desplazándose — conservando el preámbulo de Debian y el shell
de root; la propia línea del exploit `hit after 26661 attempts ... in 1437s`
indica exactamente lo que se omitió. Ambas son ejecuciones únicas: un invitado que
no gana dentro de su presupuesto se apaga, y la grabación simplemente se repite en
lugar de editarse para forzar una victoria.

Consulta *Fiabilidad* más abajo para conocer la tasa de éxito medida.

## Fiabilidad

La carrera es probabilística en ambos aspectos, por lo que una ejecución fallida es
el comportamiento esperado algunas veces, no un exploit roto.

### Seguridad de memoria

Rates derivadas en el kernel vulnerable, a partir de la ejecución anterior:

| Cantidad | Valor |
| --- | --- |
| Tasa de intentos | ~44 intentos/s por invitado, ~132/s entre tres |
| Acceso vulnerable | 7.1e-3 por intento |
| Liberación que aterriza dentro de la ventana, dado el acceso | 7.1e-3 |
| Informe KASAN | ~1 por cada 20,000 intentos, es decir, aproximadamente uno cada 2.5 minutos a esta tasa |

### Escalada de privilegios

Cada invitado es un ensayo independiente: su propio KASLR, su propia aleatorización
del direct-map, y un invitado que gana deja de competir. La cifra es, por tanto, una
**tasa de éxito por arranque**, no por intento.

Campaña registrada, 2026-08-16 23:32 UTC, `GUESTS=6 CPUS=8 MEMORY_MB=9216 ./demo/verify.sh`
— seis invitados QEMU/KVM concurrentes, `114a116aaa5f` sin parchear, userland Debian 13,
presupuesto de 60 minutos cada uno, exploit compilado dentro del invitado, iniciado como uid 1000:

| | |
| --- | --- |
| Invitados que alcanzaron uid 0 | **2 de 6** |
| Tiempo hasta root | 623 s y 1,344 s |
| Intentos en la carrera ganadora | 13,839 y 31,125 |
| Intentos de cada invitado que no ganó | ~95,000 durante los 3,600 s completos |
| Intentos totales en la campaña | 427,676 (6.8 M walkers de pila) |
| Accesos vulnerables observados (`Unexpected outstanding_txns -1`) | 256 |
| **Bloqueos, oopses o pánicos del kernel** | **0**, en 9 horas-invitado |

Hay dos cosas que merece la pena destacar de esa tabla.

**Prácticamente toda carrera ganada se convirtió en root.** El laboratorio KASAN mide
la probabilidad de que la liberación aterrice dentro de la ventana, dado el acceso
vulnerable, en 7.1e-3. Aplicado a los 256 accesos observados aquí, eso predice ~1.8
use-after-frees en toda la campaña — y se obtuvieron 2 roots. El reclaim, el
descubrimiento de direcciones y la cadena no son el cuello de botella; la carrera lo es.

**Nada se bloqueó.** Ningún invitado sufrió un oops en nueve horas-invitado, incluidos
los cuatro que nunca ganaron. O la cadena se dispara contra una página rociada y
correctamente ubicada, o nunca se dispara en absoluto — que es para lo que sirve el
rechazo por mayoría en la etapa 2.

Ese rechazo sí se activa en la práctica. Arrancar seis invitados a la vez en un host ya
cargado produjo uno que se rindió en la etapa 2 con```
[*] direct map not found; refusing to fire at an unverified address

y salió sin competir. Ese es el comportamiento previsto: un arranque desperdiciado es el resultado correcto cuando el canal de sincronización no puede alcanzar consenso, y es mucho mejor que la alternativa de disparar la cadena a una dirección que nunca fue confirmada.

docs/lpe-results.json contiene la forma legible por máquina, incluido el SHA-256 del kernel e initramfs exactos utilizados.

Por qué varias máquinas invitadas y por qué un host sobresuscrito

Ejecutar las máquinas invitadas de forma concurrente no es solo cuestión de paralelismo. En un host con contención, KVM desprograma las vCPU invitadas, y ese es exactamente el retraso que necesita la segunda condición: el caminante tiene que perder su CPU entre soltar t->lock y tomar el bloqueo interno de la víctima. Una sola máquina invitada en un host inactivo se midió en aproximadamente una vigésima parte de la tasa de acceso vulnerable de tres máquinas invitadas concurrentes.

Sin embargo, hay un límite superior. Con ocho máquinas invitadas de 8 vCPU cada una en un host de 32 hilos, con ~5 GiB de spray de mapa directo por máquina invitada, el host entró en swap y tres de las ocho máquinas invitadas no progresaron en absoluto. Seis es el valor predeterminado incluido.

Los hilos auxiliares opcionales de preferencia dentro de la máquina invitada se midieron con un costo de aproximadamente tres veces la tasa de intentos sin mejorar la tasa de aciertos, y no se utilizan.

Un factor ambiental resultó importar más de lo esperado: la salida de depuración propia de binder. Con binder.debug_mask en su valor predeterminado, el controlador emite una gran cantidad de tráfico pr_info limitado por tasa durante la carrera, y la presión de printk y del bloqueo de consola que eso crea alarga exactamente la ventana de preferencia que necesita la segunda condición. Silenciarlo con binder.debug_mask=0 para una consola más limpia — lo obvio para una grabación — redujo de forma medible la tasa de aciertos en las pruebas: las máquinas invitadas superaron con creces los recuentos de intentos de ambos ganadores sin un acierto. Por lo tanto, demo/run-demo.sh deja la depuración de binder en su valor predeterminado, y una consola silenciosa es una opción explícita. Esta es una propiedad del laboratorio, no del exploit — pero es una buena ilustración de cuánto depende esta carrera de la fluctuación de sincronización a nivel de sistema en lugar de cualquier cosa que el propio exploit controle.

Seguridad

  • Ambas máquinas invitadas son imágenes initramfs desechables. No se escribe nada en el disco de la máquina invitada ni del host.
  • La prueba de concepto solo abre /dev/binder, envía transacciones de binder y sale de los hilos de binder. No instala nada y no deja nada atrás.
  • El exploit sí escribe un archivo: una copia de sí mismo con setuid de root, utilizada para transferir privilegios del hilo ganador al proceso padre. Elimina esa copia antes de ejecutar el shell, por lo que nada con setuid sobrevive a la ejecución.
  • Una carrera perdida puede provocar pánico o bloqueo de la máquina invitada; el laboratorio de seguridad de memoria arranca con panic=1 oops=panic para que una ejecución termine en lugar de continuar con un estado corrupto. Siempre reinicie desde un arranque limpio.

Créditos

  • Autor del exploit: A. Ramos <[email protected]> (Twitter: @aramosf)
  • Descubrimiento e informe de la vulnerabilidad: Alice Ryhl, Google
  • Corrección ascendente: Carlos Llamas, Google
  • Canal lateral de KASLR con prefetch: derivado de KASLD, Copyright (c) 2019 Brendan Coles, con licencia MIT. La variante de mapa directo es trabajo nuevo aquí.

Búsqueda de exploits públicos

Verificado el 2026-08-16 con SearchSploit (copia local de Exploit-DB) y búsqueda web de CVE-2026-64468, binder_free_transaction y f223d27a546c. No se encontró ningún exploit público ni prueba de concepto para este CVE; SearchSploit solo devuelve entradas de binder de Android más antiguas y no relacionadas. Esta es una verificación puntual, no una garantía permanente.

Descargar herramienta
EstadoAfirmación
ConfirmadoLa vulnerabilidad es real, es alcanzable desde un proceso sin privilegios y el parche upstream la elimina.
DemostradoLa desreferencia vulnerable de un binder_proc moribundo se alcanza de forma natural y repetida en el kernel sin parche, y nunca en el kernel parcheado.
DemostradoEl use-after-free completo, notificado por KASAN, en el kernel sin parche.
DemostradoLa recuperación del binder_proc liberado con bytes controlados por el atacante, el secuestro del flujo de control del kernel y la escalada de privilegios a uid 0 desde un usuario sin privilegios, en x86_64.
No afirmadoCualquier resultado en un proveedor o dispositivo Android específico. Solo se probaron los kernels upstream enumerados a continuación, en x86_64.
No afirmadoQue el exploit incluido funcione sin modificaciones contra un kernel de distribución. Consulta Qué sistemas están afectados: necesita offsets específicos por kernel y, en todas las distribuciones de propósito general analizadas, el dispositivo binder no es alcanzable por un usuario sin privilegios en primer lugar.
OffsetCampoLo que hace el walker
108int outstanding_txnslo decrementa
113bool is_frozenlo lee
120wait_queue_head_t freeze_waitlo recorre si outstanding_txns == 0 && is_frozen
624spinlock_t inner_locklo toma y lo libera
EstadoCommitNotas
Linaje introducidoa370003cc301Nombrado por la etiqueta Fixes: del upstream
Vulnerable validado114a116aaa5fPadre directo de la corrección; incluye la corrección vecina de CVE-2026-64469, por lo que el par aísla CVE-2026-64468 por sí solo
Mainline corregidof223d27a546cLa corrección bajo prueba
DistribuciónKernelANDROID_BINDER_IPCDispositivoSLAB_BUCKETSRANDOM_KMALLOC_CACHES¿Alcanzable sin privilegios?
Debian 13 (trixie)6.12.101mANDROID_BINDER_DEVICES="binder", BINDERFS desactivadoydesactivadoNo — módulo no cargado; /dev/binder es 0600
Debian 12 (bookworm)6.1.0mANDROID_BINDER_DEVICES="binder", BINDERFS desactivadon/a (pre-6.11)n/a (pre-6.6)No — igual
Ubuntu 24.04 LTS6.8.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""n/a (pre-6.11)yNo — requiere root para mount -t binder
Ubuntu 22.04 LTS5.15.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""n/an/aNo — igual
Android (AOSP / proveedor)6.1, 6.6, 6.12 GKIy/dev/binder, /dev/hwbinder, /dev/vndbinder——Sí — el driver es el IPC de la plataforma y es accesible mundialmente
OpciónEfecto aquí
CONFIG_SLAB_BUCKETS (6.11+)Fatal para este reclaim. Aísla msg_msg en sus propios buckets de kmalloc, por lo que la bomba nunca puede aterrizar en el slot de binder_proc. Debian 13 lo establece. Habría que encontrar otra asignación no contabilizada de 1 KiB. El kernel 6.6, que es el que ejecutan los dispositivos Android de interés, lo precede por completo.
CONFIG_RANDOM_KMALLOC_CACHES (6.6+)Divide kmalloc-1k en varios caches por sitio de llamada, por lo que la bomba tiene que acertar en el mismo; un impuesto de 1 entre 16 sobre el reclaim, no un muro. Ubuntu lo establece, Debian no.
Aislamiento de tablas de páginas (nopti no usado)Fatal para el descubrimiento de direcciones. El prefetch no puede ver el texto del kernel con PTI activo, y el exploit detecta esto y se detiene. PTI está compilado en todas las distribuciones anteriores, pero la CPU decide si está activo: está desactivado en hardware no afectado por Meltdown, que es donde se midieron estos resultados.
CONFIG_SLAB_FREELIST_RANDOM, ..._HARDENEDHabilitados en el laboratorio, como los envían las distribuciones. Sin efecto medible: la bomba no predice el orden de la freelist, simplemente asigna una gran cantidad de objetos.
KASLR (RANDOMIZE_BASE, RANDOMIZE_MEMORY)Habilitado. Derrotado por las etapas de prefetch; sin nokaslr.
Desplazamientos por kernelLa cadena necesita commit_creds, tres globales relacionados con cred y dos gadgets, como desplazamientos desde _text. mkoffsets.sh los extrae de un vmlinux objetivo; sin ellos, el exploit dispara a direcciones incorrectas. Esto es una propiedad por compilación de cualquier exploit de kernel, no una defensa.
Laboratorio de seguridad de memoria (lab/)Laboratorio de explotación (demo/)
Versión del kernel7.2.0-rc1+7.2.0-rc1+
Commit vulnerable114a116aaa5f0295376cdf12da743c5bce3b20cemismo
Commit corregidof223d27a546c1e1f48d38fd67760e78f068fe8c4— (la explotación se mide solo en el kernel vulnerable)
Arquitecturax86_64 (KVM) y arm64 (TCG)x86_64 (KVM)
CompiladorUbuntu clang 21.1.8 / LLD 21.1.8mismo
KASANactivado — es el detectordesactivado — cambia el diseño del slab y haría que cualquier reclaim no fuera representativo
Endurecimiento del slab—SLAB_FREELIST_RANDOM, SLAB_FREELIST_HARDENED activados; SLAB_BUCKETS, RANDOM_KMALLOC_CACHES desactivados
KASLR—RANDOMIZE_BASE, RANDOMIZE_MEMORY activados
Userlandinitramfs mínimoDebian GNU/Linux 13 (trixie), con el gcc propio de la distribución
Identidad inicialuid 1000, gid 1000, sin capacidades, sin namespacesigual
Línea de comandos de arranqueconsole=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1console=ttyS0 loglevel=4 rdinit=/init — sin nopti, sin nokaslr, sin mitigations=off