Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-64468 — Unprivileged proof-of-concept and x86_64 local privilege escalation for a Linux kernel Binder use-after-free (CVE-2026-64468), with KASAN lab and vulnerable/fixed differential. | Kitploit
Tools/GitHubGitHub/aramosf/cve-2026-64468
Privilege EscalationExploit FrameworksVulnerability AnalysisExploitationLearning & EducationBinary Exploitation
GitHubaramosf/cve-2026-64468

CVE-2026-64468

Unprivileged proof-of-concept and x86_64 local privilege escalation for a Linux kernel Binder use-after-free (CVE-2026-64468), with KASAN lab and vulnerable/fixed differential.

View Repository
297281 month agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-64468 — Linux kernel Binder binder_free_transaction() process-lifetime use-after-free

Live QEMU/KVM run: unpatched vulnerable kernel reports the use-after-free, fixed kernel does not

This repository contains, for the Linux kernel Binder use-after-free fixed by upstream commit f223d27a546c1e1f48d38fd67760e78f068fe8c4:

  • binder_chain_64468.c — an unprivileged proof of concept that reaches the bug and lets the kernel prove it, with a KASAN laboratory and a vulnerable/fixed differential (lab/, run.sh, verify.sh).
  • exploit.c — a self-contained local privilege escalation for x86_64. It compiles with gcc -O2 -pthread -o exploit exploit.c, runs as an ordinary user, and ends in a root shell.
  • demo/ — a laboratory that boots a real Debian 13 userland on an unpatched kernel, so the exploit can be compiled by the target's own gcc and run on the machine it then takes over.

Everything runs as an ordinary user (uid/gid 1000, no capabilities, no namespaces) against stock upstream kernels with no patches of any kind.

Warning

This code deliberately races kernel object lifetimes and then hijacks kernel control flow. A lost race corrupts kernel heap state and can panic or hang the machine. Run it only in an isolated, disposable VM that you own. Do not run it on a host.

Status and scope

StateClaim
ConfirmedThe vulnerability is real, is reachable from an unprivileged process, and the upstream fix removes it.
DemonstratedThe vulnerable dereference of a dying binder_proc is reached naturally and repeatedly on the unpatched kernel, and never on the fixed kernel.
DemonstratedThe full use-after-free, reported by KASAN, on the unpatched kernel.
DemonstratedReclaim of the freed binder_proc with attacker-controlled bytes, kernel control-flow hijack, and privilege escalation to uid 0 from an unprivileged user, on x86_64.
Not claimedAny result on a specific vendor or Android device. Only the upstream kernels listed below were tested, on x86_64.
Not claimedThat the shipped exploit works unmodified against a distribution kernel. See Which systems are affected: it needs per-kernel offsets, and on every general-purpose distribution surveyed the binder device is not reachable by an unprivileged user in the first place.

Vulnerability

binder_free_transaction() reads the target process out of the transaction under t->lock, drops that lock, and then acquires the target's inner lock:

	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 */

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 carries the reference KASAN trace.

Reaching the vulnerable access

The only caller that can reach a foreign to_proc is binder_send_failed_reply(), and it only walks to t->from_parent when t->from is NULL:

	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 is assigned in exactly one place, and the assignment is guarded a few lines earlier by binder's bad transaction stack check: a thread may only send a synchronous transaction while its stack top is a transaction it is receiving. Therefore, for every link of that chain, the child's sender and the parent's receiver are the same thread.

That has a sharp consequence. binder_thread_release() walks the dying thread's stack with proc->inner_lock held, and writes both of

	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 adjacent iterations of a single walk, each under the respective t->lock. A walker learns child->from == NULL only once the walk has released child->lock, and needs parent->lock for its own snapshot. So the entire opportunity is the gap between that walk's spin_unlock(&child->lock) and its spin_lock(&parent->lock) — a couple of instructions. The release walk holds a spinlock and cannot be preempted there; only an interrupt can delay it.

This is why the race is narrow and why both the proof of concept and the exploit are probabilistic.

What the proof of concept builds

binder_chain_64468.c constructs the shortest chain that reaches the vulnerable access, so the walker's work inside that gap is as small as binder allows — two t->lock acquisitions and one kfree(), with no foreign inner_proc_lock, no wake_up and no reply delivery:

  B thread i   --e2 (sync, code 0x4442414b)-->  P thread Y_i
  P thread Y_i --e1 (sync, code 0x54414c4c)-->  B thread i   (nested target)

  B thread i   stack: [ e2 outgoing , e1 incoming (top) ]
  P thread Y_i stack: [ e2 incoming , e1 outgoing (top) ]

P then drops its binder fd, so binder_deferred_release() releases Y_1..Y_K and finally frees the binder_proc, while every B thread concurrently issues BINDER_THREAD_EXIT:

  binder_thread_release(B_i)     nulls e1->to_proc (own proc) and e2->from
  binder_send_failed_reply(e1)   e1->from == NULL once Y_i was released
    -> binder_free_transaction(e1)  target_proc already NULL, kfree(e1)
    -> binder_free_transaction(e2)  target_proc == P   <-- vulnerable access

A third process is the binder context manager, used only to hand out the handles the other two need. exploit.c reuses this exact construction.

Two independent conditions

A KASAN report needs both of:

  • the vulnerable access — the walker must take parent->lock inside the narrow window above, so that it snapshots a still-live to_proc; and
  • the free landing inside the window — the walker must then lose its CPU between dropping t->lock and taking the victim's inner lock, and stay off it until the deferred release has finished and freed the binder_proc.

The first condition has its own oracle that does not need KASAN: binder prints

binder: binder_free_proc: Unexpected outstanding_txns -1

whenever it happens, because the walker and binder_thread_release() then both decrement the same counter for one transaction. Note this is not a vulnerable/fixed differential by itself — the fix stops the free, not the second decrement — so it appears on both kernels. It is used here only to show the vulnerable code path being exercised.

From use-after-free to root

The proof of concept stops at a 4-byte decrement of freed memory. Turning that into uid 0 needs four things, and none of them come from the bug itself: the bug leaks nothing.

1. A shared cache

struct binder_proc is 648 bytes and is allocated with a plain GFP_KERNEL kzalloc — not __GFP_ACCOUNT. It therefore lands in kmalloc-1k, together with every other unaccounted allocation of that size, and is not isolated behind kmalloc-cg-*. That single fact is what makes the object reclaimable at all.

2. A reclaim pump that keeps up

Download Tool