
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.
binder_free_transaction() process-lifetime use-after-free
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.
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.
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.
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.
A KASAN report needs both of:
parent->lock inside the
narrow window above, so that it snapshots a still-live to_proc; andt->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.
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.
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.
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.
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.
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:
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.
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 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:
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.
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.
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.
These do not affect the vulnerability; they affect this exploit.
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.
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.
gcc -O2 -pthread -o exploit exploit.c
./exploit
For a kernel other than the one in demo/, extract its offsets first:
./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.
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.
./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.
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:
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.

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.
The race is probabilistic on both counts, so a failed run is the expected behaviour some of the time, not a broken exploit.
Derived rates on the vulnerable kernel, from the run above:
| Quantity | Value |
|---|---|
| Attempt rate | ~44 attempts/s per guest, ~132/s across three |
| Vulnerable access | 7.1e-3 per attempt |
| Free landing inside the window, given the access | 7.1e-3 |
| KASAN report | ~1 per 20,000 attempts, i.e. roughly one every 2.5 minutes at this rate |
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
[*] 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.
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.
/dev/binder, sends binder transactions and
exits binder threads. It installs nothing and leaves nothing behind.panic=1 oops=panic so a run terminates instead of continuing on
corrupted state. Always restart from a clean boot.<[email protected]> (Twitter:
@aramosf)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.
| State | Claim |
|---|
| Confirmed | The vulnerability is real, is reachable from an unprivileged process, and the upstream fix removes it. |
| Demonstrated | The vulnerable dereference of a dying binder_proc is reached naturally and repeatedly on the unpatched kernel, and never on the fixed kernel. |
| Demonstrated | The full use-after-free, reported by KASAN, on the unpatched kernel. |
| Demonstrated | Reclaim 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 claimed | Any result on a specific vendor or Android device. Only the upstream kernels listed below were tested, on x86_64. |
| Not claimed | That 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. |
| Offset | Field | What the walker does |
|---|
| 108 | int outstanding_txns | decrements it |
| 113 | bool is_frozen | reads it |
| 120 | wait_queue_head_t freeze_wait | walks it if outstanding_txns == 0 && is_frozen |
| 624 | spinlock_t inner_lock | takes and releases it |
| State | Commit | Notes |
|---|
| Introduced lineage | a370003cc301 | Named by the upstream Fixes: tag |
| Validated vulnerable | 114a116aaa5f | Direct parent of the fix; carries the neighbouring CVE-2026-64469 fix, so the pair isolates CVE-2026-64468 alone |
| Corrected mainline | f223d27a546c | The fix under test |
| Distribution | Kernel | ANDROID_BINDER_IPC | Device | SLAB_BUCKETS | RANDOM_KMALLOC_CACHES | Reachable unprivileged? |
|---|
| Debian 13 (trixie) | 6.12.101 | m | ANDROID_BINDER_DEVICES="binder", BINDERFS off | y | off | No — module not loaded; /dev/binder is 0600 |
| Debian 12 (bookworm) | 6.1.0 | m | ANDROID_BINDER_DEVICES="binder", BINDERFS off | n/a (pre-6.11) | n/a (pre-6.6) | No — same |
| Ubuntu 24.04 LTS | 6.8.0 | m | BINDERFS=m, ANDROID_BINDER_DEVICES="" | n/a (pre-6.11) | y | No — needs root to mount -t binder |
| Ubuntu 22.04 LTS | 5.15.0 | m | BINDERFS=m, ANDROID_BINDER_DEVICES="" | n/a | n/a | No — same |
| Android (AOSP / vendor) | 6.1, 6.6, 6.12 GKI | y | /dev/binder, /dev/hwbinder, /dev/vndbinder | — | — | Yes — the driver is the platform IPC and is world-accessible |
| Option | Effect 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, ..._HARDENED | Enabled 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 offsets | The 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 version | 7.2.0-rc1+ | 7.2.0-rc1+ |
| Vulnerable commit | 114a116aaa5f0295376cdf12da743c5bce3b20ce | same |
| Fixed commit | f223d27a546c1e1f48d38fd67760e78f068fe8c4 | — (exploitation is measured on the vulnerable kernel only) |
| Architecture | x86_64 (KVM) and arm64 (TCG) | x86_64 (KVM) |
| Compiler | Ubuntu clang 21.1.8 / LLD 21.1.8 | same |
| KASAN | on — it is the detector | off — 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 |
| Userland | minimal initramfs | Debian GNU/Linux 13 (trixie), with the distribution's own gcc |
| Starting identity | uid 1000, gid 1000, no capabilities, no namespaces | same |
| Boot command line | console=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1 | console=ttyS0 loglevel=4 rdinit=/init — no nopti, no nokaslr, no mitigations=off |
Vulnerable 114a116aaa5f | Fixed f223d27a546c |
|---|
| Attempts | 158,384 | 158,471 |
| Walks | 2,534,144 | 2,535,536 |
| Setup failures | 0 | 0 |
Vulnerable access (Unexpected outstanding_txns -1) | 1,127 | 934 |
KASAN slab-use-after-free | 8 | 0 |
| Guests that reached uid 0 | 2 of 6 |
| Time to root | 623 s and 1,344 s |
| Attempts at the winning race | 13,839 and 31,125 |
| Attempts by each guest that did not win | ~95,000 over the full 3,600 s |
| Total attempts across the campaign | 427,676 (6.8 M stack walks) |
Vulnerable accesses observed (Unexpected outstanding_txns -1) | 256 |
| Kernel crashes, oopses or panics | 0, in 9 guest-hours |