
Exploits CVE-2026-43499 on Android GKI 6.12 devices: deterministic arbitrary kernel read/write, KASLR bypass, and full root via LD_PRELOAD payload.
A Linux kernel exploit for the Android GKI 6.12 line (Samsung and Pixel
devices) targeting CVE-2026-43499: a buggy remove_waiter() rollback in
rt_mutex_start_proxy_lock() that uses current instead of waiter::task,
leaving a waiter's pi_blocked_on pointing at its (later-popped) kernel stack
rt_mutex_waiter. Combined with a pselect() fd_set stack overwrite and a
consumer sched_setattr-driven fake rb_tree walk, it yields a deterministic
kernel write primitive and full root.
The exploit runs as an LD_PRELOAD shared library (preload.so) and installs
a root su daemon plus wallpaper as post-root artifacts.
Status: active development. Per-target stack-depth offsets (
PSELECT_*word shifts) and slide behavior vary by device and are still being verified on-device. The m1q (ZF1) target is the current bring-up focus.
When FUTEX_CMP_REQUEUE_PI requeues a waiter and the requeue's
deadlock-detection chain walk returns -EDEADLK, __rt_mutex_start_proxy_lock()
rolls back via remove_waiter() (rtmutex.c:1535). The rollback correctly
dequeues the waiter from the wait tree but clears the requeue caller's
pi_blocked_on instead of waiter->task->pi_blocked_on. The WAITER's
pi_blocked_on stays dangling at its stack rt_mutex_waiter, which is popped
once the futex times out.
After the timeout, the waiter's kernel stack region is reused: core_sys_select()
copies the three fd_sets into that stack buffer (the nfds < 344 stack path on
ZF1). A crafted fd_set word array re-materializes a fake rt_mutex_waiter /
fake task / fake rt_mutex (with an rb_tree root whose pointers are controlled).
A consumer thread then calls sched_setattr_tid(waiter) →
rt_mutex_adjust_pi() → rb_erase_cached, which produces an arbitrary
address write of a controlled value.
The whole primitive requires the PI chain cycle: the owner holds f_pi_target
and also blocks on f_pi_chain (held by the waiter), so the chain walk hits
owner → chain → waiter → target → owner and fails with -EDEADLK. Removing the
cycle makes the requeue return success with zero kernel effect.
sched_blocked_reason (m1q primary): a blocked kworker's
saved return PC (stack_trace_save_tsk) is read from the ring buffer and
compared against the compiled-in worker_thread offset.SLIDE_LOGGERS_0_1 into the boot_id sysctl data via a linear-map
alias; the leaked value reconstructs stext.mm_struct-sized objects and use a futex-hash
collision to locate an mm_struct on the heap, leaking a kernel heap page
address used as the fake-object spray base.configfs_bin_write_iter
into the write_iter slot (+0x30) of the static .data ashmem
file_operations. Opening ashmem then yields arbitrary kernel read/write
through the configfs binary-fs path.preload.c installs the embedded su daemon (tmpfs-mounted into
/apex/com.android.virt/bin, plus adbd-namespace and local variants) and
swaps the wallpaper.Optional STAGE3=1 phase that forks a bridge child, swaps its mm->pgd
to a staged fake page table (3-level: PGD→L1→L2, RX loop leaf + RW stack leaf),
and lets the child run a register-only assembly blob that patches its own cred
through a 2MB physical scan window — all-RAM phys R/W without the configfs/pipe
primitives. Currently wired only into the m1q target and has not been run
on-device.
41 targets in src/targets/<codename>-<build>/, each requiring at minimum a
target.h (kernel symbol offsets, KIMAGE_TEXT_BASE, direct-map base, struct
offsets). Pixel targets (comet, tokay, tegu, caiman, komodo,
frankel, mustang, rango, stallion, blazer) override shared sources;
Samsung targets (m1q-*) add device-specific logic.
make list-projects # full list
Kernel struct layouts differ per device — offsets must be verified against the actual kernel binary / kallsyms for each target.
Output: build/<PROJECT>/bin/preload.so (shared lib loaded via LD_PRELOAD).
# Default project
CC=clang make
# Specific device target (default: blazer-CP2A.260605.012)
CC=clang make PROJECT=m1q-BP4A.251205.006
# Without CC=clang: uses NDK if found ($NDK_ROOT / $ANDROID_NDK_HOME /
# $ANDROID_NDK_ROOT), otherwise host clang + Android sysroot
make PROJECT=m1q-BP4A.251205.006
# Show build configuration
make info
# Clean
make clean
The build embeds a PIE su_daemon binary (src/su_daemon.c, built to
build/embed/su_daemon_aarch64_pie) and assets/wallpaper.webp into
preload.so via src/su_blob.S / src/wallpaper_blob.S.
Push the build outputs to /data/local/tmp, then run the exploit under
LD_PRELOAD. Run WITHOUT tee on a live bring-up target — tee buffers its own
stdio and loses the tail of the log on a kernel panic (exploit stdout is
unbuffered, so redirecting straight to a file preserves every line):
adb push build/m1q-BP4A.251205.006/bin/preload.so /data/local/tmp/preload.so
adb push build/embed/su_daemon_aarch64_pie /data/local/tmp/su_daemon_aarch64_pie
adb shell "chmod 755 /data/local/tmp/preload.so /data/local/tmp/su_daemon_aarch64_pie"
adb shell "LD_PRELOAD=/data/local/tmp/preload.so \
/data/local/tmp/su_daemon_aarch64_pie \
> /data/local/tmp/output.log 2>&1"
On success the daemon listens on /data/local/tmp/temp_su.sock and su is
installed under /apex/com.android.virt/bin.
| Variable | Default | Purpose |
|---|---|---|
PSELECT_ROUTE_SHIFT | compile-time | A/B fd_set word shift for the main route (m1q) |
SLIDE_SHIFT | compile-time | A/B fd_set word shift for the slide route |
PSELECT_ROUTE_DELAY_USEC | 50000 | Consumer delay before sched_setattr (must be > 0) |
SLIDE_CONSUME_USEC, SLIDE_ENTER_DELAY_USEC, SLIDE_CONSUMER_CORE | — | Slide route timing/pinning |
SKIP_SLIDE | 0 | Use direct-map fallback (slide = 0) |
SLIDE_ONLY | 0 | Run only the KASLR slide and exit |
SLIDE_P0_OFFSET, SLIDE_BOOTID_OFF | — | Override slide p0-alias / boot_id offsets |
KSNITCH_COLLISIONS | 4 | KernelSnitch collision count |
STAGE3 | 0 | Enable the pgd-swap bridge child phase (m1q) |
STAGE3_DRYRUN | 0 | Stage/verify tables then abort before the swap |
PAGE_RECLAIM_SENDS, PSELECT_SIMPLE_LAYOUT | — | Spray/fd-set layout tweaks |
TMP_PAGE_* / TMP_UNAME_* | — | tmp_page experiment route (m1q) |