
Unprivilegierter Proof-of-Concept und x86_64 lokale Privilegieneskalation für einen Linux-Kernel-Binder-Use-after-Free (CVE-2026-64468), mit KASAN-Labor und verwundbarer/behobener Differenzialanalyse.
binder_free_transaction()
Dieses Repository enthält, für den durch den Upstream-Commit
f223d27a546c1e1f48d38fd67760e78f068fe8c4 behobenen Binder-Use-after-free im Linux-Kernel:
binder_chain_64468.c — ein Proof of Concept ohne Privilegien, der den
Fehler erreicht und den Kernel ihn nachweisen lässt, mit einem KASAN-Labor und
einem verwundbaren/gepatchten Differenzial (lab/, run.sh, verify.sh).exploit.c — eine eigenständige lokale Privilegienerweiterung für x86_64.
Sie kompiliert mit gcc -O2 -pthread -o exploit exploit.c, läuft als normaler
Benutzer und endet in einer Root-Shell.demo/ — ein Labor, das eine echte Debian-13-Userland auf einem
nicht gepatchten Kernel bootet, sodass der Exploit vom eigenen gcc des
Zielsystems kompiliert und auf der Maschine ausgeführt werden kann, die er
dann übernimmt.Alles läuft als normaler Benutzer (uid/gid 1000, keine Capabilities, keine Namespaces) gegen unveränderte Upstream-Kernel ohne Patches jeglicher Art.
Warnung
Dieser Code setzt bewusst Kernel-Objektlebensdauern einem Wettlauf aus und übernimmt anschließend die Kontrollflusssteuerung des Kernels. Ein verlorener Wettlauf beschädigt den Kernel-Heap-Zustand und kann die Maschine zum Absturz oder Hängen bringen. Führen Sie ihn nur in einer isolierten, wegwerfbaren VM aus, die Ihnen gehört. Führen Sie ihn nicht auf einem Host aus.
| Status | Behauptung |
|---|---|
| Bestätigt | Die Schwachstelle ist real, von einem Prozess ohne Privilegien erreichbar, und der Upstream-Fix beseitigt sie. |
| Demonstriert | Die verwundbare Dereferenzierung eines sterbenden binder_proc wird auf dem nicht gepatchten Kernel natürlich und wiederholt erreicht, auf dem gepatchten Kernel nie. |
| Demonstriert | Der vollständige, von KASAN gemeldete Use-after-free auf dem nicht gepatchten Kernel. |
| Demonstriert | Rückgewinnung des freigegebenen binder_proc mit angreiferkontrollierten Bytes, Übernahme der Kernel-Kontrollflusssteuerung und Privilegienerweiterung auf uid 0 von einem Benutzer ohne Privilegien, auf x86_64. |
| Nicht behauptet | Jegliches Ergebnis auf einem bestimmten Herstellergerät oder Android-Gerät. Nur die unten aufgeführten Upstream-Kernel wurden auf x86_64 getestet. |
| Nicht behauptet | Dass der mitgelieferte Exploit unverändert gegen einen Distributionskernel funktioniert. Siehe Welche Systeme betroffen sind: Er benötigt pro Kernel spezifische Offsets, und auf jeder untersuchten Allzweck-Distribution ist das Binder-Gerät für einen Benutzer ohne Privilegien ohnehin nicht erreichbar. |
binder_free_transaction() liest den Zielprozess aus der Transaktion unter
t->lock aus, gibt diese Sperre frei und erwirbt anschließend die innere Sperre
des Ziels:```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 */
Nichts hält `target_proc` über diese Lücke hinweg am Leben. Ein Prozess, der parallel
abgebaut wird, kann dazwischen `binder_proc_dec_tmpref() -> kfree()` erreichen, sodass
die Sperre auf freigegebenem Speicher erworben wird. Der Upstream-Fix pinnt `t->to_thread`,
während `t->lock` noch gehalten wird, wodurch der besitzende Prozess am Leben bleibt, bis
die innere Sperre verwendet und freigegeben wurde.
Die Schwachstelle wurde von **Alice Ryhl** gemeldet und von **Carlos
Llamas** behoben, beide von Google. Der
[ursprüngliche Bericht](https://lore.kernel.org/all/[email protected]/)
enthält die zugehörige KASAN-Ablaufverfolgung.
### Erreichen des verwundbaren Zugriffs
Der einzige Aufrufer, der einen *fremden* `to_proc` erreichen kann, ist
`binder_send_failed_reply()`, und dieser geht nur dann zu `t->from_parent`, wenn
`t->from` `NULL` ist:```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 wird an genau einer Stelle zugewiesen, und die Zuweisung wird einige
Zeilen zuvor durch die Bad-Transaction-Stack-Prüfung von Binder abgesichert: Ein Thread darf
eine synchrone Transaktion nur senden, während sein Stack-Top eine Transaktion ist, die er
empfängt. Daher sind für jedes Glied dieser Kette der Sender des Kindes und der
Empfänger des Elternteils derselbe Thread.
Das hat eine scharfe Konsequenz. binder_thread_release() durchläuft den Stack
des sterbenden Threads mit gehaltenem proc->inner_lock und schreibt beide```
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
in **aufeinanderfolgenden Iterationen eines einzelnen Durchlaufs**, jeweils unter der jeweiligen
`t->lock`. Ein Walker erfährt, dass `child->from == NULL` ist, erst, wenn der Durchlauf
`child->lock` freigegeben hat, und benötigt `parent->lock` für seine eigene Momentaufnahme. Die gesamte
Gelegenheit ist also die Lücke zwischen dem `spin_unlock(&child->lock)` dieses Durchlaufs und seinem
`spin_lock(&parent->lock)` — ein paar Anweisungen. Der Freigabe-Durchlauf hält eine
Spinlock und kann dort nicht unterbrochen werden; nur ein Interrupt kann ihn verzögern.
Deshalb ist der Wettlauf eng und sowohl der Proof of Concept als auch der
Exploit sind probabilistisch.
### Was der Proof of Concept aufbaut
`binder_chain_64468.c` konstruiert die kürzeste Kette, die den
verwundbaren Zugriff erreicht, sodass die Arbeit des Walkers innerhalb dieser Lücke so klein ist, wie Binder
es zulässt — zwei `t->lock`-Erwerbungen und ein `kfree()`, ohne fremde
`inner_proc_lock`, ohne `wake_up` und ohne Antwortzustellung:```
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 gibt daraufhin seinen Binder-fd auf, sodass binder_deferred_release() die
Y_1..Y_K freigibt und schließlich den binder_proc freigibt, während jeder B-Thread
gleichzeitig BINDER_THREAD_EXIT ausführt:```
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
Ein dritter Prozess ist der Binder-Context-Manager, der nur dazu dient, die
Handles auszugeben, die die anderen beiden benötigen. `exploit.c` verwendet exakt diese Konstruktion erneut.
### Zwei unabhängige Bedingungen
Ein KASAN-Bericht benötigt beides:
* **den verwundbaren Zugriff** — der Walker muss `parent->lock` innerhalb des
schmalen Fensters oben übernehmen, sodass er ein noch lebendes `to_proc` als Schnappschuss erfasst; und
* **das Free-Landing innerhalb des Fensters** — der Walker muss dann seine CPU
zwischen dem Freigeben von `t->lock` und dem Übernehmen der inneren Sperre des Opfers verlieren und von ihr fernbleiben,
bis die verzögerte Freigabe abgeschlossen ist und das `binder_proc` freigegeben wurde.