
Xiaomi 17(pudding)向けのターゲット固有Android CVE-2026-98260調査ポート
Xiaomi 17(コードネーム
pudding)の特定のカーネルビルドを対象とした Android AArch64 エクスプロイトチェーン:通常の ADB shell から出発し、順に SELinux を Permissive に設定し、本プロセスの UID/GID をゼロにし、完全な Linux capabilities を補完し、現在の起動サイクル内で root サービスを提供する。
本プロジェクトは CVE-2026-98260-popsicle-android(Xiaomi 17 Pro Max / popsicle)を参考に移植したものであり、ゼロから実装した独立した研究ではありません。
非メインスレッドの execve() はまず de_thread() を実行してスレッドのアイデンティティを交換する。この後に exec が失敗した場合(例えば exec_mmap() が SIGKILL に中断されて -EINTR を返す場合)、タスクは POSIX timer をクリーンアップする前に終了する:k_itimer は解放されるが、その timerqueue ノードは生存タスクのツリーに残ったままになる。その後、親プロセスがこのタスクを回収する際、cleanup_timerqueue() がこの解放済みノードを再び操作し、use-after-free が発生する。
エクスプロイトはこの UAF を制限付きの 8 バイトゼロ書き込みに変換し、さらに SELinux → UID/GID → 完全な capabilities の 3 段階で進行する。入門者向けの完全な解説は docs/VULNERABILITY.md を参照。
| 項目 | 実測値 |
|---|---|
| デバイス | Xiaomi 17、型番 25113PN0EC、コードネーム pudding |
| システム | Xiaomi HyperOS 4 OS4.0.5.0.XPCCNXM、Android 17 |
| カーネル | 6.12.69-android16-6-g43b5ab7e247d-abogki561813157-4k |
| ページサイズ | 4 KiB |
| 起点 | 認可済みの通常 ADB shell、UID 2000、SELinux Enforcing、bootloader ロック |
| 成功結果 | UID/GID 0、SELinux Permissive、CapPrm/CapEff/CapBnd=000001ffffffffff |
| ライフサイクル | root サービスは今回の起動後の新しい ADB shell で使用可能。再起動後は再実行が必要 |
成功後の独立検証(新しく開いた ADB shell):
uid=0(root) gid=0(root)
CapInh: 000001ffffffffff
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff
CapBnd: 000001ffffffffff
getenforce: Permissive
これは永続的な root ではありません。 boot/init_boot やその他のパーティションへの書き込みは行われず、ブートローダーはロックされたままです。再起動後、SELinux は Enforcing に戻り、root サービスは消えます。
成功率は保証しません。 実測では9回の起動のうち1回で完全な A→B→C が揃いました。3つの段階の通過条件は互いに独立しています:
| 段階 | デバイス側プログラム | 通過条件 |
|---|---|---|
| 1. SELinux | exploit-android-perf | getenforce が Permissive であり、ステータスワードの残りのフィールドが想定どおりに保たれている |
| 2. アイデンティティ | exploit-android-perf-chain | 同一プロセスの UID/GID が 0 になり、その後 exec で fullcap になる |
| 3. 完全な権限 | exploit-android-key-ptr + pc98260_rootd.c | 完全な capabilities を持ち、root サービスが新しいシェルからアクセス可能になる |
いずれかのステップがヒットしなかった場合は [PARKED] を出力して危険な状態を保持するため、必ず再起動する必要があり、今回の起動内で強制的に再試行してはなりません。
--aio 16384 は本機のチューニング値です(成功した回は3903番目の perf メタデータページでヒットしました)。
| 位置 | popsicle | pudding |
|---|---|---|
LINK_SELINUX_STATE | 0xffffffc08289aba0 | 0xffffffc0827f9ba0 |
LINK_GETPID | 0xffffffc080074eac | 0xffffffc080102e10 |
| timer アドレスリーク | alloc_posix_timer が返すプローブ | このシンボルはインライン化されているため、posix_cpu_timer_create のエントリ $arg1 に変更 |
PERF_MAX | 8192 | 16384(ページヒットの機会を増やす) |
根拠と証拠は docs/ADAPTATION.md を参照してください。
Android NDK が必要です。リポジトリ内の bin/ は NDK 26.3.11579264 / API 34 でビルドされています:
powershell -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -NdkHome "$env:LOCALAPPDATA\Android\Sdk\ndk\26.3.11579264" -ApiLevel 34
-VerifyReference は新しい成果物と bin/ の SHA-256 を1つずつ比較します。bin/ と同じツールチェーンを使用した場合のみバイト単位で同一になります。NDK バージョンを変更しても実行は可能ですが、ハッシュは異なります。
成果物:
| 成果物 | 入力ソースコード |
|---|---|
exploit-android-perf | src/exploit-android-perf.c |
exploit-android-perf-chain | src/exploit-android-perf-chain.c |
exploit-android-key-ptr | src/exploit-android-key-ptr.c + src/pc98260_rootd.c |
pc98260-su | src/pc98260_su.c |
ビルドは端末に接続せず、デバイスにも書き込みません。
USB デバッグが有効化され、認可済みのデバイスを接続し、platform-tools を PATH に追加します:
.\开始98260临时ROOT.bat
またはデバイスを明示的に指定します:
powershell -NoProfile -ExecutionPolicy Bypass -File .\run_98260.ps1 -Serial <ADB序列号> -Reboot
ホストスクリプトは機種、システム増分バージョン、完全なカーネル文字列、boot ID、および4つのバイナリのハッシュを照合します。また、各段階を起動する前に /sys/kernel/tracing/tracing_on を 0 に設定します(一部の HyperOS ビルドの miuibpf はこれを 1 に保つため、エクスプロイトチェーンは実行を拒否します)。[PARKED] が表示されるとスクリプトは停止し、再起動を促します。
成功後は次のように root を使用できます:
adb shell -t /data/local/tmp/pc98260-su
adb shell /data/local/tmp/pc98260-su -c "id"
エクスプロイトチェーンは、「対象タイマーが存在するページ」を正確に回収することを要求します。同一の対象ページが観測できない場合、またはポインタ書き込み後の検証が一致しない場合、プログラムは関連する zombie/マッピングを保持したまま停止し、ダングリングノードがまだカーネルに参照されている状態での盲目的な回収を避けます。この場合、新たな端末起動を次の試行の境界とすべきです。
本リポジトリには以下を含みません:デバイスバックアップ、boot/init_boot イメージ、パーティションイメージ、payload.bin、ファームウェアから抽出したカーネル/ELF、生の実行ログ、ADB シリアル番号または boot ID。logs/ と build/ は Git で無視されています。bin/ には 4 つの一時的な権限昇格チェーンバイナリのみが含まれます。
/data/local/tmp/pc98260-root.sock をリッスンします;今回の起動中は端末全体が完全に露出します。