
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.
binder_free_transaction() do kernel Linux
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.
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);
if (target_proc) {
binder_inner_proc_lock(target_proc); /* use after free */
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
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
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.
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.
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.
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.
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.
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:
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.
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
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.
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.
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.
Estas não afetam a vulnerabilidade; afetam este exploit.
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.
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.
gcc -O2 -pthread -o exploit exploit.c ./exploit
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.
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
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.
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
`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

`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.
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.
/dev/binder, envia transações binder e
encerra threads binder. Ela não instala nada e não deixa nada para trás.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.<[email protected]> (Twitter:
@aramosf)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.
| Estado | Afirmação |
|---|
| Confirmado | A vulnerabilidade é real, é alcançável a partir de um processo sem privilégios, e a correção upstream a remove. |
| Demonstrado | A dereferência vulnerável de um binder_proc moribundo é alcançada naturalmente e repetidamente no kernel sem patch, e nunca no kernel corrigido. |
| Demonstrado | O use-after-free completo, reportado pelo KASAN, no kernel sem patch. |
| Demonstrado | Recuperaçã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 afirmado | Qualquer resultado em um fornecedor específico ou dispositivo Android. Apenas os kernels upstream listados abaixo foram testados, em x86_64. |
| Não afirmado | Que 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. |
| Offset | Campo | O que o walker faz |
|---|
| 108 | int outstanding_txns | decrementa |
| 113 | bool is_frozen | lê |
| 120 | wait_queue_head_t freeze_wait | percorre se outstanding_txns == 0 && is_frozen |
| 624 | spinlock_t inner_lock | adquire e libera |
| Estado | Commit | Notas |
|---|
| Linhagem introduzida | a370003cc301 | Nomeada pela tag Fixes: upstream |
| Validado como vulnerável | 114a116aaa5f | Pai direto da correção; carrega a correção vizinha da CVE-2026-64469, então o par isola a CVE-2026-64468 sozinha |
| Mainline corrigido | f223d27a546c | A correção sob teste |
| Distribuição | Kernel | ANDROID_BINDER_IPC | Dispositivo | SLAB_BUCKETS | RANDOM_KMALLOC_CACHES | Alcançável sem privilégios? |
|---|
| Debian 13 (trixie) | 6.12.101 | m | ANDROID_BINDER_DEVICES="binder", BINDERFS desativado | y | desativado | Não — módulo não carregado; /dev/binder é 0600 |
| Debian 12 (bookworm) | 6.1.0 | m | ANDROID_BINDER_DEVICES="binder", BINDERFS desativado | n/d (pré-6.11) | n/d (pré-6.6) | Não — o mesmo |
| Ubuntu 24.04 LTS | 6.8.0 | m | BINDERFS=m, ANDROID_BINDER_DEVICES="" | n/d (pré-6.11) | y | Não — precisa de root para mount -t binder |
| Ubuntu 22.04 LTS | 5.15.0 | m | BINDERFS=m, ANDROID_BINDER_DEVICES="" | n/d | n/d | Não — o mesmo |
| Android (AOSP / fornecedor) | 6.1, 6.6, 6.12 GKI | y | /dev/binder, /dev/hwbinder, /dev/vndbinder | — | — | Sim — o driver é o IPC da plataforma e é acessível mundialmente |
| Opção | Efeito 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, ..._HARDENED | Ativadas 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 kernel | A 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 kernel | 7.2.0-rc1+ | 7.2.0-rc1+ |
| Commit vulnerável | 114a116aaa5f0295376cdf12da743c5bce3b20ce | o mesmo |
| Commit corrigido | f223d27a546c1e1f48d38fd67760e78f068fe8c4 | — (a exploração é medida apenas no kernel vulnerável) |
| Arquitetura | x86_64 (KVM) e arm64 (TCG) | x86_64 (KVM) |
| Compilador | Ubuntu clang 21.1.8 / LLD 21.1.8 | o mesmo |
| KASAN | ativado — é o detector | desativado — 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 |
| Userland | initramfs mínimo | Debian GNU/Linux 13 (trixie), com o próprio gcc da distribuição |
| Identidade inicial | uid 1000, gid 1000, sem capacidades, sem namespaces | o mesmo |
| Linha de comando de boot | console=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1 | console=ttyS0 loglevel=4 rdinit=/init — sem nopti, sem nokaslr, sem mitigations=off |