Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
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.

··Feeds·Contact·Privacy·© 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
115 days agoNot yet reviewed

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

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:

root@kitploit:~
	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:

root@kitploit:~
	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

root@kitploit:~
	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:

root@kitploit:~
  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:

root@kitploit:~
  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

root@kitploit:~
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

The moment of the kfree() is not observable from userspace, and neither is the moment the walker touches the object again, so there is nothing to time against. The spray therefore runs as a pump: it allocates and frees kmalloc-1k objects continuously, from the CPU that ran the deferred release, for as long as the walkers are running.

System V messages are used for it. alloc_msg() is a plain unaccounted kmalloc of a 48-byte header plus payload, so a 976-byte message is a 1024-byte allocation; the quota is per queue rather than per uid; and msgrcv() frees synchronously. Measured throughput: ~198,000 allocations per second, zero failures.

add_key/user_key_payload was tried first and is a trap. Its payload is charged against a per-uid byte quota (kernel.keys.maxbytes, 20000 by default) which is released only when the key garbage collector destroys the key, so a tight allocate/free loop exhausts it in milliseconds: measured 27,151 successful allocations against 2,121,009 failures — 98.7% of the spray silently doing nothing, which looks exactly like a spray that never wins the slot. KEYCTL_INVALIDATE made it worse (4,775 successes), because it queues GC work.

3. What the reclaimed object has to contain

The walker touches four fields of the freed binder_proc (offsets measured with pahole on the target build):

With outstanding_txns == 1 and is_frozen == 1, the decrement reaches zero and the walker calls wake_up_interruptible_all(&proc->freeze_wait). __wake_up_common then computes curr = head.next - 24 and calls *(head.next - 8): a function pointer read from wherever head.next points, which is a value the reclaimed object supplies.

4. Two addresses, from a side channel

That pointer has to reach memory the attacker controls, at a kernel address, and the bug leaks nothing. Both addresses come from prefetch timing instead — the same channel as KASLD (Brendan Coles, MIT), whose implementation this derives from:

  • Kernel text. A prefetch of a mapped kernel address resolves in the page table walk and retires measurably faster than one of an unmapped address, even though the access never becomes architecturally visible. Scanning the 2 MiB slots of the text range shows the image as a run of fast slots; its first slot is _text.

  • The direct map. With CONFIG_RANDOMIZE_MEMORY the direct map is randomised in 1 GiB units, so it has to be located too. Unlike the text it covers all of RAM, so it is the longest contiguous run of mapped slots. Two refinements were needed to make this usable:

    • a single prefetch separates mapped from unmapped by only ~4 cycles under KVM, which does not survive the noise, so each sample times a batch of 400 prefetches (measured 311 vs 523 cycles — separable);
    • a single scan is not trustworthy on a contended host — 10/10 correct with one guest at a time, 3/5 with five guests at once — so the scan is run five times and a majority is required. Without consensus the exploit reports failure and stops rather than firing at an unverified address.

    What the run reliably starts at is not page_offset_base itself but the first slot the kernel could map with a 1 GiB page — the one covering physical 4 GiB. Below that, the PCI hole and firmware reservations force 2 MiB pages whose longer walk is not separable from unmapped here. That slot is exactly what the spray needs, so it is what is used.

Then ~60% of physical memory is filled with copies of one crafted 4 KiB page, so a fixed offset from that anchor is backed by the crafted page whatever the layout turns out to be.

The chain

root@kitploit:~
  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 and no iretq: the hijacked thread returns out of its ioctl() normally and is simply back in userspace, as root.

The forged cred, and the bug that would have wasted it

The dispatcher gadget takes its RAX from cred+0x18, and cred+0x18 is euid/egid, so immediately after the chain euid is the low half of a kernel pointer. That is cosmetic. What is not cosmetic is that prepare_creds() — which every later fork() and execve() calls — dereferences three fields with no NULL check:

root@kitploit:~
	get_group_info(new->group_info);        /* refcount_inc(&gi->usage) */
	get_uid(new->user);                     /* refcount_inc(&u->__count) */
	new->ucounts = get_ucounts(new->ucounts);

A forged cred that leaves them NULL gives uid 0 and then panics the machine on the first execve — that is, the exploit would report success and immediately destroy the box. The chain therefore points user, ucounts and group_info at the real kernel globals root_user, init_ucounts and init_groups, whose addresses come from the same _text base. With those set, the rooted thread holds CAP_SETUID over init_user_ns, so it calls setresuid(0,0,0) and the kernel installs a clean, kernel-allocated root cred over the forged one. Only then is anything else done.

Privilege is handed to the parent through a setuid-root copy of the exploit binary, which unlinks itself before exec'ing the shell, so the root shell runs in the main process on a clean terminal and nothing setuid is left behind.

Which systems are affected

uname -r is not enough. Vendor kernels routinely backport binder fixes without changing to the corresponding mainline version; inspect the source or the package changelog.

The fix is tagged Cc: stable, so stable and vendor branches receive backports.

Where the bug is, and where it is reachable

Those are different questions, and the second is the one that decides impact.

The vulnerable code is compiled in wherever CONFIG_ANDROID_BINDER_IPC is set. That includes the general-purpose distributions — but on all of them surveyed the driver is a module that is not loaded by default, and even when it is loaded, init_binder_device() registers the misc device without setting miscdev.mode, so devtmpfs creates /dev/binder as 0600 root:root. On Android it is ueventd that opens it to 0666, which is exactly why the bug matters there and mostly does not here.

Configurations read from the distributions' own shipped kernel packages:

The practical exposure on a desktop distribution is therefore indirect: anything that loads binder and opens it up — Waydroid, Anbox, an Android emulator or container runtime — reintroduces exactly the Android reachability on a machine whose kernel still has the bug.

What each hardening option costs the exploit

These do not affect the vulnerability; they affect this exploit.

Validated targets

Both kernels are stock upstream trees. Nothing is patched.

CONFIG_KASAN_GENERIC in the first laboratory is a detector, not an enabler: the race is identical without it. CONFIG_PREEMPT is a real precondition, and is what Android ships.

The architecture is not a factor for the bug — it is a lifetime error in architecture-independent C. It is very much a factor for the exploit: the gadgets, the prefetch channel and the direct-map layout are all x86_64.

Building and running

Requirements: clang, lld, make, cpio, gzip, qemu-system-x86_64, docker (only to assemble the Debian rootfs), a local clone of the Linux git tree, and gcc.

The exploit, on a system that is already vulnerable

root@kitploit:~
gcc -O2 -pthread -o exploit exploit.c
./exploit

For a kernel other than the one in demo/, extract its offsets first:

root@kitploit:~
./mkoffsets.sh /path/to/vmlinux > offsets.h
gcc -O2 -pthread -DEXPLOIT_OFFSETS='"offsets.h"' -o exploit exploit.c

Tunables, all optional, all read from the environment: 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.

The memory-safety laboratory

root@kitploit:~
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

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

root@kitploit:~
./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 boots the guest and hands the console to uid 1000, which compiles exploit.c with the guest's own gcc and runs it.

Kernel images, worktrees, rootfs trees and initramfs are laboratory artifacts and are not versioned.

Real runs

Memory safety, vulnerable versus fixed

docs/example-output.txt is the real vulnerable-kernel transcript, including the KASAN report; docs/patched-negative-output.txt is the fixed-kernel control; docs/e2e-results.json is the machine-readable result.

The reported call chain matches the upstream report exactly:

root@kitploit:~
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 is the proof of concept's own unprivileged identity: the victim binder_proc is allocated by binder_open(), freed by the binder deferred workqueue, and read by the walker after the free.

Recorded run, 2026-08-16, ./verify.sh 1200 16 3 — three concurrent QEMU/KVM guests per variant, 10 vCPUs each, 16 threads, 1200 s per variant, no kernel patches on either side:

That is the differential: the same workload, the same attempt count to within 0.06%, and the use-after-free only on the unpatched vulnerable kernel.

The vulnerable access appears on both kernels, and that is expected — the fix prevents the process from being freed inside the window, not the second decrement of outstanding_txns. It is why that line is used only as a cheap oracle and never as the differential.

Privilege escalation

Live QEMU/KVM run: Debian 13 guest, unprivileged user compiles exploit.c with the guest's own gcc and ends at a root prompt

docs/lpe-output.txt is a real transcript from a demonstration guest, and docs/lpe-demo.cast is the full, unedited Asciinema recording the animation above was rendered from (asciinema play docs/lpe-demo.cast replays it in its entirety). The animation elides the long racing middle of that recording — the guest's serial console streams binder debug for the whole ~24 minute race, which would render to tens of megabytes of scrolling log — keeping the Debian preamble and the root shell; the exploit's own hit after 26661 attempts ... in 1437s line states exactly what was elided. Both are single runs: a guest that does not win inside its budget powers off, and the recording is simply repeated rather than edited into a win.

See Reliability below for the measured success rate.

Reliability

The race is probabilistic on both counts, so a failed run is the expected behaviour some of the time, not a broken exploit.

Memory safety

Derived rates on the vulnerable kernel, from the run above:

QuantityValue
Attempt rate~44 attempts/s per guest, ~132/s across three
Vulnerable access7.1e-3 per attempt
Free landing inside the window, given the access7.1e-3
KASAN report~1 per 20,000 attempts, i.e. roughly one every 2.5 minutes at this rate

Privilege escalation

Each guest is an independent trial: its own KASLR, its own direct-map randomisation, and a guest that wins stops racing. The figure is therefore a success rate per boot, not per attempt.

Recorded campaign, 2026-08-16 23:32 UTC, GUESTS=6 CPUS=8 MEMORY_MB=9216 ./demo/verify.sh — six concurrent QEMU/KVM guests, unpatched 114a116aaa5f, Debian 13 userland, 60 minutes budget each, exploit compiled in-guest, started as uid 1000:

Two things are worth reading out of that table.

Essentially every won race became root. The KASAN laboratory measures the probability that the free lands inside the window, given the vulnerable access, at 7.1e-3. Applied to the 256 accesses observed here that predicts ~1.8 use-after-frees across the campaign — and 2 roots were obtained. The reclaim, the address discovery and the chain are not the bottleneck; the race is.

Nothing crashed. No guest took an oops in nine guest-hours, including the four that never won. Either the chain fires against a correctly located, sprayed page, or it never fires at all — which is what the majority-vote refusal in stage 2 is for.

That refusal does fire in practice. Booting six guests at once on an already loaded host produced one that gave up at stage 2 with

root@kitploit:~
[*] direct map not found; refusing to fire at an unverified address

and exited without racing. That is the intended behaviour: a wasted boot is the correct outcome when the timing channel cannot reach consensus, and it is much better than the alternative of firing the chain at an address that was never confirmed.

docs/lpe-results.json carries the machine-readable form, including the SHA-256 of the exact kernel and initramfs used.

Why several guests, and why an oversubscribed host

Running the guests concurrently is not only about parallelism. On a contended host, KVM deschedules guest vCPUs, and that is exactly the delay the second condition needs: the walker has to lose its CPU between dropping t->lock and taking the victim's inner lock. A single guest on an idle host was measured at roughly one twentieth of the vulnerable-access rate of three concurrent guests.

There is an upper bound, though. At eight guests of 8 vCPUs each on a 32-thread host, with ~5 GiB of direct-map spray per guest, the host went into swap and three of the eight guests made no progress at all. Six is the shipped default.

The optional in-guest preemption helper threads were measured to cost about three times the attempt rate without improving the hit rate, and are not used.

One environmental factor turned out to matter more than expected: binder's own debug output. With binder.debug_mask at its default the driver emits a large amount of rate-limited pr_info traffic during the race, and the printk and console-lock pressure that creates lengthens the exact preemption window the second condition needs. Silencing it with binder.debug_mask=0 for a tidier console — the obvious thing to do for a recording — measurably lowered the hit rate in testing: guests ran well past both winners' attempt counts without a hit. demo/run-demo.sh therefore leaves binder debug at its default, and a quiet console is an explicit opt-in. This is a property of the laboratory, not of the exploit — but it is a good illustration of how much this race depends on system-wide timing jitter rather than on anything the exploit itself controls.

Safety

  • Both guests are disposable initramfs images. Nothing is written to guest or host disk.
  • The proof of concept only opens /dev/binder, sends binder transactions and exits binder threads. It installs nothing and leaves nothing behind.
  • The exploit does write one file: a setuid-root copy of itself, used to hand privilege from the winning thread to the parent process. It unlinks that copy before exec'ing the shell, so nothing setuid survives the run.
  • A lost race can panic or hang the guest; the memory-safety laboratory boots with panic=1 oops=panic so a run terminates instead of continuing on corrupted state. Always restart from a clean boot.

Credits

  • Exploit author: A. Ramos <[email protected]> (Twitter: @aramosf)
  • Vulnerability discovery and report: Alice Ryhl, Google
  • Upstream fix: Carlos Llamas, Google
  • Prefetch KASLR side channel: derived from KASLD, Copyright (c) 2019 Brendan Coles, MIT licensed. The direct-map variant is new work here.

Public-exploit search

Checked on 2026-08-16 with SearchSploit (local Exploit-DB copy) and web search for CVE-2026-64468, binder_free_transaction and f223d27a546c. No public exploit or proof of concept for this CVE was found; SearchSploit returns only unrelated, older Android binder entries. This is a point-in-time check, not a permanent guarantee.

Download Tool
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.
OffsetFieldWhat the walker does
108int outstanding_txnsdecrements it
113bool is_frozenreads it
120wait_queue_head_t freeze_waitwalks it if outstanding_txns == 0 && is_frozen
624spinlock_t inner_locktakes and releases it
StateCommitNotes
Introduced lineagea370003cc301Named by the upstream Fixes: tag
Validated vulnerable114a116aaa5fDirect parent of the fix; carries the neighbouring CVE-2026-64469 fix, so the pair isolates CVE-2026-64468 alone
Corrected mainlinef223d27a546cThe fix under test
DistributionKernelANDROID_BINDER_IPCDeviceSLAB_BUCKETSRANDOM_KMALLOC_CACHESReachable unprivileged?
Debian 13 (trixie)6.12.101mANDROID_BINDER_DEVICES="binder", BINDERFS offyoffNo — module not loaded; /dev/binder is 0600
Debian 12 (bookworm)6.1.0mANDROID_BINDER_DEVICES="binder", BINDERFS offn/a (pre-6.11)n/a (pre-6.6)No — same
Ubuntu 24.04 LTS6.8.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""n/a (pre-6.11)yNo — needs root to mount -t binder
Ubuntu 22.04 LTS5.15.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""n/an/aNo — same
Android (AOSP / vendor)6.1, 6.6, 6.12 GKIy/dev/binder, /dev/hwbinder, /dev/vndbinder——Yes — the driver is the platform IPC and is world-accessible
OptionEffect here
CONFIG_SLAB_BUCKETS (6.11+)Fatal to this reclaim. It isolates msg_msg into its own kmalloc buckets, so the pump can never land in binder_proc's slot. Debian 13 sets it. Another unaccounted 1 KiB allocation would have to be found. Kernel 6.6, which is what the Android devices of interest run, predates it entirely.
CONFIG_RANDOM_KMALLOC_CACHES (6.6+)Splits kmalloc-1k into several caches by call site, so the pump has to hit the same one; a 1-in-16 tax on the reclaim, not a wall. Ubuntu sets it, Debian does not.
Page table isolation (nopti not used)Fatal to the address discovery. Prefetch cannot see kernel text with PTI active, and the exploit detects this and stops. PTI is compiled in on every distribution above, but the CPU decides whether it is active: it is off on hardware that is not Meltdown-affected, which is where these results were measured.
CONFIG_SLAB_FREELIST_RANDOM, ..._HARDENEDEnabled in the laboratory, as distributions ship them. No measurable effect: the pump does not predict freelist order, it simply allocates a great many objects.
KASLR (RANDOMIZE_BASE, RANDOMIZE_MEMORY)Enabled. Defeated by the prefetch stages; no nokaslr.
Per-kernel offsetsThe chain needs commit_creds, three cred-related globals and two gadgets, as offsets from _text. mkoffsets.sh extracts them from a target vmlinux; without them the exploit fires at the wrong addresses. This is a per-build property of any kernel exploit, not a defence.
Memory-safety laboratory (lab/)Exploitation laboratory (demo/)
Kernel version7.2.0-rc1+7.2.0-rc1+
Vulnerable commit114a116aaa5f0295376cdf12da743c5bce3b20cesame
Fixed commitf223d27a546c1e1f48d38fd67760e78f068fe8c4— (exploitation is measured on the vulnerable kernel only)
Architecturex86_64 (KVM) and arm64 (TCG)x86_64 (KVM)
CompilerUbuntu clang 21.1.8 / LLD 21.1.8same
KASANon — it is the detectoroff — it changes slab layout and would make any reclaim unrepresentative
Slab hardening—SLAB_FREELIST_RANDOM, SLAB_FREELIST_HARDENED on; SLAB_BUCKETS, RANDOM_KMALLOC_CACHES off
KASLR—RANDOMIZE_BASE, RANDOMIZE_MEMORY on
Userlandminimal initramfsDebian GNU/Linux 13 (trixie), with the distribution's own gcc
Starting identityuid 1000, gid 1000, no capabilities, no namespacessame
Boot command lineconsole=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1console=ttyS0 loglevel=4 rdinit=/init — no nopti, no nokaslr, no mitigations=off
Vulnerable 114a116aaa5fFixed f223d27a546c
Attempts158,384158,471
Walks2,534,1442,535,536
Setup failures00
Vulnerable access (Unexpected outstanding_txns -1)1,127934
KASAN slab-use-after-free80
Guests that reached uid 02 of 6
Time to root623 s and 1,344 s
Attempts at the winning race13,839 and 31,125
Attempts by each guest that did not win~95,000 over the full 3,600 s
Total attempts across the campaign427,676 (6.8 M stack walks)
Vulnerable accesses observed (Unexpected outstanding_txns -1)256
Kernel crashes, oopses or panics0, in 9 guest-hours