Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-64468 — Prova de conceito sem privilégios e escalonamento local de privilégios x86_64 para um use-after-free do Binder no kernel Linux (CVE-2026-64468), com laboratório KASAN e diferencial vulnerável/corrigido. | Kitploit
Ferramentas/GitHubGitHub/aramosf/cve-2026-64468
Escalada de PrivilégiosFrameworks de ExploraçãoAnálise de VulnerabilidadesExploraçãoAprendizado e EducaçãoExploração de Binários
GitHubaramosf/cve-2026-64468

CVE-2026-64468

Prova de conceito sem privilégios e escalonamento local de privilégios x86_64 para um use-after-free do Binder no kernel Linux (CVE-2026-64468), com laboratório KASAN e diferencial vulnerável/corrigido.

Ver Repositório
1há 15 diasAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-64468 — Use-after-free ao longo da vida do processo no Binder binder_free_transaction() do kernel Linux

Execução ao vivo em QEMU/KVM: kernel vulnerável sem patch reporta o use-after-free, kernel corrigido não reporta

Este repositório contém, para o use-after-free do Binder do kernel Linux corrigido pelo commit upstream f223d27a546c1e1f48d38fd67760e78f068fe8c4:

  • binder_chain_64468.c — uma prova de conceito sem privilégios que atinge o bug e permite que o kernel o comprove, com um laboratório KASAN e um teste diferencial vulnerável/corrigido (lab/, run.sh, verify.sh).
  • exploit.c — uma escalada de privilégios local autossuficiente para x86_64. Compila com gcc -O2 -pthread -o exploit exploit.c, executa como um usuário comum e termina em um shell root.
  • demo/ — um laboratório que inicializa um userland real do Debian 13 em um kernel sem patch, para que o exploit possa ser compilado pelo próprio gcc do alvo e executado na máquina que ele então assume.

Tudo é executado como um usuário comum (uid/gid 1000, sem capabilities, sem namespaces) contra kernels upstream padrão sem nenhum patch de qualquer tipo.

Aviso

Este código deliberadamente disputa tempos de vida de objetos do kernel e depois sequestra o fluxo de controle do kernel. Uma corrida perdida corrompe o estado do heap do kernel e pode causar pânico ou travar a máquina. Execute-o apenas em uma VM isolada e descartável que você possua. Não o execute em um host.

Status e escopo

Vulnerabilidade

binder_free_transaction() lê o processo alvo da transação sob t->lock, libera esse lock e, em seguida, adquire o lock interno do alvo:```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:~
Nada mantém `target_proc` vivo através dessa lacuna. Um processo que está sendo
destruído em paralelo pode alcançar `binder_proc_dec_tmpref() -> kfree()` no meio,
então o lock é adquirido em memória liberada. A correção upstream fixa `t->to_thread` enquanto
`t->lock` ainda está mantido, o que mantém o processo proprietário vivo até que o
lock interno tenha sido usado e liberado.

A vulnerabilidade foi relatada por **Alice Ryhl** e corrigida por **Carlos
Llamas**, ambos do Google. O
[relatório original](https://lore.kernel.org/all/[email protected]/)
carrega o trace KASAN de referência.

### Alcançando o acesso vulnerável

O único chamador que pode alcançar um `to_proc` *estrangeiro* é
`binder_send_failed_reply()`, e ele só percorre até `t->from_parent` quando
`t->from` é `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 é atribuído em exatamente um lugar, e a atribuição é protegida algumas linhas antes pela verificação de pilha de transações ruim do binder: uma thread só pode enviar uma transação síncrona enquanto o topo de sua pilha é uma transação que ela está recebendo. Portanto, para cada elo dessa cadeia, o remetente do filho e o receptor do pai são a mesma thread.

Isso tem uma consequência marcante. binder_thread_release() percorre a pilha da thread que está morrendo com proc->inner_lock mantido e escreve ambos os``` 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:~
em **iterações adjacentes de uma única caminhada**, cada uma sob o respectivo
`t->lock`. Um caminhante só descobre que `child->from == NULL` depois que a caminhada liberou
`child->lock`, e precisa de `parent->lock` para seu próprio snapshot. Portanto, toda a
oportunidade é o intervalo entre o `spin_unlock(&child->lock)` dessa caminhada e o seu
`spin_lock(&parent->lock)` — algumas instruções. A caminhada de liberação mantém um
spinlock e não pode ser preemptada ali; apenas uma interrupção pode atrasá-la.

É por isso que a corrida é estreita e por que tanto a prova de conceito quanto o
exploit são probabilísticos.

### O que a prova de conceito constrói

`binder_chain_64468.c` constrói a cadeia mais curta que alcança o
acesso vulnerável, de modo que o trabalho do caminhante dentro desse intervalo seja o menor que o binder
permite — duas aquisições de `t->lock` e um `kfree()`, sem `inner_proc_lock`
estrangeiro, sem `wake_up` e sem entrega de resposta:```
  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 então libera o seu fd de binder, de modo que binder_deferred_release() libera Y_1..Y_K e, por fim, libera o binder_proc, enquanto cada thread B emite simultaneamente 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:~
Um terceiro processo é o gerenciador de contexto do binder, usado apenas para distribuir os
handles que os outros dois precisam. `exploit.c` reutiliza exatamente essa construção.

### Duas condições independentes

Um relatório do KASAN precisa de ambas:

* **o acesso vulnerável** — o walker deve adquirir `parent->lock` dentro da
  janela estreita acima, para que ele capture um `to_proc` ainda vivo; e
* **o pouso da liberação dentro da janela** — o walker deve então perder sua CPU
  entre soltar `t->lock` e adquirir o lock interno da vítima, e permanecer fora dela
  até que a liberação adiada tenha terminado e liberado o `binder_proc`.

A primeira condição tem seu próprio oráculo que não precisa do KASAN: o binder imprime```
binder: binder_free_proc: Unexpected outstanding_txns -1

quando isso acontece, porque o walker e binder_thread_release() então ambos decrementam o mesmo contador para uma transação. Note que isso não é uma diferença vulnerável/corrigida por si só — a correção interrompe o free, não o segundo decremento — então aparece em ambos os kernels. É usado aqui apenas para mostrar o caminho de código vulnerável sendo exercitado.

De use-after-free a root

A prova de conceito para em um decremento de 4 bytes de memória liberada. Transformar isso em uid 0 precisa de quatro coisas, e nenhuma delas vem do bug em si: o bug não vaza nada.

1. Um cache compartilhado

struct binder_proc tem 648 bytes e é alocada com um GFP_KERNEL kzalloc simples — não __GFP_ACCOUNT. Portanto, ela cai em kmalloc-1k, junto com todas as outras alocações não contabilizadas desse tamanho, e não é isolada atrás de kmalloc-cg-*. Esse único fato é o que torna o objeto recuperável em primeiro lugar.

2. Uma bomba de recuperação que acompanha o ritmo

O momento do kfree() não é observável a partir do userspace, e também não é o momento em que o walker toca o objeto novamente, então não há nada para cronometrar. O spray, portanto, roda como uma bomba: ele aloca e libera objetos kmalloc-1k continuamente, a partir da CPU que executou o release adiado, enquanto os walkers estiverem rodando.

Mensagens System V são usadas para isso. alloc_msg() é um kmalloc simples não contabilizado de um cabeçalho de 48 bytes mais payload, então uma mensagem de 976 bytes é uma alocação de 1024 bytes; a cota é por fila em vez de por uid; e msgrcv() libera sincronamente. Throughput medido: ~198.000 alocações por segundo, zero falhas.

add_key/user_key_payload foi tentado primeiro e é uma armadilha. Seu payload é cobrado contra uma cota de bytes por uid (kernel.keys.maxbytes, 20000 por padrão) que é liberada apenas quando o coletor de lixo de chaves destrói a chave, então um loop apertado de alocar/liberar o esgota em milissegundos: medido 27.151 alocações bem-sucedidas contra 2.121.009 falhas — 98,7% do spray silenciosamente não fazendo nada, o que parece exatamente um spray que nunca ganha o slot. KEYCTL_INVALIDATE piorou (4.775 sucessos), porque ele enfileira trabalho de GC.

3. O que o objeto recuperado tem que conter

O walker toca quatro campos do binder_proc liberado (offsets medidos com pahole no build alvo):

Com outstanding_txns == 1 e is_frozen == 1, o decremento chega a zero e o walker chama wake_up_interruptible_all(&proc->freeze_wait). __wake_up_common então calcula curr = head.next - 24 e chama *(head.next - 8): um ponteiro de função lido de onde quer que head.next aponte, que é um valor que o objeto recuperado fornece.

4. Dois endereços, de um canal lateral

Esse ponteiro tem que alcançar memória que o atacante controla, em um endereço de kernel, e o bug não vaza nada. Ambos os endereços vêm de timing de prefetch em vez disso — o mesmo canal do KASLD (Brendan Coles, MIT), cuja implementação isso deriva:

  • Texto do kernel. Um prefetch de um endereço de kernel mapeado resolve na caminhada da tabela de páginas e se aposenta mensuravelmente mais rápido do que um de um endereço não mapeado, mesmo que o acesso nunca se torne arquiteturalmente visível. Escanear os slots de 2 MiB do intervalo de texto mostra a imagem como uma sequência de slots rápidos; seu primeiro slot é _text.

  • O mapa direto. Com CONFIG_RANDOMIZE_MEMORY o mapa direto é randomizado em unidades de 1 GiB, então ele também tem que ser localizado. Diferente do texto, ele cobre toda a RAM, então é a sequência contígua mais longa de slots mapeados. Dois refinamentos foram necessários para torná-lo utilizável:

    • um único prefetch separa mapeado de não mapeado por apenas ~4 ciclos sob KVM, o que não sobrevive ao ruído, então cada amostra cronometra um lote de 400 prefetches (medido 311 vs 523 ciclos — separável);
    • uma única varredura não é confiável em um host disputado — 10/10 corretos com um guest por vez, 3/5 com cinco guests ao mesmo tempo — então a varredura é executada cinco vezes e uma maioria é exigida. Sem consenso, o exploit reporta falha e para em vez de disparar em um endereço não verificado.

    O que a execução confiavelmente começa não é page_offset_base em si, mas o primeiro slot que o kernel poderia mapear com uma página de 1 GiB — aquele cobrindo o 4 GiB físico. Abaixo disso, o buraco PCI e as reservas de firmware forçam páginas de 2 MiB cuja caminhada mais longa não é separável de não mapeado aqui. Esse slot é exatamente o que o spray precisa, então é o que é usado.

Então ~60% da memória física é preenchida com cópias de uma única página de 4 KiB elaborada, então um offset fixo a partir dessa âncora é respaldado pela página elaborada qualquer que seja o layout que se revele.

A cadeia```

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 e sem `iretq`: a thread sequestrada retorna do seu `ioctl()`
normalmente e simplesmente volta ao userspace, como root.

### A cred forjada, e o bug que a teria desperdiçado

O gadget dispatcher obtém seu `RAX` de `cred+0x18`, e `cred+0x18` é
`euid`/`egid`, então imediatamente após a cadeia `euid` é a metade baixa de um ponteiro
de kernel. Isso é cosmético. O que não é cosmético é que `prepare_creds()` —
que **toda chamada posterior a `fork()` e `execve()` invoca** — desreferencia três campos
sem verificação de 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);

Uma credencial forjada que as deixa NULL dá uid 0 e depois causa pânico na máquina no primeiro execve — ou seja, o exploit reportaria sucesso e imediatamente destruiria a caixa. A cadeia, portanto, aponta user, ucounts e group_info para os globais reais do kernel root_user, init_ucounts e init_groups, cujos endereços vêm da mesma base _text. Com isso definido, a thread com privilégios detém CAP_SETUID sobre init_user_ns, então ela chama setresuid(0,0,0) e o kernel instala uma credencial root limpa, alocada pelo kernel, sobre a forjada. Só então qualquer outra coisa é feita.

O privilégio é entregue ao processo pai através de uma cópia do binário do exploit com setuid-root, que se remove antes de executar o shell, de modo que o shell root roda no processo principal num terminal limpo e nada com setuid fica para trás.

Quais sistemas são afetados

uname -r não é suficiente. Kernels de fornecedores rotineiramente aplicam backports de correções do binder sem alterar para a versão mainline correspondente; inspecione o código-fonte ou o changelog do pacote.

A correção é marcada com Cc: stable, então ramos estáveis e de fornecedores recebem backports.

Onde está o bug e onde ele é alcançável

Essas são perguntas diferentes, e a segunda é a que decide o impacto.

O código vulnerável é compilado onde quer que CONFIG_ANDROID_BINDER_IPC esteja definido. Isso inclui as distribuições de propósito geral — mas em todas as pesquisadas o driver é um módulo que não é carregado por padrão, e mesmo quando é carregado, init_binder_device() registra o dispositivo misc sem definir miscdev.mode, então devtmpfs cria /dev/binder como 0600 root:root. No Android, é o ueventd que o abre para 0666, que é exatamente por que o bug importa lá e na maioria das vezes não aqui.

Configurações lidas dos próprios pacotes de kernel fornecidos pelas distribuições:

A exposição prática numa distribuição desktop é, portanto, indireta: qualquer coisa que carregue o binder e o abra — Waydroid, Anbox, um emulador Android ou runtime de contêiner — reintroduz exatamente a alcançabilidade do Android numa máquina cujo kernel ainda tem o bug.

O que cada opção de endurecimento custa ao exploit

Estas não afetam a vulnerabilidade; afetam este exploit.

Alvos validados

Ambos os kernels são árvores upstream padrão. Nada está corrigido.

CONFIG_KASAN_GENERIC no primeiro laboratório é um detector, não um habilitador: a corrida é idêntica sem ele. CONFIG_PREEMPT é um pré-requisito real, e é o que o Android fornece.

A arquitetura não é um fator para o bug — é um erro de tempo de vida em C independente de arquitetura. É muito um fator para o exploit: os gadgets, o canal de prefetch e o layout do mapa direto são todos x86_64.

Compilação e execução

Requisitos: clang, lld, make, cpio, gzip, qemu-system-x86_64, docker (apenas para montar o rootfs Debian), um clone local da árvore git do Linux, e gcc.

O exploit, num sistema que já é vulnerável```sh

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

root@kitploit:~
Para um kernel diferente do que está em `demo/`, extraia primeiro os seus offsets:```sh
./mkoffsets.sh /path/to/vmlinux > offsets.h
gcc -O2 -pthread -DEXPLOIT_OFFSETS='"offsets.h"' -o exploit exploit.c

Tunables, todos opcionais, todos lidos do ambiente: 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.

O laboratório de segurança de memória```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:~
O script cria duas worktrees destacadas nos commits acima, recusa-se a executar
se alguma das worktrees estiver suja, verifica se a árvore vulnerável não contém a correção e
se a árvore corrigida a possui, compila ambos os kernels e empacota a prova de conceito
em um initramfs.

### O laboratório de exploração```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 inicia o convidado e entrega o console ao uid 1000, que compila exploit.c com o próprio gcc do convidado e o executa.

Imagens de kernel, worktrees, árvores rootfs e initramfs são artefatos de laboratório e não são versionados.

Execuções reais

Segurança de memória, vulnerável versus corrigido

docs/example-output.txt é a transcrição real do kernel vulnerável, incluindo o relatório KASAN; docs/patched-negative-output.txt é o controle do kernel corrigido; docs/e2e-results.json é o resultado legível por máquina.

A cadeia de chamadas relatada corresponde exatamente ao relatório upstream:``` 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` é a identidade não privilegiada do próprio conceito de prova: a vítima
`binder_proc` é alocada por `binder_open()`, libertada pela workqueue adiada do binder,
e lida pelo walker após a libertação.

Execução registada, 2026-08-16, `./verify.sh 1200 16 3` — três convidados QEMU/KVM
concorrentes por variante, 10 vCPUs cada, 16 threads, 1200 s por variante, **sem patches
de kernel em nenhum dos lados**:

| | Vulnerável `114a116aaa5f` | Corrigido `f223d27a546c` |
| --- | --- | --- |
| Tentativas | 158.384 | 158.471 |
| Walks | 2.534.144 | 2.535.536 |
| Falhas de configuração | 0 | 0 |
| Acesso vulnerável (`Unexpected outstanding_txns -1`) | 1.127 | 934 |
| **KASAN `slab-use-after-free`** | **8** | **0** |

Essa é a diferença: a mesma carga de trabalho, a mesma contagem de tentativas com uma
diferença de 0,06%, e o use-after-free apenas no kernel vulnerável sem patch.

O acesso vulnerável aparece em *ambos* os kernels, e isso é esperado — a correção
impede que o processo seja libertado dentro da janela, não o segundo decremento de
`outstanding_txns`. É por isso que essa linha é usada apenas como um oráculo barato
e nunca como a diferença.

### Escalação de privilégios

![Execução QEMU/KVM ao vivo: convidado Debian 13, utilizador não privilegiado compila exploit.c com o próprio gcc do convidado e termina num prompt de root](https://assets.kitploit.com/production/public/readmes/54229/da1e333d7516671ffab64e10d89604ee2a8043de1c48306abc03aaeee88d0d62/205a1253ab216ae4b62a65f761fffb9f0329bcbf9e4feb38563647f68ad2c5e0-display-v1.webp)

`docs/lpe-output.txt` é uma transcrição real de um convidado de demonstração, e
`docs/lpe-demo.cast` é a gravação Asciinema completa e sem edição a partir da qual a
animação acima foi renderizada (`asciinema play docs/lpe-demo.cast` reprodu-la na
íntegra). A animação omite a longa parte central da corrida dessa gravação — a consola
serial do convidado transmite debug do binder durante toda a corrida de ~24 minutos, o
que renderizaria dezenas de megabytes de registo em scroll — mantendo o preâmbulo do
Debian e a shell de root; a linha do próprio exploit `hit after 26661 attempts ... in 1437s`
indica exatamente o que foi omitido. Ambas são execuções únicas: um convidado que não
vence dentro do seu orçamento desliga-se, e a gravação é simplesmente repetida em vez de
ser editada para uma vitória.

Consulte *Fiabilidade* abaixo para a taxa de sucesso medida.

## Fiabilidade

A corrida é probabilística em ambos os aspetos, pelo que uma execução falhada é o
comportamento esperado algumas vezes, não um exploit partido.

### Segurança de memória

Taxas derivadas no kernel vulnerável, a partir da execução acima:

| Quantidade | Valor |
| --- | --- |
| Taxa de tentativas | ~44 tentativas/s por convidado, ~132/s em três |
| Acesso vulnerável | 7,1e-3 por tentativa |
| Libertação a aterrar dentro da janela, dado o acesso | 7,1e-3 |
| Relatório KASAN | ~1 por 20.000 tentativas, ou seja, aproximadamente um a cada 2,5 minutos a esta taxa |

### Escalação de privilégios

Cada convidado é uma tentativa independente: o seu próprio KASLR, a sua própria
aleatorização do direct-map, e um convidado que vence deixa de correr. O valor é, portanto,
uma **taxa de sucesso por arranque**, não por tentativa.

Campanha registada, 2026-08-16 23:32 UTC, `GUESTS=6 CPUS=8 MEMORY_MB=9216 ./demo/verify.sh`
— seis convidados QEMU/KVM concorrentes, `114a116aaa5f` sem patch, userland Debian 13,
orçamento de 60 minutos cada, exploit compilado no convidado, iniciado como uid 1000:

| | |
| --- | --- |
| Convidados que atingiram uid 0 | **2 de 6** |
| Tempo até root | 623 s e 1.344 s |
| Tentativas na corrida vencedora | 13.839 e 31.125 |
| Tentativas de cada convidado que não venceu | ~95.000 ao longo dos 3.600 s completos |
| Total de tentativas na campanha | 427.676 (6,8 M de stack walks) |
| Acessos vulneráveis observados (`Unexpected outstanding_txns -1`) | 256 |
| **Crashes, oopses ou panics do kernel** | **0**, em 9 guest-hours |

Duas coisas valem a pena destacar dessa tabela.

**Essencialmente, todas as corridas vencidas tornaram-se root.** O laboratório KASAN mede a
probabilidade de a libertação aterrar dentro da janela, dado o acesso vulnerável,
em 7,1e-3. Aplicado aos 256 acessos observados aqui, isso prevê ~1,8
use-after-frees ao longo da campanha — e foram obtidos 2 roots. A reclaim, a descoberta
de endereço e a cadeia não são o gargalo; a corrida é.

**Nada crashou.** Nenhum convidado sofreu um oops em nove guest-hours, incluindo os
quatro que nunca venceram. Ou a cadeia dispara contra uma página corretamente localizada e
sprayed, ou nunca dispara de todo — que é para isso que serve a recusa por maioria de votos
no estágio 2.

Essa recusa dispara na prática. Arrancar seis convidados de uma vez num host já carregado
produziu um que desistiu no estágio 2 com```
[*] direct map not found; refusing to fire at an unverified address

e saiu sem corrida. Esse é o comportamento pretendido: um boot desperdiçado é o resultado correto quando o canal de temporização não consegue chegar a um consenso, e é muito melhor do que a alternativa de disparar a cadeia em um endereço que nunca foi confirmado.

docs/lpe-results.json carrega a forma legível por máquina, incluindo o SHA-256 do kernel e initramfs exatos usados.

Por que vários convidados, e por que um host superalocado

Executar os convidados simultaneamente não é apenas sobre paralelismo. Em um host contendido, o KVM desescalona os vCPUs dos convidados, e esse é exatamente o atraso que a segunda condição precisa: o caminhante tem que perder sua CPU entre soltar t->lock e obter o lock interno da vítima. Um único convidado em um host ocioso foi medido em aproximadamente um vigésimo da taxa de acesso vulnerável de três convidados simultâneos.

Há um limite superior, no entanto. Com oito convidados de 8 vCPUs cada em um host de 32 threads, com ~5 GiB de spray de mapa direto por convidado, o host entrou em swap e três dos oito convidados não fizeram nenhum progresso. Seis é o padrão enviado.

Os threads auxiliares opcionais de preempção no convidado foram medidos com um custo de cerca de três vezes a taxa de tentativas sem melhorar a taxa de acerto, e não são usados.

Um fator ambiental acabou importando mais do que o esperado: a própria saída de debug do binder. Com binder.debug_mask em seu padrão, o driver emite uma grande quantidade de tráfego pr_info limitado por taxa durante a corrida, e a pressão de printk e console-lock que isso cria alonga exatamente a janela de preempção que a segunda condição precisa. Silenciá-lo com binder.debug_mask=0 para um console mais limpo — a coisa óbvia a se fazer para uma gravação — reduziu mensuravelmente a taxa de acerto nos testes: os convidados rodaram muito além das contagens de tentativas de ambos os vencedores sem um acerto. demo/run-demo.sh portanto deixa o debug do binder em seu padrão, e um console silencioso é uma opção explícita. Esta é uma propriedade do laboratório, não do exploit — mas é uma boa ilustração de o quanto essa corrida depende de jitter de temporização em todo o sistema, em vez de qualquer coisa que o próprio exploit controle.

Segurança

  • Ambos os convidados são imagens initramfs descartáveis. Nada é gravado no disco do convidado ou do host.
  • A prova de conceito apenas abre /dev/binder, envia transações binder e encerra threads binder. Ela não instala nada e não deixa nada para trás.
  • O exploit grava um arquivo: uma cópia setuid-root de si mesmo, usada para passar privilégio do thread vencedor para o processo pai. Ele remove o link dessa cópia antes de executar o shell, então nada setuid sobrevive à execução.
  • Uma corrida perdida pode causar pânico ou travar o convidado; o laboratório de segurança de memória inicializa com panic=1 oops=panic para que uma execução termine em vez de continuar em estado corrompido. Sempre reinicie a partir de um boot limpo.

Créditos

  • Autor do exploit: A. Ramos <[email protected]> (Twitter: @aramosf)
  • Descoberta e relato da vulnerabilidade: Alice Ryhl, Google
  • Correção upstream: Carlos Llamas, Google
  • Canal lateral de KASLR por prefetch: derivado de KASLD, Copyright (c) 2019 Brendan Coles, licenciado sob MIT. A variante de mapa direto é um trabalho novo aqui.

Busca por exploit público

Verificado em 2026-08-16 com SearchSploit (cópia local do Exploit-DB) e busca na web por CVE-2026-64468, binder_free_transaction e f223d27a546c. Nenhum exploit público ou prova de conceito para este CVE foi encontrado; SearchSploit retorna apenas entradas binder Android antigas e não relacionadas. Esta é uma verificação pontual, não uma garantia permanente.

Baixar ferramenta
EstadoAfirmação
ConfirmadoA vulnerabilidade é real, é alcançável a partir de um processo sem privilégios, e a correção upstream a remove.
DemonstradoA dereferência vulnerável de um binder_proc moribundo é alcançada naturalmente e repetidamente no kernel sem patch, e nunca no kernel corrigido.
DemonstradoO use-after-free completo, reportado pelo KASAN, no kernel sem patch.
DemonstradoRecuperação do binder_proc liberado com bytes controlados pelo atacante, sequestro do fluxo de controle do kernel e escalada de privilégios para uid 0 a partir de um usuário sem privilégios, em x86_64.
Não afirmadoQualquer resultado em um fornecedor específico ou dispositivo Android. Apenas os kernels upstream listados abaixo foram testados, em x86_64.
Não afirmadoQue o exploit fornecido funcione sem modificações contra um kernel de distribuição. Consulte Quais sistemas são afetados: ele precisa de offsets por kernel, e em todas as distribuições de propósito geral pesquisadas o dispositivo binder não é alcançável por um usuário sem privilégios em primeiro lugar.
OffsetCampoO que o walker faz
108int outstanding_txnsdecrementa
113bool is_frozenlê
120wait_queue_head_t freeze_waitpercorre se outstanding_txns == 0 && is_frozen
624spinlock_t inner_lockadquire e libera
EstadoCommitNotas
Linhagem introduzidaa370003cc301Nomeada pela tag Fixes: upstream
Validado como vulnerável114a116aaa5fPai direto da correção; carrega a correção vizinha da CVE-2026-64469, então o par isola a CVE-2026-64468 sozinha
Mainline corrigidof223d27a546cA correção sob teste
DistribuiçãoKernelANDROID_BINDER_IPCDispositivoSLAB_BUCKETSRANDOM_KMALLOC_CACHESAlcançável sem privilégios?
Debian 13 (trixie)6.12.101mANDROID_BINDER_DEVICES="binder", BINDERFS desativadoydesativadoNão — módulo não carregado; /dev/binder é 0600
Debian 12 (bookworm)6.1.0mANDROID_BINDER_DEVICES="binder", BINDERFS desativadon/d (pré-6.11)n/d (pré-6.6)Não — o mesmo
Ubuntu 24.04 LTS6.8.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""n/d (pré-6.11)yNão — precisa de root para mount -t binder
Ubuntu 22.04 LTS5.15.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""n/dn/dNão — o mesmo
Android (AOSP / fornecedor)6.1, 6.6, 6.12 GKIy/dev/binder, /dev/hwbinder, /dev/vndbinder——Sim — o driver é o IPC da plataforma e é acessível mundialmente
OpçãoEfeito aqui
CONFIG_SLAB_BUCKETS (6.11+)Fatal para esta reclaim. Isola msg_msg nos seus próprios buckets kmalloc, então a bomba nunca pode pousar no slot de binder_proc. O Debian 13 a define. Outra alocação não contabilizada de 1 KiB teria de ser encontrada. O kernel 6.6, que é o que os dispositivos Android de interesse executam, a antecede por completo.
CONFIG_RANDOM_KMALLOC_CACHES (6.6+)Divide kmalloc-1k em vários caches por local de chamada, então a bomba tem de atingir o mesmo; um imposto de 1 em 16 na reclaim, não uma barreira. O Ubuntu a define, o Debian não.
Isolamento de tabela de páginas (nopti não usado)Fatal para a descoberta de endereços. O prefetch não consegue ver o texto do kernel com PTI ativo, e o exploit detecta isso e para. O PTI é compilado em todas as distribuições acima, mas a CPU decide se está ativo: está desativado em hardware não afetado por Meltdown, que é onde estes resultados foram medidos.
CONFIG_SLAB_FREELIST_RANDOM, ..._HARDENEDAtivadas no laboratório, como as distribuições as fornecem. Sem efeito mensurável: a bomba não prevê a ordem da freelist, ela simplesmente aloca um grande número de objetos.
KASLR (RANDOMIZE_BASE, RANDOMIZE_MEMORY)Ativado. Derrotado pelos estágios de prefetch; sem nokaslr.
Deslocamentos por kernelA cadeia precisa de commit_creds, três globais relacionados a cred e dois gadgets, como deslocamentos de _text. mkoffsets.sh os extrai de um vmlinux alvo; sem eles o exploit dispara em endereços errados. Esta é uma propriedade por compilação de qualquer exploit de kernel, não uma defesa.
Laboratório de segurança de memória (lab/)Laboratório de exploração (demo/)
Versão do kernel7.2.0-rc1+7.2.0-rc1+
Commit vulnerável114a116aaa5f0295376cdf12da743c5bce3b20ceo mesmo
Commit corrigidof223d27a546c1e1f48d38fd67760e78f068fe8c4— (a exploração é medida apenas no kernel vulnerável)
Arquiteturax86_64 (KVM) e arm64 (TCG)x86_64 (KVM)
CompiladorUbuntu clang 21.1.8 / LLD 21.1.8o mesmo
KASANativado — é o detectordesativado — altera o layout do slab e tornaria qualquer reclaim não representativa
Endurecimento do slab—SLAB_FREELIST_RANDOM, SLAB_FREELIST_HARDENED ativados; SLAB_BUCKETS, RANDOM_KMALLOC_CACHES desativados
KASLR—RANDOMIZE_BASE, RANDOMIZE_MEMORY ativados
Userlandinitramfs mínimoDebian GNU/Linux 13 (trixie), com o próprio gcc da distribuição
Identidade inicialuid 1000, gid 1000, sem capacidades, sem namespaceso mesmo
Linha de comando de bootconsole=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1console=ttyS0 loglevel=4 rdinit=/init — sem nopti, sem nokaslr, sem mitigations=off