Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2026-64468 — Privilege escalation locale non privilegiato e proof-of-concept x86_64 per un use-after-free del kernel Linux Binder (CVE-2026-64468), con laboratorio KASAN e differenziale vulnerabile/corretto. | Kitploit
Strumenti/GitHubGitHub/aramosf/cve-2026-64468
Escalation di PrivilegiFramework di ExploitAnalisi delle VulnerabilitàExploitApprendimento e FormazioneBinary Exploitation
GitHubaramosf/cve-2026-64468

CVE-2026-64468

Privilege escalation locale non privilegiato e proof-of-concept x86_64 per un use-after-free del kernel Linux Binder (CVE-2026-64468), con laboratorio KASAN e differenziale vulnerabile/corretto.

Vedi Repository
115 giorni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2026-64468 — Use-after-free della durata del processo in binder_free_transaction() del kernel Linux

Esecuzione live QEMU/KVM: il kernel vulnerabile non patchato segnala l'use-after-free, il kernel corretto no

Questo repository contiene, per l'use-after-free del Binder del kernel Linux corretto dal commit upstream f223d27a546c1e1f48d38fd67760e78f068fe8c4:

  • binder_chain_64468.c — una proof of concept non privilegiata che raggiunge il bug e lascia che il kernel lo dimostri, con un laboratorio KASAN e un confronto differenziale vulnerabile/corretto (lab/, run.sh, verify.sh).
  • exploit.c — un'escalation di privilegi locale autonoma per x86_64. Si compila con gcc -O2 -pthread -o exploit exploit.c, viene eseguito come utente normale e termina in una shell di root.
  • demo/ — un laboratorio che avvia un vero userland Debian 13 su un kernel non patchato, così che l'exploit possa essere compilato dal gcc della macchina di destinazione ed eseguito sulla macchina che poi prende il controllo.

Tutto viene eseguito come utente normale (uid/gid 1000, nessuna capability, nessun namespace) contro kernel upstream stock senza alcuna patch di alcun tipo.

Avviso

Questo codice fa deliberatamente gareggiare i tempi di vita degli oggetti del kernel e poi dirotta il flusso di controllo del kernel. Una gara persa corrompe lo stato dell'heap del kernel e può causare un panic o un blocco della macchina. Eseguilo solo in una VM isolata e usa e getta che possiedi. Non eseguirlo su un host.

Stato e ambito

Vulnerabilità

binder_free_transaction() legge il processo di destinazione dalla transazione sotto t->lock, rilascia quel lock e poi acquisisce il lock interno della destinazione:```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.

### Raggiungere l'accesso vulnerabile

L'unico chiamante che può raggiungere un `to_proc` *esterno* è
`binder_send_failed_reply()`, e attraversa `t->from_parent` solo 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 viene assegnato in un unico punto, e l'assegnazione è protetta poche righe prima dal controllo di bad transaction stack del binder: un thread può inviare una transazione sincrona solo mentre la cima del suo stack è una transazione che sta ricevendo. Pertanto, per ogni anello di quella catena, il sender del figlio e il receiver del genitore sono lo stesso thread.

Questo ha una conseguenza netta. binder_thread_release() percorre lo stack del thread morente con proc->inner_lock acquisito e scrive entrambi i``` 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:~
in **iterazioni adiacenti di una singola walk**, ciascuna sotto il rispettivo
`t->lock`. Un walker apprende che `child->from == NULL` solo dopo che la walk ha rilasciato
`child->lock`, e necessita di `parent->lock` per la propria snapshot. Quindi l'intera
opportunità è il divario tra lo `spin_unlock(&child->lock)` di quella walk e il suo
`spin_lock(&parent->lock)` — un paio di istruzioni. La release walk mantiene uno
spinlock e non può essere preemptata lì; solo un interrupt può ritardarla.

Questo è il motivo per cui la race è ristretta e per cui sia la proof of concept che
l'exploit sono probabilistici.

### Cosa costruisce la proof of concept

`binder_chain_64468.c` costruisce la catena più corta che raggiunge
l'accesso vulnerabile, così il lavoro del walker all'interno di quel divario è il più piccolo
che binder consente — due acquisizioni di `t->lock` e una `kfree()`, senza
`inner_proc_lock` estraneo, senza `wake_up` e senza consegna di risposta:```
  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 quindi chiude il proprio fd del binder, così binder_deferred_release() rilascia Y_1..Y_K e infine libera il binder_proc, mentre ogni thread B emette contemporaneamente 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 terzo processo è il binder context manager, usato solo per distribuire gli
handle di cui gli altri due hanno bisogno. `exploit.c` riutilizza esattamente questa costruzione.

### Due condizioni indipendenti

Un report KASAN richiede entrambe le condizioni:

* **l'accesso vulnerabile** — il walker deve acquisire `parent->lock` all'interno della
  finestra ristretta sopra, così da catturare un'istantanea di un `to_proc` ancora vivo; e
* **il free che atterra dentro la finestra** — il walker deve poi perdere la CPU
  tra il rilascio di `t->lock` e l'acquisizione del lock interno della vittima, e restarne lontano
  finché il rilascio differito non è terminato e ha liberato il `binder_proc`.

La prima condizione ha un proprio oracolo che non richiede KASAN: binder stampa```
binder: binder_free_proc: Unexpected outstanding_txns -1

quando ciò accade, perché il walker e binder_thread_release() poi decrementano entrambi lo stesso contatore per una transazione. Nota che questa non è di per sé una differenza vulnerabile/corretta — la correzione ferma la free, non il secondo decremento — quindi appare su entrambi i kernel. Qui viene usata solo per mostrare il percorso di codice vulnerabile in esecuzione.

Da use-after-free a root

La proof of concept si ferma a un decremento di 4 byte di memoria liberata. Trasformare questo in uid 0 richiede quattro cose, e nessuna di esse deriva dal bug stesso: il bug non fa trapelare nulla.

1. Una cache condivisa

struct binder_proc è di 648 byte ed è allocata con un semplice GFP_KERNEL kzalloc — non __GFP_ACCOUNT. Finisce quindi in kmalloc-1k, insieme a ogni altra allocazione non contabilizzata di quella dimensione, e non è isolata dietro kmalloc-cg-*. Questo singolo fatto è ciò che rende l'oggetto riutilizzabile.

2. Una pompa di riallocazione che tiene il passo

Il momento della kfree() non è osservabile dallo spazio utente, e nemmeno lo è il momento in cui il walker tocca di nuovo l'oggetto, quindi non c'è nulla su cui basare il timing. Lo spray funziona quindi come una pompa: alloca e libera oggetti kmalloc-1k continuamente, dalla CPU che ha eseguito il rilascio differito, per tutto il tempo in cui i walker sono in esecuzione.

Per questo vengono usati i messaggi System V. alloc_msg() è un semplice kmalloc non contabilizzato di un header di 48 byte più il payload, quindi un messaggio di 976 byte è un'allocazione di 1024 byte; la quota è per coda piuttosto che per uid; e msgrcv() libera in modo sincrono. Throughput misurato: ~198.000 allocazioni al secondo, zero errori.

add_key/user_key_payload è stato provato per primo ed è una trappola. Il suo payload viene addebitato a una quota di byte per uid (kernel.keys.maxbytes, 20000 di default) che viene rilasciata solo quando il garbage collector delle chiavi distrugge la chiave, quindi un ciclo serrato di allocazione/liberazione la esaurisce in millisecondi: misurati 27.151 allocazioni riuscite contro 2.121.009 errori — il 98,7% dello spray che silenziosamente non fa nulla, il che sembra esattamente uno spray che non vince mai lo slot. KEYCTL_INVALIDATE ha peggiorato le cose (4.775 successi), perché mette in coda il lavoro del GC.

3. Cosa deve contenere l'oggetto riallocato

Il walker tocca quattro campi del binder_proc liberato (offset misurati con pahole sulla build di destinazione):

Con outstanding_txns == 1 e is_frozen == 1, il decremento raggiunge zero e il walker chiama wake_up_interruptible_all(&proc->freeze_wait). __wake_up_common calcola quindi curr = head.next - 24 e chiama *(head.next - 8): un puntatore a funzione letto da dove punta head.next, che è un valore fornito dall'oggetto riallocato.

4. Due indirizzi, da un canale laterale

Quel puntatore deve raggiungere memoria controllata dall'attaccante, a un indirizzo kernel, e il bug non fa trapelare nulla. Entrambi gli indirizzi provengono invece dal timing di prefetch — lo stesso canale di KASLD (Brendan Coles, MIT), dalla cui implementazione questo deriva:

  • Testo del kernel. Un prefetch di un indirizzo kernel mappato si risolve nella camminata della page table e termina in modo misurabilmente più veloce di uno di un indirizzo non mappato, anche se l'accesso non diventa mai visibile architetturalmente. La scansione degli slot da 2 MiB dell'intervallo di testo mostra l'immagine come una sequenza di slot veloci; il suo primo slot è _text.

  • La mappa diretta. Con CONFIG_RANDOMIZE_MEMORY la mappa diretta è randomizzata in unità da 1 GiB, quindi deve essere individuata anch'essa. A differenza del testo, copre tutta la RAM, quindi è la sequenza contigua più lunga di slot mappati. Sono state necessarie due rifiniture per renderla utilizzabile:

    • un singolo prefetch separa mappato da non mappato di solo ~4 cicli sotto KVM, il che non sopravvive al rumore, quindi ogni campione misura il tempo di un batch di 400 prefetch (misurati 311 contro 523 cicli — separabili);
    • una singola scansione non è affidabile su un host conteso — 10/10 corrette con un guest alla volta, 3/5 con cinque guest contemporaneamente — quindi la scansione viene eseguita cinque volte e viene richiesta una maggioranza. Senza consenso l'exploit segnala un errore e si ferma piuttosto che sparare a un indirizzo non verificato.

    Ciò da cui l'esecuzione parte in modo affidabile non è page_offset_base stesso ma il primo slot che il kernel potrebbe mappare con una pagina da 1 GiB — quello che copre i 4 GiB fisici. Al di sotto, il buco PCI e le riserve del firmware impongono pagine da 2 MiB la cui camminata più lunga non è separabile da quella non mappata qui. Quello slot è esattamente ciò di cui lo spray ha bisogno, quindi è ciò che viene usato.

Poi ~60% della memoria fisica viene riempita con copie di una singola pagina da 4 KiB artefatta, così un offset fisso da quell'ancora è supportato dalla pagina artefatta qualunque sia il layout risultante.

La catena```

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:~
Nessun stack pivot e nessun `iretq`: il thread dirottato esce normalmente dalla sua `ioctl()`
e si ritrova semplicemente nello spazio utente, come root.

### Le credenziali contraffatte, e il bug che le avrebbe sprecate

Il gadget dispatcher prende il suo `RAX` da `cred+0x18`, e `cred+0x18` è
`euid`/`egid`, quindi subito dopo la catena `euid` è la metà bassa di un puntatore
del kernel. Questo è solo estetico. Ciò che non è estetico è che `prepare_creds()` —
che **ogni successiva `fork()` ed `execve()` chiama** — dereferenzia tre campi
senza alcun controllo 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);

Una credenziale falsificata che le lascia NULL assegna uid 0 e poi manda in panic la macchina alla prima execve — cioè, l'exploit riporterebbe successo e distruggerebbe immediatamente il sistema. La catena quindi punta user, ucounts e group_info ai globali reali del kernel root_user, init_ucounts e init_groups, i cui indirizzi provengono dalla stessa base _text. Con questi impostati, il thread con privilegi elevati detiene CAP_SETUID su init_user_ns, quindi chiama setresuid(0,0,0) e il kernel installa una credenziale root pulita e allocata dal kernel sopra quella falsificata. Solo allora viene fatto qualsiasi altro passo.

Il privilegio viene trasferito al processo padre tramite una copia setuid-root del binario dell'exploit, che rimuove se stessa prima di eseguire la shell, così la shell root gira nel processo principale su un terminale pulito e non rimane nulla di setuid.

Quali sistemi sono interessati

uname -r non basta. I kernel dei vendor applicano regolarmente backport delle correzioni binder senza cambiare alla corrispondente versione mainline; ispeziona il sorgente o il changelog del pacchetto.

La correzione è taggata Cc: stable, quindi i rami stable e dei vendor ricevono i backport.

Dove si trova il bug e dove è raggiungibile

Sono domande diverse, e la seconda è quella che determina l'impatto.

Il codice vulnerabile viene compilato ovunque sia impostato CONFIG_ANDROID_BINDER_IPC. Questo include le distribuzioni generiche — ma su tutte quelle esaminate il driver è un modulo che non viene caricato di default, e anche quando viene caricato, init_binder_device() registra il dispositivo misc senza impostare miscdev.mode, quindi devtmpfs crea /dev/binder come 0600 root:root. Su Android è ueventd ad aprirlo a 0666, che è esattamente il motivo per cui il bug conta lì e per lo più non qui.

Configurazioni lette dai pacchetti kernel forniti dalle distribuzioni stesse:

L'esposizione pratica su una distribuzione desktop è quindi indiretta: qualsiasi cosa che carichi binder e lo apra — Waydroid, Anbox, un emulatore Android o un runtime container — reintroduce esattamente la raggiungibilità di Android su una macchina il cui kernel ha ancora il bug.

Cosa costa all'exploit ogni opzione di hardening

Queste non influenzano la vulnerabilità; influenzano questo exploit.

Target validati

Entrambi i kernel sono alberi upstream standard. Niente è patchato.

CONFIG_KASAN_GENERIC nel primo laboratorio è un rilevatore, non un abilitatore: la race è identica senza di esso. CONFIG_PREEMPT è una vera precondizione, ed è ciò che Android fornisce.

L'architettura non è un fattore per il bug — è un errore di lifetime in C indipendente dall'architettura. È invece molto un fattore per l'exploit: i gadget, il canale di prefetch e il layout della direct-map sono tutti x86_64.

Compilazione ed esecuzione

Requisiti: clang, lld, make, cpio, gzip, qemu-system-x86_64, docker (solo per assemblare il rootfs Debian), un clone locale dell'albero git di Linux e gcc.

L'exploit, su un sistema già vulnerabile```sh

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

root@kitploit:~
Per un kernel diverso da quello in `demo/`, estrai prima i suoi offset:```sh
./mkoffsets.sh /path/to/vmlinux > offsets.h
gcc -O2 -pthread -DEXPLOIT_OFFSETS='"offsets.h"' -o exploit exploit.c

Tunables, tutti opzionali, tutti letti dall'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.

Il laboratorio di sicurezza della 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:~
The script creates two detached worktrees at the commits above, refuses to run
if either worktree is dirty, verifies that the vulnerable tree lacks the fix and
the fixed tree carries it, builds both kernels, and packs the proof of concept
into an initramfs.

### The exploitation laboratory```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 avvia il guest e consegna la console all'uid 1000, che compila exploit.c con il gcc del guest stesso e lo esegue.

Le immagini del kernel, i worktree, gli alberi rootfs e gli initramfs sono artefatti di laboratorio e non sono versionati.

Esecuzioni reali

Sicurezza della memoria, vulnerabile rispetto a corretta

docs/example-output.txt è il trascritto reale del kernel vulnerabile, inclusa la segnalazione KASAN; docs/patched-negative-output.txt è il controllo del kernel corretto; docs/e2e-results.json è il risultato leggibile dalla macchina.

La catena di chiamate riportata corrisponde esattamente alla segnalazione 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` è l'identità non privilegiata della proof of concept: la vittima
`binder_proc` viene allocata da `binder_open()`, liberata dalla workqueue
deferred di binder e letta dal walker dopo la liberazione.

Esecuzione registrata, 2026-08-16, `./verify.sh 1200 16 3` — tre guest QEMU/KVM
concorrenti per variante, 10 vCPU ciascuno, 16 thread, 1200 s per variante, **nessuna
patch del kernel su nessuno dei due lati**:

| | Vulnerabile `114a116aaa5f` | Corretto `f223d27a546c` |
| --- | --- | --- |
| Tentativi | 158.384 | 158.471 |
| Walk | 2.534.144 | 2.535.536 |
| Errori di setup | 0 | 0 |
| Accesso vulnerabile (`Unexpected outstanding_txns -1`) | 1.127 | 934 |
| **KASAN `slab-use-after-free`** | **8** | **0** |

Questo è il differenziale: lo stesso carico di lavoro, lo stesso numero di tentativi
entro lo 0,06%, e l'use-after-free solo sul kernel vulnerabile non patchato.

L'accesso vulnerabile compare su *entrambi* i kernel, ed è previsto — la fix
impedisce che il processo venga liberato all'interno della finestra, non il secondo
decremento di `outstanding_txns`. È per questo che quella riga viene usata solo come
oracolo economico e mai come differenziale.

### Escalation dei privilegi

![Esecuzione live QEMU/KVM: guest Debian 13, utente non privilegiato compila exploit.c con il gcc del guest e termina a un prompt di root](https://assets.kitploit.com/production/public/readmes/54229/da1e333d7516671ffab64e10d89604ee2a8043de1c48306abc03aaeee88d0d62/205a1253ab216ae4b62a65f761fffb9f0329bcbf9e4feb38563647f68ad2c5e0-display-v1.webp)

`docs/lpe-output.txt` è una trascrizione reale di un guest dimostrativo, e
`docs/lpe-demo.cast` è la registrazione Asciinema completa e non modificata da cui
è stata renderizzata l'animazione sopra (`asciinema play docs/lpe-demo.cast` la
riproduce integralmente). L'animazione omette la lunga parte centrale di gara di
quella registrazione — la console seriale del guest trasmette il debug di binder per
l'intera gara di ~24 minuti, che renderebbe in decine di megabyte di log scorrevole —
mantenendo il preambolo Debian e la shell di root; la riga dell'exploit stesso
`hit after 26661 attempts ... in 1437s` indica esattamente cosa è stato omesso.
Entrambe sono esecuzioni singole: un guest che non vince entro il suo budget si
spegne, e la registrazione viene semplicemente ripetuta anziché modificata per
ottenere una vittoria.

Vedi *Affidabilità* sotto per il tasso di successo misurato.

## Affidabilità

La gara è probabilistica su entrambi i fronti, quindi un'esecuzione fallita è il
comportamento previsto alcune volte, non un exploit rotto.

### Sicurezza della memoria

Tassi derivati sul kernel vulnerabile, dall'esecuzione sopra:

| Quantità | Valore |
| --- | --- |
| Tasso di tentativi | ~44 tentativi/s per guest, ~132/s su tre |
| Accesso vulnerabile | 7,1e-3 per tentativo |
| Liberazione che atterra nella finestra, dato l'accesso | 7,1e-3 |
| Report KASAN | ~1 ogni 20.000 tentativi, cioè circa uno ogni 2,5 minuti a questo ritmo |

### Escalation dei privilegi

Ogni guest è una prova indipendente: il proprio KASLR, la propria randomizzazione
della direct-map, e un guest che vince smette di gareggiare. La cifra è quindi un
**tasso di successo per avvio**, non per tentativo.

Campagna registrata, 2026-08-16 23:32 UTC, `GUESTS=6 CPUS=8 MEMORY_MB=9216 ./demo/verify.sh`
— sei guest QEMU/KVM concorrenti, `114a116aaa5f` non patchato, userland Debian 13,
budget di 60 minuti ciascuno, exploit compilato nel guest, avviato come uid 1000:

| | |
| --- | --- |
| Guest che hanno raggiunto uid 0 | **2 su 6** |
| Tempo fino a root | 623 s e 1.344 s |
| Tentativi nella gara vincente | 13.839 e 31.125 |
| Tentativi di ogni guest che non ha vinto | ~95.000 nell'intero periodo di 3.600 s |
| Tentativi totali nella campagna | 427.676 (6,8 M walk dello stack) |
| Accessi vulnerabili osservati (`Unexpected outstanding_txns -1`) | 256 |
| **Crash, oops o panic del kernel** | **0**, in 9 ore-guest |

Due cose meritano di essere lette da quella tabella.

**Praticamente ogni gara vinta è diventata root.** Il laboratorio KASAN misura la
probabilità che la liberazione atterri nella finestra, dato l'accesso vulnerabile,
al 7,1e-3. Applicato ai 256 accessi osservati qui, ciò prevede ~1,8
use-after-free nell'intera campagna — e sono state ottenute 2 root. Il reclaim,
la scoperta dell'indirizzo e la catena non sono il collo di bottiglia; la gara lo è.

**Niente è andato in crash.** Nessun guest ha subito un oops in nove ore-guest,
inclusi i quattro che non hanno mai vinto. O la catena scatta contro una pagina
spruzzata e correttamente individuata, oppure non scatta mai affatto — ed è a
questo che serve il rifiuto a maggioranza nello stage 2.

Quel rifiuto scatta nella pratica. Avviare sei guest contemporaneamente su un host
già carico ha prodotto uno che si è arreso allo stage 2 con```
[*] direct map not found; refusing to fire at an unverified address

e uscito senza gareggiare. Questo è il comportamento previsto: un avvio sprecato è l'esito corretto quando il canale di temporizzazione non riesce a raggiungere un consenso, ed è molto meglio dell'alternativa di attivare la catena su un indirizzo mai confermato.

docs/lpe-results.json contiene la forma leggibile dalla macchina, inclusi gli SHA-256 del kernel e dell'initramfs esatti utilizzati.

Perché più guest e perché un host sovraccarico

Eseguire i guest contemporaneamente non serve solo al parallelismo. Su un host conteso, KVM deprogramma le vCPU dei guest, e questo è esattamente il ritardo di cui la seconda condizione ha bisogno: il walker deve perdere la sua CPU tra il rilascio di t->lock e l'acquisizione del lock interno della vittima. Un singolo guest su un host inattivo è stato misurato a circa un ventesimo del tasso di accesso vulnerabile rispetto a tre guest concorrenti.

C'è però un limite superiore. Con otto guest da 8 vCPU ciascuno su un host a 32 thread, con circa 5 GiB di spray della mappa diretta per guest, l'host è andato in swap e tre degli otto guest non hanno fatto alcun progresso. Sei è il valore predefinito fornito.

I thread helper opzionali di preemption all'interno del guest sono stati misurati costare circa tre volte il tasso di tentativi senza migliorare il tasso di successo, e non vengono utilizzati.

Un fattore ambientale si è rivelato più importante del previsto: l'output di debug di binder stesso. Con binder.debug_mask al suo valore predefinito, il driver emette una grande quantità di traffico pr_info limitato in frequenza durante la gara, e la pressione di printk e del console-lock che ne deriva allunga esattamente la finestra di preemption di cui la seconda condizione ha bisogno. Silenziarlo con binder.debug_mask=0 per una console più pulita — la cosa ovvia da fare per una registrazione — ha ridotto sensibilmente il tasso di successo nei test: i guest hanno superato di gran lunga i conteggi di tentativi di entrambi i vincitori senza un successo. demo/run-demo.sh quindi lascia il debug di binder al suo valore predefinito, e una console silenziosa è un'opzione esplicita. Questa è una proprietà del laboratorio, non dell'exploit — ma è una buona illustrazione di quanto questa gara dipenda dal jitter di temporizzazione a livello di sistema piuttosto che da qualsiasi cosa l'exploit stesso controlli.

Sicurezza

  • Entrambi i guest sono immagini initramfs usa e getta. Non viene scritto nulla sul disco del guest o dell'host.
  • La prova di concetto apre solo /dev/binder, invia transazioni binder ed esce dai thread binder. Non installa nulla e non lascia nulla dietro di sé.
  • L'exploit scrive un file: una copia setuid-root di se stesso, usata per trasferire il privilegio dal thread vincitore al processo padre. Elimina quella copia prima di eseguire la shell, quindi nulla di setuid sopravvive all'esecuzione.
  • Una gara persa può causare panic o hang del guest; il laboratorio di memory-safety si avvia con panic=1 oops=panic così un'esecuzione termina invece di continuare su uno stato corrotto. Riavviare sempre da un avvio pulito.

Crediti

  • Autore dell'exploit: A. Ramos <[email protected]> (Twitter: @aramosf)
  • Scoperta e segnalazione della vulnerabilità: Alice Ryhl, Google
  • Fix a monte: Carlos Llamas, Google
  • Canale laterale KASLR con prefetch: derivato da KASLD, Copyright (c) 2019 Brendan Coles, licenza MIT. La variante della mappa diretta è un nuovo lavoro qui.

Ricerca di exploit pubblici

Verificato il 2026-08-16 con SearchSploit (copia locale di Exploit-DB) e ricerca web per CVE-2026-64468, binder_free_transaction e f223d27a546c. Non è stato trovato alcun exploit pubblico o prova di concetto per questa CVE; SearchSploit restituisce solo voci non correlate e più vecchie su binder Android. Questa è una verifica puntuale, non una garanzia permanente.

Scarica lo strumento
StatoAffermazione
ConfermatoLa vulnerabilità è reale, è raggiungibile da un processo non privilegiato e la correzione upstream la elimina.
DimostratoIl dereferenziamento vulnerabile di un binder_proc morente viene raggiunto naturalmente e ripetutamente sul kernel non patchato, e mai sul kernel corretto.
DimostratoL'use-after-free completo, segnalato da KASAN, sul kernel non patchato.
DimostratoRecupero del binder_proc liberato con byte controllati dall'attaccante, dirottamento del flusso di controllo del kernel ed escalation di privilegi a uid 0 da un utente non privilegiato, su x86_64.
Non affermatoQualsiasi risultato su un dispositivo o vendor specifico. Sono stati testati solo i kernel upstream elencati di seguito, su x86_64.
Non affermatoChe l'exploit fornito funzioni senza modifiche contro un kernel di distribuzione. Vedi Quali sistemi sono interessati: richiede offset per-kernel e, su ogni distribuzione generica esaminata, il dispositivo binder non è raggiungibile da un utente non privilegiato in primo luogo.
OffsetCampoCosa fa il walker
108int outstanding_txnslo decrementa
113bool is_frozenlo legge
120wait_queue_head_t freeze_waitlo attraversa se outstanding_txns == 0 && is_frozen
624spinlock_t inner_locklo acquisisce e lo rilascia
StatoCommitNote
Linea di introduzionea370003cc301Indicato dal tag upstream Fixes:
Validato come vulnerabile114a116aaa5fGenitore diretto della correzione; include la correzione adiacente per CVE-2026-64469, quindi la coppia isola CVE-2026-64468 da sola
Mainline correttof223d27a546cLa correzione sotto test
DistribuzioneKernelANDROID_BINDER_IPCDispositivoSLAB_BUCKETSRANDOM_KMALLOC_CACHESRaggiungibile senza privilegi?
Debian 13 (trixie)6.12.101mANDROID_BINDER_DEVICES="binder", BINDERFS offyoffNo — modulo non caricato; /dev/binder è 0600
Debian 12 (bookworm)6.1.0mANDROID_BINDER_DEVICES="binder", BINDERFS offn/d (pre-6.11)n/d (pre-6.6)No — stesso motivo
Ubuntu 24.04 LTS6.8.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""n/d (pre-6.11)yNo — serve root per mount -t binder
Ubuntu 22.04 LTS5.15.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""n/dn/dNo — stesso motivo
Android (AOSP / vendor)6.1, 6.6, 6.12 GKIy/dev/binder, /dev/hwbinder, /dev/vndbinder——Sì — il driver è l'IPC di piattaforma ed è accessibile a tutti
OpzioneEffetto qui
CONFIG_SLAB_BUCKETS (6.11+)Fatale per questo reclaim. Isola msg_msg nei propri bucket kmalloc, quindi la pompa non può mai finire nello slot di binder_proc. Debian 13 la imposta. Andrebbe trovata un'altra allocazione da 1 KiB non contabilizzata. Il kernel 6.6, che è quello usato dai dispositivi Android di interesse, la precede del tutto.
CONFIG_RANDOM_KMALLOC_CACHES (6.6+)Divide kmalloc-1k in diverse cache per punto di chiamata, quindi la pompa deve colpire la stessa; una tassa di 1 su 16 sul reclaim, non un muro. Ubuntu la imposta, Debian no.
Isolamento delle tabelle di pagina (nopti non usato)Fatale per la scoperta degli indirizzi. Il prefetch non può vedere il testo del kernel con PTI attivo, e l'exploit lo rileva e si ferma. PTI è compilato in ogni distribuzione sopra, ma è la CPU a decidere se è attivo: è spento su hardware non affetto da Meltdown, che è dove sono stati misurati questi risultati.
CONFIG_SLAB_FREELIST_RANDOM, ..._HARDENEDAbilitate in laboratorio, come le distribuzioni le forniscono. Nessun effetto misurabile: la pompa non prevede l'ordine della freelist, semplicemente alloca un gran numero di oggetti.
KASLR (RANDOMIZE_BASE, RANDOMIZE_MEMORY)Abilitato. Sconfitto dalle fasi di prefetch; nessun nokaslr.
Offset per kernelLa catena richiede commit_creds, tre globali legati alle credenziali e due gadget, come offset da _text. mkoffsets.sh li estrae da un vmlinux di destinazione; senza di essi l'exploit spara agli indirizzi sbagliati. Questa è una proprietà per-build di qualsiasi exploit del kernel, non una difesa.
Laboratorio di memory-safety (lab/)Laboratorio di sfruttamento (demo/)
Versione kernel7.2.0-rc1+7.2.0-rc1+
Commit vulnerabile114a116aaa5f0295376cdf12da743c5bce3b20cestesso
Commit correttof223d27a546c1e1f48d38fd67760e78f068fe8c4— (lo sfruttamento è misurato solo sul kernel vulnerabile)
Architetturax86_64 (KVM) e arm64 (TCG)x86_64 (KVM)
CompilatoreUbuntu clang 21.1.8 / LLD 21.1.8stesso
KASANon — è il rilevatoreoff — cambia il layout dello slab e renderebbe qualsiasi reclaim non rappresentativo
Hardening dello slab—SLAB_FREELIST_RANDOM, SLAB_FREELIST_HARDENED on; SLAB_BUCKETS, RANDOM_KMALLOC_CACHES off
KASLR—RANDOMIZE_BASE, RANDOMIZE_MEMORY on
Userlandinitramfs minimaleDebian GNU/Linux 13 (trixie), con il gcc della distribuzione
Identità inizialeuid 1000, gid 1000, nessuna capability, nessun namespacestesso
Riga di comando di bootconsole=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1console=ttyS0 loglevel=4 rdinit=/init — nessun nopti, nessun nokaslr, nessun mitigations=off