
Android GKI 6.12カーネル向けのCVE-2026-43499エクスプロイト。rt_mutexのロールバックバグとpselectのスタック上書きを連鎖させ、SamsungおよびPixelデバイスでroot権限を取得します。
Android GKI 6.12 系(Samsung および Pixel デバイス)を対象とした Linux カーネルエクスプロイトで、CVE-2026-43499 を狙う: rt_mutex_start_proxy_lock() 内の欠陥のある remove_waiter() ロールバックが waiter::task ではなく current を使用してしまい、waiter の pi_blocked_on が(後にポップされる)カーネルスタック上の rt_mutex_waiter を指したままになる。これに pselect() の fd_set スタック上書きと、consumer 側の sched_setattr 駆動による偽 rb_tree 走査を組み合わせることで、決定論的なカーネル書き込みプリミティブと完全な root が得られる。
このエクスプロイトは LD_PRELOAD 共有ライブラリ(preload.so)として動作し、root 権限の su デーモンと壁紙を post-root 成果物としてインストールする。
ステータス: 活発に開発中。m1q (ZF1) ターゲットが立ち上げの焦点である。 pipei tmp_page-uname ブートストラップが現在のアクティブな経路である: 走査は 実機上でクリーンであることが証明されており(build #33: 子プロセスごとにシードされた rt_mutex 領域)、168 候補の完全なスイープが進行中である。configfs CFI 経路は m1q では行き止まりである(Rust ashmem — 注入可能な fops スロットが存在しない)が、 C-ashmem ターゲット向けの経路として残っている。ターゲットごとのオフセットは デバイスによって異なる — 信頼する前に実際のカーネルバイナリと照合して検証すること。
FUTEX_CMP_REQUEUE_PI が waiter を requeue し、requeue のデッドロック検出チェーン走査が -EDEADLK を返すと、__rt_mutex_start_proxy_lock() は remove_waiter() (rtmutex.c:1535) を介してロールバックする。このロールバックは waiter を待機ツリーから正しくデキューするが、waiter->task->pi_blocked_on ではなく requeue 呼び出し元の pi_blocked_on をクリアしてしまう。WAITER の pi_blocked_on はスタック上の rt_mutex_waiter を指したままダングリングし、futex がタイムアウトするとポップされる。
タイムアウト後、waiter のカーネルスタック領域は再利用される: core_sys_select() が 3 つの fd_set をそのスタックバッファにコピーする(ZF1 では nfds < 344 のスタック経路)。細工された fd_set ワード配列が、偽の rt_mutex_waiter / 偽タスク / 偽 rt_mutex(ポインタが制御された rb_tree ルートを持つ)を再構築する。その後 consumer スレッドが sched_setattr_tid(waiter) → rt_mutex_adjust_pi() → rb_erase_cached を呼び出し、制御された値の任意アドレス書き込みを生み出す。
このプリミティブ全体は PI チェーンのサイクルを必要とする: オーナーは f_pi_target を保持しつつ、waiter が保持する f_pi_chain でもブロックするため、チェーン走査は owner → chain → waiter → target → owner に到達して -EDEADLK で失敗する。サイクルを除去すると requeue は成功を返し、カーネルへの影響はゼロになる。
sched_blocked_reason(m1q の主経路、実機で証明済み): ブロックされた kworker の保存済みリターン PC(stack_trace_save_tsk)をリングバッファから読み取り、コンパイル時に埋め込まれた worker_thread オフセットと比較する。boot_id 経路のワードが使われる前に実行されるため、データエイリアス書き込みターゲット(data_addr() = p0 エイリアス + slide_p0_offset)は非ゼロのスライドでも正しいままである。SLIDE_LOGGERS_0_1 を boot_id sysctl データに植え付け、リークされた値が stext を再構築する。m1q では保留中: その W1 ターゲット(SLIDE_RANDOM_BOOT_ID_DATA_OFF、低物理 RAM)はページ依存の書き込み可能性を持ち、スライドがブートごとにターゲットをページ境界をまたいで移動させるため、約 6/7 の実行でフォールトする。SLIDE_FORCE_BOOTID(メカニズムテスト)で明示的に実行される場合のみで、tree_pc/tree_left はスプレーされたページにリダイレクトされる。mm_struct サイズのオブジェクトをスプレーし、futex ハッシュ衝突を利用してヒープ上の mm_struct を特定し、偽オブジェクトスプレーのベースとして使われるカーネルヒープページアドレスをリークする。waiter はクリーンアップ時に join せずデタッチされる(#26 以前のビルドをクラッシュさせたカーネルスタック OOM を回避するため)。write_iter スロットがなく、ASHMEM_SET_NAME ヒープオブジェクトは KVec であり、そのレイアウトは configfs_bin_write_iter の private_data と一致しない。m1q は代わりに kmalloc 書き込みへとピボットする: リークされた mm の order-3 ブロックを pipe_inode_info オブジェクトとして再利用し、pselect W1 書き込み(*(tree_left) = tree_pc)を使って候補の tmp_page スロット(+0x90)を UTS 名前空間ページ(init_uts_ns)で上書きする。ファンアウト書き込みが sysname オフセットにマーカー名を植え付け、uname() が "CatOS" を報告する — 静的オブジェクトに依存しない検証可能なカーネル書き込みである。C-ashmem ターゲットは configfs 経路を維持する。preload.c が埋め込みの su デーモン(/apex/com.android.virt/bin に tmpfs マウント、加えて adbd 名前空間およびローカル変種)をインストールし、壁紙を差し替える。pselect の fd_set ワードが waiter のカーネルスタック上に偽の rt_mutex_waiter を再構築し、consumer スレッドの sched_setattr_tid(waiter) が rt_mutex_adjust_pi() を介してそれを走査する。ZF1 ワードマップ(逆アセンブリ検証済み): PSELECT_WAITER_WORD_SHIFT = 0、ワード 12 = task (@+0x50)、ワード 13 = lock (@+0x58)、ワード 14 = wake_state (@+0x60 = 3)。シードされた偽 rt_mutex があれば走査はクリーンに完了する: [7] の rb_erase W1 *(tree_left)=tree_pc / W2 *(tree_pc&~3+8)=tree_left が唯一のカーネル書き込みであり、[11](setprio / dequeue_pi)と [9] の wake はどちらもスキップされる(prio 100 のローカル偽 waiter がスタックノードを top-waiter から除外し続ける)。build #19 以降のすべての走査進入完了は実機上で生存している。それ以前の決定論的クラッシュは 8 バイトのペイロード配置エラー(SKB_DATA_DELTA)であり、走査本体のフォールトではなかった。
オプションの STAGE3=1 フェーズで、ブリッジ子プロセスを fork し、その mm->pgd をステージングされた偽ページテーブル(3 レベル: PGD→L1→L2、RX ループリーフ + RW スタックリーフ)にスワップし、子プロセスにレジスタのみのアセンブリブロブを実行させる。このブロブは 2MB の物理スキャンウィンドウを通じて自身の cred をパッチする — configfs/pipe プリミティブなしの全 RAM 物理 R/W である。現在は m1q ターゲットにのみ配線されており、実機では未実行である。
src/targets/<codename>-<build>/ に 41 ターゲットがあり、それぞれ最低限 target.h(カーネルシンボルオフセット、KIMAGE_TEXT_BASE、direct-map ベース、構造体オフセット)を必要とする。Pixel ターゲット(comet、tokay、tegu、caiman、komodo、frankel、mustang、rango、stallion、blazer)は共有ソースを上書きし、Samsung ターゲット(m1q-*)はデバイス固有のロジックを追加する。
make list-projects # full list
カーネル構造体レイアウトはデバイスごとに異なる — オフセットは各ターゲットの実際のカーネルバイナリ / kallsyms と照合して検証する必要がある。
出力: build/<PROJECT>/bin/preload.so(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
ビルドは PIE の su_daemon バイナリ(src/su_daemon.c、build/embed/su_daemon_aarch64_pie にビルド)と assets/wallpaper.webp を、src/su_blob.S / src/wallpaper_blob.S を介して preload.so に埋め込む。
ビルド出力を /data/local/tmp にプッシュし、LD_PRELOAD の下でエクスプロイトを実行する。ライブの立ち上げターゲットでは tee なしで実行すること — tee は自身の stdio をバッファリングし、カーネルパニック時にログの末尾を失う(エクスプロイトの stdout はアンバッファリングなので、ファイルへ直接リダイレクトすればすべての行が保持される):
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"
成功するとデーモンは /data/local/tmp/temp_su.sock で待ち受け、su は /apex/com.android.virt/bin の下にインストールされる。
tools/run_pipei.sh はバイナリをプッシュし、実機上で sha256 検証し(古い preload.so はサイレントに読めない判定を生む — ラダーはかつてログ形式が異なる build < #22 を実行したことがある)、その後 168 候補の完全なスイープを 3x56 でチャンク化して実行するため、パニックで失われるのは最大 1 チャンク(約 10 分)である。エクスプロイトの stdout は adb を通じて直接ストリームされる(デバイス側リダイレクトなし — デバイス側の tee はパニック時に末尾を失う)。各チャンクのホスト側コピーは /tmp/m1q_pipei_chunk{1..3}_stream.log に保持される:
sh tools/run_pipei.sh
PASS = いずれかのチャンクが tmp_page uname changed + after uname='CatOS を示す。PIPEI_CHILD_REGIONS は少なくともチャンクサイズに設定する必要があり、そうでなければ子プロセスごとの rt_mutex 領域がシードされないままになり、スイープは候補 0 でパニックする。完全なカバレッジには TMP_UNAME_PIPEI_SPLIT_ORDER_PAGES=1 も必要である — これがないと解放された 8 ページの mm ブロックのうち 1 ページしかスイープされない。run_pipei_slim.sh は単一候補の診断変種であり、run_m1q_ladder.sh は走査/分離バッテリー(B16-2 オラクル、スライドメカニズム、スイープステップ)である。