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-43499-S26 — Android GKI 6.12 kernel exploit for CVE-2026-43499, chaining an rt_mutex rollback bug with pselect stack overwrite to gain root on Samsung and Pixel devices. | Kitploit
Tools/GitHubGitHub/sammyenigma/cve-2026-43499-s26
Android SecurityPrivilege EscalationMemory ForensicsPersistence MechanismsExploitationReverse EngineeringShellcodePost-ExploitationMobile Security

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
Payload Development
Binary Exploitation
GitHubsammyenigma/cve-2026-43499-s26

CVE-2026-43499-S26

Android GKI 6.12 kernel exploit for CVE-2026-43499, chaining an rt_mutex rollback bug with pselect stack overwrite to gain root on Samsung and Pixel devices.

View Repository
122 days agoNot yet reviewed

CVE-2026-43499 — Android GKI 6.12 pselect / configfs kernel exploit

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. The m1q (ZF1) target is the bring-up focus. The pipei tmp_page-uname bootstrap is the current active route: the walk is proven clean on-device (build #33: per-child seeded rt_mutex regions) and the full 168-candidate sweep is in progress. The configfs CFI route is a dead end on m1q (Rust ashmem — no injectable fops slot) and remains the route for C-ashmem targets. Per-target offsets vary by device — verify against the actual kernel binary before trusting them.

Vulnerable primitive (CVE-2026-43499)

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.

Exploitation chain

  1. Slide (KASLR) — leak the kernel base. Two routes:
    • tracefs sched_blocked_reason (m1q primary, on-device proven): 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. Runs before any boot_id-route words are used so data-alias write targets (data_addr() = p0 alias + slide_p0_offset) stay correct at a nonzero slide.
    • boot_id pselect route (fallback, slide-independent): a pselect write plants SLIDE_LOGGERS_0_1 into the boot_id sysctl data via a linear-map alias; the leaked value reconstructs stext. Parked on m1q: its W1 target (SLIDE_RANDOM_BOOT_ID_DATA_OFF, low physical RAM) has page-dependent writability — the slide moves the target across page boundaries per boot, so ~6/7 runs fault. Only exercised explicitly via SLIDE_FORCE_BOOTID (mechanism test) with the tree_pc/tree_left redirected to the sprayed page.
  2. KernelSnitch — spray mm_struct-sized objects and use a futex-hash collision to locate an on the heap, leaking a kernel heap page address used as the fake-object spray base. Waiters are detached (not joined) on cleanup to avoid the kernel-stack OOM that crashed builds before #26.

Walk shape (m1q, on-device proven)

The pselect fd_set words re-materialize a fake rt_mutex_waiter on the waiter's kernel stack; a consumer thread's sched_setattr_tid(waiter) walks it via rt_mutex_adjust_pi(). ZF1 word map (disasm-verified): PSELECT_WAITER_WORD_SHIFT = 0, word 12 = task (@+0x50), word 13 = lock (@+0x58), word 14 = wake_state (@+0x60 = 3). With a seeded fake rt_mutex the walk completes cleanly: [7]'s rb_erase W1 *(tree_left)=tree_pc / W2 *(tree_pc&~3+8)=tree_left are the only kernel writes; [11] (setprio / dequeue_pi) and the [9] wake are both skipped (local fake waiter at prio 100 keeps the stack node out of top-waiter). Every walk-entered completion since build #19 survives on-device; the deterministic crashes before that were an 8-byte payload placement error (SKB_DATA_DELTA), not a walk-body fault.

Stage 3 (pgd-swap bridge, static/build only)

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.

Supported targets

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.

root@kitploit:~
make list-projects   # full list

Kernel struct layouts differ per device — offsets must be verified against the actual kernel binary / kallsyms for each target.

Build

Output: build/<PROJECT>/bin/preload.so (shared lib loaded via LD_PRELOAD).

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

Run on device

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):

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

m1q pipei sweep (current bring-up)

tools/run_pipei.sh pushes the binary, sha256-verifies it on-device (a stale preload.so produces silently unreadable verdicts — the ladder once ran a build < #22 whose log format differs), then runs the full 168-candidate sweep chunked 3x56 so a panic loses at most one chunk (~10 min). The exploit stdout streams directly through adb (no on-device redirect — a device-side tee loses the tail on panic); a host-side copy of each chunk is kept at /tmp/m1q_pipei_chunk{1..3}_stream.log:

root@kitploit:~
sh tools/run_pipei.sh

PASS = any chunk shows tmp_page uname changed + after uname='CatOS. PIPEI_CHILD_REGIONS must be set to at least the chunk size or the per-child rt_mutex regions stay unseeded and the sweep panics at candidate 0. Full coverage also requires TMP_UNAME_PIPEI_SPLIT_ORDER_PAGES=1 — without it only one page of the freed 8-page mm block is swept. run_pipei_slim.sh is the single-candidate diagnostic variant; run_m1q_ladder.sh is the walk/isolation battery (B16-2 oracle, slide mechanism, sweep steps).

Runtime configuration (environment variables)

Project layout

root@kitploit:~
exploit/
  Makefile
  assets/                     # wallpaper.webp (embedded post-root artifact)
  src/
    main.c                    # run_exploit() entry point (shared)
    preload.c                 # LD_PRELOAD constructor, su/wallpaper install
    util.c                    # addr translation, page prep, configfs helpers
    pipe.c                    # pipe-based physical read/write
    root.c                    # cred patching / task walk
    slide.c                   # KASLR slide (tracefs + boot_id routes)
    fops.c                    # pselect corruption + fops manipulation
    faketables.c              # stage-3 fake page-table builders (3-level)
    stage3.c                  # pgd-swap bridge child (no-op unless configured)
    stage3_loop.S, stage3_poll.S  # bridge blob + enter trampoline
    su_daemon.c               # root daemon (compiled as separate PIE)
    su_blob.S, wallpaper_blob.S   # embedded binaries
    offset.h                  # pulls per-target target.h via -DTARGET_CONFIG_H
    kernelsnitch/             # mm_struct futex-hash leak library
    targets/<codename>-<build>/    # per-device target.h + optional overrides
poc/poc.c                     # standalone POC (separate from the exploit)

Targets override shared sources through the Makefile pick_src mechanism: if src/targets/<PROJECT>/<file>.c exists it is used instead of the shared src/<file>.c; otherwise the shared version is used.

Notes and gotchas

  • Never remove the PI chain cycle. The write primitive depends on the requeue failing with -EDEADLK (errno 35). A missing cycle shows up as success=1 with zero kernel effect.
  • The waiter must hold the PI chain lock across pselect. Releasing it first lets the owner wake and race the fake rb_tree traversal → panic.
  • m1q: PSELECT_WAITER_WORD_SHIFT must be 0 (ZF1 stack depths verified from ELF). Other targets still default to 1 — verify per device before trusting them.
  • p0_data_alias() uses DIRECT_MAP_BASE (the linear map), which differs from P0_PAGE_OFFSET on ZF1. Data aliases for dereferenced kernel objects must use the linear map.
  • pr_error() is fatal (exit(-1)), unlike the warning/info macros.
  • Slide reconstruction uses SLIDE_LOGGERS_0_1, not SLIDE_NFULNL_LOGGER; the word is a non-randomized linear-map alias, so the boot_id route is slide-independent (use it only at slide=0 — run the tracefs leak first).

Disclaimer

This is exploit code for Android devices and is intended for security research, authorized vulnerability testing, and defense work on hardware you own or have explicit permission to test. Misuse on devices you do not own is illegal. The author is not responsible for bricked devices, bricked kernels, or voided warranties.

Download Tool
mm_struct
  • Main route — pipei tmp_page bootstrap (m1q, current focus) — the original configfs CFI route is a dead end on m1q: the Rust ashmem has no injectable static write_iter slot and the ASHMEM_SET_NAME heap object is a KVec whose layout does not line up with configfs_bin_write_iter's private_data. m1q pivots to a kmalloc write instead: reclaim the leaked mm order-3 block as pipe_inode_info objects and use the pselect W1 write (*(tree_left) = tree_pc) to overwrite a candidate's tmp_page slot (+0x90) with the UTS namespace page (init_uts_ns). A fanout write plants a marker name at the sysname offset and uname() reports "CatOS" — a verifiable kernel write with no static-object dependence. C-ashmem targets keep the configfs path.
  • Pipe physrw — forge pipe buffer pages in the sprayed kernel page for physical read/write: cred patching, SELinux disable, and direct kernel memory manipulation.
  • Root — patch the root child's cred (uid/gid/caps/SELinux SID), then 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.
  • VariableDefaultPurpose
    PSELECT_ROUTE_SHIFTcompile-timeA/B fd_set word shift for the main route (m1q)
    SLIDE_SHIFTcompile-timeA/B fd_set word shift for the slide route
    PSELECT_ROUTE_DELAY_USEC50000Consumer delay before sched_setattr (must be > 0)
    SLIDE_CONSUME_USEC, SLIDE_ENTER_DELAY_USEC, SLIDE_CONSUMER_CORE—Slide route timing/pinning
    SKIP_SLIDE0Use direct-map fallback (slide = 0)
    SLIDE_ONLY0Run only the KASLR slide and exit
    SLIDE_P0_OFFSET, SLIDE_BOOTID_OFF—Override slide p0-alias / boot_id offsets
    SLIDE_FORCE_BOOTID0Force the boot_id pselect route after the tracefs slide (mechanism test)
    KSNITCH_COLLISIONS4KernelSnitch collision count
    STAGE30Enable the pgd-swap bridge child phase (m1q)
    STAGE3_DRYRUN0Stage/verify tables then abort before the swap
    PAGE_RECLAIM_SENDS, PSELECT_SIMPLE_LAYOUT—Spray/fd-set layout tweaks
    TMP_PAGE_UNAME0Run the m1q pipei tmp_page-uname bootstrap
    TMP_UNAME_PIPEI_SWEEP, TMP_UNAME_PIPEI_ALIGN_ORDER, TMP_UNAME_PIPEI_SPLIT_ORDER_PAGES—Sweep mode; split_order sweeps all 8 pages of the freed mm block
    TMP_UNAME_PIPEI_SLOT, TMP_UNAME_PIPEI_SLOT_CANDIDATES—Sweep range (chunk start / count)
    PIPEI_CHILD_REGIONS0Per-child seeded rt_mutex regions; MUST be >= sweep chunk or unseeded regions panic at candidate 0
    PIPEI_RECLAIM_COUNT, PIPEI_DRAIN_COUNT—Reclaim/drain pipe pairs; raise on a reclaim miss
    PIPEI_SPONGE_MB3072Order-0 flood sponge held across the reclaim (build #29)
    PSELECT_W0_PRIO_OVERRIDE130Top-waiter prio; inert with seeded regions (legacy stale-tree guard, A/B only)
    PSELECT_SKB_DELTA_OVERRIDE0A/B payload intra-page offset (only if a device differs from -0xe78)
    TMP_UNAME_NAME, TMP_UNAME_HOLD_SEC"CatOS", 5Marker name / hold time after success
  • SKB_DATA_DELTA is -0xe78 (on-device measured, build #19) — the real skb frag head is 8 bytes less than the disasm-derived -0xe80; that 8-byte error caused every deterministic pselect crash from builds #6-18 (walk-entered → garbage deref). GKI 6.12 shared value; per-target util.c copies carry a SKB_DATA_DELTA env A/B knob. Do NOT stack PSELECT_SKB_DELTA_OVERRIDE on top of the baked constant.
  • The walk body is clean; placement was the bug. With the seeded payload at the modeled offsets the chain walk completes fault-free on-device. A walk-entered crash means the memory at the fd-word addresses is NOT the payload (reclaim/placement race), not a model error.
  • m1q: configfs CFI route is a dead end (Rust ashmem) — no injectable static fops slot and the ASHMEM_SET_NAME KVec envelope does not line up with configfs_bin_write_iter. Use the pipei tmp_page bootstrap. The PSELECT_WRITE_RIGHT_SPRAYED / PSELECT_PI_TOP_TASK_SELF guards in m1q target.h redirect the walk's [11] writes off the RO static fops.
  • Per-child rt_mutex regions (build #33): each sweep child redirects fd word w13 to its own seeded region (payload+0x6000 + idx*0x40) inside the reclaimed page. Shared-fake_lock sweeps panicked at candidate ~9-12 (stale kernel-stack nodes recycled), and build #32's empty regions panicked at candidate 0 ([9] owner==NULL wake on the 0x41-filled fake_task). PIPEI_CHILD_REGIONS must be set or the regions stay unseeded.
  • m1q boot_id pselect slide is parked — its W1 target is low RAM whose page writability varies per boot; tracefs is the primary and measures the slide every run.