
権限のない概念実証と、LinuxカーネルBinderのuse-after-free(CVE-2026-64468)に対するx86_64ローカル権限昇格。KASANラボと、脆弱/修正版の差分比較を備えています。
binder_free_transaction() のプロセスライフタイムuse-after-free
このリポジトリには、上流コミット
f223d27a546c1e1f48d38fd67760e78f068fe8c4
によって修正されたLinuxカーネルBinderのuse-after-freeに関する以下の内容が含まれています:
binder_chain_64468.c — バグに到達し、カーネルにそれを証明させる非特権の概念実証。KASANラボと、脆弱/修正済みの差分検証(lab/、run.sh、verify.sh)を備えています。exploit.c — x86_64向けの自己完結型ローカル権限昇格。gcc -O2 -pthread -o exploit exploit.c でコンパイルし、通常ユーザーとして実行すると、rootシェルで終了します。demo/ — パッチ未適用のカーネル上で実際のDebian 13ユーザーランドを起動するラボ。エクスプロイトを標的自身のgccでコンパイルし、乗っ取る対象のマシン上で実行できます。すべては、いかなる種類のパッチも適用されていないストックの上流カーネルに対して、通常ユーザー(uid/gid 1000、ケーパビリティなし、名前空間なし)として実行されます。
警告
このコードは意図的にカーネルオブジェクトのライフタイムを競合させ、その後カーネルの制御フローを乗っ取ります。競合に負けるとカーネルのヒープ状態が破損し、マシンがパニックまたはハングする可能性があります。所有する隔離された使い捨てVMでのみ実行してください。ホスト上では実行しないでください。
binder_free_transaction() は、t->lock の下でトランザクションから対象プロセスを読み出し、そのロックを解放した後、対象の内部ロックを取得します:```c
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](https://lore.kernel.org/all/[email protected]/)
carries the reference KASAN trace.
### Reaching the vulnerable access
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_proc` を生かし続けるものは何もない。並行して破棄
されているプロセスは、その間に `binder_proc_dec_tmpref() -> kfree()` に到達
できるため、解放済みメモリ上でロックが取得されることになる。上流の修正では、
`t->lock` が保持されている間 `t->to_thread` をピン留めし、内側のロックが使用
されて解放されるまで所有プロセスを生かし続ける。
この脆弱性は、Google の **Alice Ryhl** によって報告され、同じく Google の
**Carlos Llamas** によって修正された。[元のレポート](https://lore.kernel.org/all/[email protected]/)
には、参照となる KASAN トレースが記載されている。
### 脆弱なアクセスに到達する方法
*外部* の `to_proc` に到達できる唯一の呼び出し元は
`binder_send_failed_reply()` であり、`t->from` が `NULL` の場合にのみ
`t->from_parent` へと辿る:```c
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 は正確に 1 か所でのみ代入され、その代入は数行前にバインダーの 不正なトランザクションスタック チェックによって保護されています。つまり、スレッドは、そのスタックの先頭が自身が 受信している トランザクションである場合にのみ、同期トランザクションを送信できます。したがって、そのチェーンのすべてのリンクについて、子の送信者と親の受信者は 同じスレッド です。
これには重大な結果があります。binder_thread_release() は、終了するスレッドのスタックを proc->inner_lock を保持したまま走査し、以下の両方を書き込みます。```
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
`binder_chain_64468.c`は、脆弱なアクセスに到達する最短のチェーンを構築するため、そのギャップ内でのウォーカーの作業はbinderが許す限り小さくなります — 2回の`t->lock`取得と1回の`kfree()`で、外部の`inner_proc_lock`も`wake_up`も応答配信もありません:
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.
### What the proof of concept builds
`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 がそのバインダー fd をドロップすると、binder_deferred_release() が
Y_1..Y_K を解放し、最終的に binder_proc を解放する一方、すべての B スレッドが
同時に 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
第三のプロセスはバインダーコンテキストマネージャであり、他の2つが必要とするハンドルを配布するためだけに使用される。`exploit.c`はこの正確な構造を再利用する。
### 2つの独立した条件
KASANレポートには次の両方が必要である:
* **脆弱なアクセス** — ウォーカーが上記の狭いウィンドウ内で`parent->lock`を取得し、まだ生存している`to_proc`のスナップショットを取得すること;および
* **ウィンドウ内での解放の着地** — ウォーカーが`t->lock`を解放してから被害者の内部ロックを取得するまでの間にCPUを失い、遅延解放が完了して`binder_proc`が解放されるまでCPUに戻らないこと。
最初の条件にはKASANを必要としない独自のオラクルがある:バインダーは次のように出力する。```
binder: binder_free_proc: Unexpected outstanding_txns -1
それが発生するたびに、ウォーカーとbinder_thread_release()の両方が1つのトランザクションに対して同じカウンタをデクリメントするためです。これはそれ自体では脆弱性/修正済みの差分ではないことに注意してください — 修正は解放を止めるものであり、2回目のデクリメントを止めるものではないため、両方のカーネルに現れます。ここでは、脆弱なコードパスが実行されていることを示すためだけに使用されています。
概念実証は、解放されたメモリへの4バイトのデクリメントで停止します。それをuid 0にするには4つの要素が必要であり、そのどれもバグ自体からは得られません。バグは何も漏洩させないからです。
struct binder_procは648バイトで、通常のGFP_KERNELのkzalloc — __GFP_ACCOUNTではない — で割り当てられます。そのため、同じサイズの他のすべての非アカウント割り当てとともにkmalloc-1kに入り、kmalloc-cg-*の背後に隔離されません。この単一の事実が、オブジェクトをそもそも再利用可能にしているのです。
kfree()の瞬間はユーザー空間からは観測できず、ウォーカーが再びオブジェクトに触れる瞬間も同様です。したがって、タイミングを合わせる対象はありません。そのため、スプレーはポンプとして実行されます。つまり、遅延解放を実行したCPU上で、ウォーカーが実行されている間、kmalloc-1kオブジェクトの割り当てと解放を継続的に行います。
これにはSystem Vメッセージが使用されます。alloc_msg()は、48バイトのヘッダーとペイロードからなる通常の非アカウントkmallocであるため、976バイトのメッセージは1024バイトの割り当てになります。クォータはuidごとではなくキューごとであり、msgrcv()は同期的に解放します。測定されたスループット: 毎秒約198,000回の割り当て、失敗ゼロ。
add_key/user_key_payloadが最初に試されましたが、これは罠です。そのペイロードはuidごとのバイトクォータ(kernel.keys.maxbytes、デフォルト20000)に対して課金され、これはキーガベージコレクタがキーを破棄したときにのみ解放されます。そのため、タイトな割り当て/解放ループはミリ秒単位でこれを枯渇させます。測定結果: 2,121,009回の失敗に対して27,151回の成功した割り当て — スプレーの98.7%が静かに何もしておらず、これはスロットを獲得できないスプレーとまったく同じように見えます。KEYCTL_INVALIDATEは状況を悪化させました(4,775回の成功)。GC作業をキューに入れるためです。
ウォーカーは解放されたbinder_procの4つのフィールドに触れます(オフセットはターゲットビルドでpaholeを使用して測定):
outstanding_txns == 1かつis_frozen == 1の場合、デクリメントはゼロに達し、ウォーカーはwake_up_interruptible_all(&proc->freeze_wait)を呼び出します。__wake_up_commonはその後curr = head.next - 24を計算し、*(head.next - 8)を呼び出します: head.nextが指す場所から読み取られた関数ポインタであり、これは再利用されたオブジェクトが供給する値です。
そのポインタは、攻撃者が制御するメモリにカーネルアドレスで到達しなければならず、バグは何も漏洩させません。両方のアドレスは代わりにプリフェッチタイミングから得られます — KASLD(Brendan Coles、MIT)と同じチャネルであり、その実装はこれに由来します:
カーネルテキスト。 マップされたカーネルアドレスのプリフェッチはページテーブルウォークで解決され、アクセスがアーキテクチャ的に可視になることはなくても、マップされていないアドレスのものよりも測定可能なほど速く完了します。テキスト範囲の2 MiBスロットをスキャンすると、イメージが高速スロットの連続として表示されます。その最初のスロットは_textです。
ダイレクトマップ。 CONFIG_RANDOMIZE_MEMORYを使用すると、ダイレクトマップは1 GiB単位でランダム化されるため、これも特定する必要があります。テキストとは異なり、RAM全体をカバーするため、マップされたスロットの最長の連続実行です。これを実用的にするには2つの改良が必要でした:
実行が確実に開始するのはpage_offset_base自体ではなく、カーネルが1 GiBページでマップできる最初のスロット — 物理4 GiBをカバーするスロットです。その下では、PCIホールとファームウェア予約により2 MiBページが強制され、そのより長いウォークはここでは未マップと分離できません。そのスロットはスプレーが必要とするものと正確に一致するため、それが使用されます。
その後、物理メモリの約60%が、1つの巧妙に細工された4 KiBページのコピーで満たされ、そのアンカーからの固定オフセットは、レイアウトがどうなろうと、細工されたページによってバックアップされます。
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 forged cred, and the bug that would have wasted it
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:```c
get_group_info(new->group_info); /* refcount_inc(&gi->usage) */
get_uid(new->user); /* refcount_inc(&u->__count) */
new->ucounts = get_ucounts(new->ucounts);
偽造された資格情報をNULLにするとuid 0が得られるが、最初のexecveでマシンがパニックする——つまり、エクスプロイトは成功を報告した直後にマシンを破壊することになる。そこでチェーンはuser、ucounts、group_infoを実際のカーネルグローバル変数であるroot_user、init_ucounts、init_groupsに向け、それらのアドレスは同じ_textベースから取得される。これらが設定されると、ルート化されたスレッドはinit_user_ns上でCAP_SETUIDを保持するため、setresuid(0,0,0)を呼び出し、カーネルは偽造された資格情報の上にクリーンでカーネル割り当てのルート資格情報をインストールする。その後になって初めて他の処理が行われる。
特権は、setuid-rootのエクスプロイトバイナリのコピーを通じて親プロセスに渡され、そのバイナリはシェルをexecする前に自身をunlinkする。これにより、ルートシェルはクリーンなターミナル上のメインプロセスで実行され、setuidの残骸は何も残らない。
uname -rだけでは不十分である。ベンダーカーネルは、対応するメインライン版に変更することなくbinder修正をバックポートすることが日常的に行われている。ソースまたはパッケージのチェンジログを調査すること。
この修正にはCc: stableタグが付いているため、stableおよびベンダーブランチにバックポートが配布される。
これらは異なる問題であり、影響を決定するのは後者である。
脆弱なコードはCONFIG_ANDROID_BINDER_IPCが設定されている場所ならどこでもコンパイルされる。これには汎用ディストリビューションも含まれる——しかし調査したすべてのディストリビューションで、ドライバはデフォルトではロードされないモジュールであり、ロードされた場合でもinit_binder_device()はmiscdev.modeを設定せずにmiscデバイスを登録するため、devtmpfsは/dev/binderを0600 root:rootとして作成する。Androidではueventdがこれを0666にオープンする。これこそが、このバグがAndroidで重要であり、ここではほとんど重要でない理由である。
ディストリビューション自身が配布するカーネルパッケージから読み取った構成:
したがって、デスクトップディストリビューションでの実際の露出は間接的である: binderをロードしてオープンするもの——Waydroid、Anbox、Androidエミュレータまたはコンテナランタイム——は、カーネルにまだバグがあるマシン上で、Androidとまったく同じ到達可能性を再導入する。
これらは脆弱性には影響しない。このエクスプロイトに影響する。
両カーネルともストックの上流ツリーである。何もパッチされていない。
最初の実験室のCONFIG_KASAN_GENERICは検出器であり、有効化手段ではない。レースはこれなしでも同一である。CONFIG_PREEMPTは実際の前提条件であり、Androidが配布するものである。
アーキテクチャはバグにとっては要因ではない——これはアーキテクチャ非依存のCにおけるライフタイムエラーである。エクスプロイトにとっては非常に大きな要因である: ガジェット、prefetchチャネル、ダイレクトマップレイアウトはすべてx86_64固有である。
要件: clang、lld、make、cpio、gzip、qemu-system-x86_64、docker (Debian rootfsの組み立てのみ)、Linux gitツリーのローカルクローン、およびgcc。
gcc -O2 -pthread -o exploit exploit.c ./exploit
カーネルが `demo/` 内のもの以外の場合、まずそのオフセットを抽出します:```sh
./mkoffsets.sh /path/to/vmlinux > offsets.h
gcc -O2 -pthread -DEXPLOIT_OFFSETS='"offsets.h"' -o exploit exploit.c
チューナブル(すべて任意、すべて環境変数から読み取ります):
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.
### The exploitation laboratory```sh
./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 はゲストを起動し、コンソールを uid 1000 に渡します。これにより、ゲスト自身の gcc で exploit.c をコンパイルして実行します。
カーネルイメージ、ワークツリー、rootfs ツリー、initramfs は実験室の成果物であり、バージョン管理の対象外です。
docs/example-output.txt は実際の脆弱カーネルのトランスクリプトで、KASAN レポートを含みます。docs/patched-negative-output.txt は修正カーネルの対照実験です。docs/e2e-results.json は機械可読な結果です。
報告された呼び出しチェーンは、上流のレポートと正確に一致します。``` 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` は概念実証自身の非特権IDであり、被害者である。
`binder_proc` は `binder_open()` によって割り当てられ、binder の遅延
ワークキューによって解放され、解放後にウォーカーによって読み取られる。
記録された実行、2026-08-16、`./verify.sh 1200 16 3` — 各バリアントにつき3つの同時 QEMU/KVM
ゲスト、各10 vCPU、16スレッド、各バリアントにつき1200秒、**どちらの側にもカーネルパッチなし**:
| | 脆弱な `114a116aaa5f` | 修正済み `f223d27a546c` |
| --- | --- | --- |
| 試行回数 | 158,384 | 158,471 |
| ウォーク数 | 2,534,144 | 2,535,536 |
| セットアップ失敗 | 0 | 0 |
| 脆弱なアクセス (`Unexpected outstanding_txns -1`) | 1,127 | 934 |
| **KASAN `slab-use-after-free`** | **8** | **0** |
これが差分である: 同じワークロード、試行回数は0.06%以内の差、
そして use-after-free はパッチ未適用の脆弱なカーネルでのみ発生する。
脆弱なアクセスは*両方の*カーネルに現れるが、これは想定通りである — 修正は
ウィンドウ内でプロセスが解放されるのを防ぐものであり、`outstanding_txns` の
2回目のデクリメントを防ぐものではない。そのため、この行は安価な
オラクルとしてのみ使用され、差分としては決して使用されない。
### 権限昇格

`docs/lpe-output.txt` はデモンストレーションゲストからの実際のトランスクリプトであり、
`docs/lpe-demo.cast` は上記のアニメーションの元となった完全かつ無編集の Asciinema 録画である
(`asciinema play docs/lpe-demo.cast` で全体を再生できる)。アニメーションはその録画の
長いレースの中間部分を省略している — ゲストのシリアルコンソールは約24分間のレース全体にわたって
binder デバッグをストリーミングし、スクロールログが数十メガバイトになるため — Debian の
プリアンブルと root シェルは保持している。エクスプロイト自身の `hit after 26661 attempts ... in 1437s`
という行が、省略された内容を正確に示している。どちらも単一実行である: 予算内で勝利しない
ゲストは電源が切れ、録画は勝利に編集されるのではなく単に繰り返される。
測定された成功率については、下記の *信頼性* を参照。
## 信頼性
レースは両方の点で確率的であるため、失敗した実行はエクスプロイトの欠陥ではなく、
一部の時間で期待される挙動である。
### メモリ安全性
上記の実行から導出された、脆弱なカーネルでの率:
| 量 | 値 |
| --- | --- |
| 試行レート | ゲストあたり約44試行/秒、3つで約132/秒 |
| 脆弱なアクセス | 試行あたり 7.1e-3 |
| アクセスが与えられた場合の、ウィンドウ内での解放の着地 | 7.1e-3 |
| KASAN レポート | 約20,000試行に1回、すなわちこのレートでは約2.5分に1回 |
### 権限昇格
各ゲストは独立した試行である: 独自の KASLR、独自のダイレクトマップ
ランダム化を持ち、勝利したゲストはレースを停止する。したがって、この数値は
試行あたりではなく、**ブートあたりの成功率** である。
記録されたキャンペーン、2026-08-16 23:32 UTC、`GUESTS=6 CPUS=8 MEMORY_MB=9216 ./demo/verify.sh`
— 6つの同時 QEMU/KVM ゲスト、パッチ未適用の `114a116aaa5f`、Debian 13 ユーザーランド、
各60分の予算、エクスプロイトはゲスト内でコンパイルされ、uid 1000 として開始:
| | |
| --- | --- |
| uid 0 に到達したゲスト | **6台中2台** |
| root までの時間 | 623秒および1,344秒 |
| 勝利したレースでの試行回数 | 13,839および31,125 |
| 勝利しなかった各ゲストの試行回数 | 全3,600秒で約95,000 |
| キャンペーン全体の総試行回数 | 427,676 (6.8M スタックウォーク) |
| 観測された脆弱なアクセス (`Unexpected outstanding_txns -1`) | 256 |
| **カーネルクラッシュ、oops、パニック** | **0**、9ゲスト時間で |
この表から読み取る価値のある点が2つある。
**勝利したレースはほぼすべて root になった。** KASAN ラボは、脆弱なアクセスが
与えられた場合に解放がウィンドウ内に着地する確率を 7.1e-3 と測定している。
これをここで観測された256のアクセスに適用すると、キャンペーン全体で約1.8回の
use-after-free が予測される — そして2つの root が得られた。リクレーム、
アドレス発見、チェーンはボトルネックではない。レースこそがボトルネックである。
**何もクラッシュしなかった。** 勝利しなかった4台を含め、9ゲスト時間で oops を
起こしたゲストはなかった。チェーンは正しく配置されスプレーされたページに対して
発火するか、まったく発火しないかのどちらかである — それがステージ2の
多数決拒否の目的である。
その拒否は実際に発火する。すでに負荷がかかっているホスト上で6つのゲストを
同時に起動すると、ステージ2で諦めるものが1つ発生し、次のように出力された。```
[*] 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.
| 状態 | 主張 |
|---|
| 確認済み | この脆弱性は実在し、非特権プロセスから到達可能であり、上流の修正によって除去されます。 |
| 実証済み | 消滅しつつあるbinder_procの脆弱な逆参照が、パッチ未適用のカーネル上で自然かつ繰り返し発生し、修正済みカーネルでは決して発生しません。 |
| 実証済み | KASANによって報告された完全なuse-after-freeが、パッチ未適用のカーネル上で発生します。 |
| 実証済み | 解放されたbinder_procを攻撃者制御のバイトで再利用し、カーネルの制御フローを乗っ取り、x86_64上で非特権ユーザーからuid 0への権限昇格を達成します。 |
| 主張なし | 特定のベンダーまたはAndroidデバイスでのいかなる結果。テストされたのは以下に列挙する上流カーネルのみで、x86_64上です。 |
| 主張なし | 同梱のエクスプロイトがディストリビューションカーネルに対して無修正で動作すること。影響を受けるシステムを参照してください: カーネルごとのオフセットが必要であり、調査したすべての汎用ディストリビューションでは、そもそもbinderデバイスが非特権ユーザーから到達不能です。 |
| オフセット | フィールド | ウォーカーが行うこと |
|---|
| 108 | int outstanding_txns | それをデクリメントする |
| 113 | bool is_frozen | それを読み取る |
| 120 | wait_queue_head_t freeze_wait | outstanding_txns == 0 && is_frozenの場合にそれを走査する |
| 624 | spinlock_t inner_lock | それを取得して解放する |
| 状態 | コミット | 備考 |
|---|
| 導入された系統 | a370003cc301 | 上流のFixes:タグによって特定される |
| 脆弱性確認済み | 114a116aaa5f | 修正の直接の親。隣接するCVE-2026-64469修正を含むため、このペアがCVE-2026-64468単体を分離する |
| 修正済みメインライン | f223d27a546c | テスト対象の修正 |
| ディストリビューション | カーネル | ANDROID_BINDER_IPC | デバイス | SLAB_BUCKETS | RANDOM_KMALLOC_CACHES | 非特権で到達可能か? |
|---|
| Debian 13 (trixie) | 6.12.101 | m | ANDROID_BINDER_DEVICES="binder"、BINDERFSオフ | y | オフ | いいえ — モジュールがロードされていない。/dev/binderは0600 |
| Debian 12 (bookworm) | 6.1.0 | m | ANDROID_BINDER_DEVICES="binder"、BINDERFSオフ | n/a (6.11より前) | n/a (6.6より前) | いいえ — 同上 |
| Ubuntu 24.04 LTS | 6.8.0 | m | BINDERFS=m、ANDROID_BINDER_DEVICES="" | n/a (6.11より前) | y | いいえ — mount -t binderにrootが必要 |
| Ubuntu 22.04 LTS | 5.15.0 | m | BINDERFS=m、ANDROID_BINDER_DEVICES="" | n/a | n/a | いいえ — 同上 |
| Android (AOSP / ベンダー) | 6.1、6.6、6.12 GKI | y | /dev/binder、/dev/hwbinder、/dev/vndbinder | — | — | はい — ドライバはプラットフォームのIPCであり、ワールドアクセス可能 |
| オプション | ここでの効果 |
|---|
CONFIG_SLAB_BUCKETS (6.11+) | このリクレームにとって致命的。 msg_msgを独自のkmallocバケットに分離するため、ポンプはbinder_procのスロットに決して到達できない。Debian 13はこれを設定している。別の未計上の1 KiB割り当てを見つける必要があるだろう。対象となるAndroidデバイスが実行するカーネル6.6は、これを完全に先取りしている。 |
CONFIG_RANDOM_KMALLOC_CACHES (6.6+) | kmalloc-1kをコールサイトごとに複数のキャッシュに分割するため、ポンプは同じキャッシュに当たる必要がある。リクレームに対する1/16の課税であり、壁ではない。Ubuntuはこれを設定し、Debianは設定しない。 |
ページテーブル分離 (noptiを使用しない) | アドレス発見にとって致命的。 PTIがアクティブな場合、prefetchはカーネルテキストを見ることができず、エクスプロイトはこれを検出して停止する。PTIは上記のすべてのディストリビューションでコンパイルされているが、アクティブかどうかはCPUが決定する: Meltdownの影響を受けないハードウェアではオフになっており、これらの結果が測定されたのはそこである。 |
CONFIG_SLAB_FREELIST_RANDOM、..._HARDENED | ディストリビューションが配布する通り、実験室で有効化されている。測定可能な効果はない: ポンプはフリーリストの順序を予測せず、単に非常に多くのオブジェクトを割り当てるだけである。 |
KASLR (RANDOMIZE_BASE、RANDOMIZE_MEMORY) | 有効。prefetchステージによって無効化される。nokaslrなし。 |
| カーネルごとのオフセット | チェーンはcommit_creds、3つのcred関連グローバル変数、2つのガジェットを_textからのオフセットとして必要とする。mkoffsets.shはこれらを対象のvmlinuxから抽出する。これらがないと、エクスプロイトは誤ったアドレスに向けて発火する。これはあらゆるカーネルエクスプロイトのビルドごとの特性であり、防御策ではない。 |
メモリ安全性実験室 (lab/) | エクスプロイト実験室 (demo/) |
|---|
| カーネルバージョン | 7.2.0-rc1+ | 7.2.0-rc1+ |
| 脆弱なコミット | 114a116aaa5f0295376cdf12da743c5bce3b20ce | 同じ |
| 修正済みコミット | f223d27a546c1e1f48d38fd67760e78f068fe8c4 | — (エクスプロイトは脆弱なカーネル上でのみ測定される) |
| アーキテクチャ | x86_64 (KVM) および arm64 (TCG) | x86_64 (KVM) |
| コンパイラ | Ubuntu clang 21.1.8 / LLD 21.1.8 | 同じ |
| KASAN | オン — これが検出器である | オフ — スラブレイアウトを変更し、あらゆるリクレームを非代表的にするため |
| スラブハードニング | — | SLAB_FREELIST_RANDOM、SLAB_FREELIST_HARDENEDオン。SLAB_BUCKETS、RANDOM_KMALLOC_CACHESオフ |
| KASLR | — | RANDOMIZE_BASE、RANDOMIZE_MEMORYオン |
| ユーザーランド | 最小限のinitramfs | Debian GNU/Linux 13 (trixie)、ディストリビューション自身のgccを使用 |
| 開始時の識別情報 | uid 1000、gid 1000、ケーパビリティなし、名前空間なし | 同じ |
| ブートコマンドライン | console=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1 | console=ttyS0 loglevel=4 rdinit=/init — noptiなし、nokaslrなし、mitigations=offなし |