
Linux cifs.ko の DACL ヒープオーバーフロー(CVE-2026-97555)に関する分析、POC、および arm64 エクスプロイトチェーン。参照ビルド上で uid-0 への資格情報上書きまで昇格します。
日本語 | 简体中文
SMB クライアントの DACL 書き換えにおけるカーネルヒープオーバーフロー。id_mode_to_cifs_acl() は、サーバーから返された DACL に基づいて出力セキュリティ記述子のサイズを計算するが、replace_sids_and_copy_aces() はすべての ACE を新しい owner/group SID(最大 68 バイト)で書き換える。したがって、短い SID の DACL を返すサーバーに対しては、書き換え後のサイズが割り当てを超え、kmalloc オブジェクトの境界を越えて書き込みが行われる。
本リポジトリには、解析結果、クロスオブジェクト書き込みを再現する POC、および参照ビルド上でこのオーバーフローを制御されたエンドツーエンドのチェーン(ヒープリーク、ページテーブル回収、任意の PTE 書き込み、自身の cred を上書きして uid 0 化)に変換する EXP が含まれている。
| 項目 | 値 |
|---|---|
| CVE | CVE-2026-97555 |
| クラス | id_mode_to_cifs_acl() におけるヒープバッファオーバーフロー(書き込み、CWE-122) |
| コンポーネント | cifs.ko(SMB クライアント) |
| 影響範囲 | 修正のない Linux cifs.ko(6.12 GKI ベースの Android カーネルで検証済み) |
| パッチ | upstream 0ee150794c75(stable fb736ab4fb8c) |
| 参照ビルド | Android 16 GKI ベースの 6.12 カーネル、arm64、4 KiB ページ、64 バイトの struct page |
| POC | クロスオブジェクト書き込みを再現(696 バイトの SET_INFO セキュリティ記述子) |
| EXP | 参照ビルド上で uid 0 のシェルまでの完全なチェーン |
/proc/kpageflags を読み取る。マウント権限の取得はテスト用の前提条件であり、バグの一部ではなく、本リポジトリのスコープ外である。uid/suid/euid/fsuid フィールドのみを書き換えて昇格する。capability や securebits のフィールドには一切触れない。クリーンアップは再起動である。src/exp.c 内の定数(シンボルアドレス、vmemmap のジオメトリ、BTF オフセット)は参照ビルドに固有のものである。他のビルドで実行する前に再導出すること。scripts/derive_offsets.sh を参照。id_mode_to_cifs_acl() は dacl_ptr->size または num_aces * sizeof(struct smb_ace) から割り当てサイズを計算し、その後 replace_sids_and_copy_aces() が各 ACE を新しい owner/group SID で再構築する。サーバーの SID が短く(0-subauth)、新しい SID が 68 バイトの場合、書き換え後の DACL は割り当てサイズより長くなり、build_sec_desc() がスラブオブジェクトの境界を越えて書き込む。
カーネルの動的 FORTIFY インストルメンテーションはインライン化された ACE ヘッダーのストアをチェックするが、SID のコピーはアウトオブラインの cifs_copy_sid() を経由するためインストルメントされない。すべての ACE ヘッダーが nsecdesclen - 28 の範囲内に収まるように DACL を整列させると、脆弱なコピーに到達する。
cross7 サーバーレイアウト(7 個の ACE、ソースサイズ 76/76/76/44/44/16/16)では、割り当てサイズがちょうど 512 バイトになる。7 番目の ACE の 68 バイトの SID コピーはオフセット 492 から始まりオブジェクト境界を越え、書き換え後の記述子は 696 バイトに成長する — オブジェクトを 184 バイト超える。拡張された記述子は依然として SET_INFO で送信されるため、書き込みはサーバー側から観測可能である。
fb736ab4fb8c なしでビルドされた 6.12 GKI ベースのカーネルを搭載した arm64 Android デバイス。kpageflags、mount、chown)と、cifs idmap upcall 用の SELinux permissive ウィンドウ(または同等のポリシー許可)。src/poc_smb2_acl.py が提供する共有を、標準カーネルクライアント経由で cifsacl/modefromsid を付けてマウントする(scripts/mount_cifs.sh)。マウント自体は特権を要する前提条件であり、テスターがその特権をどのように取得するかは本リポジトリの範囲外である。cifs.idmap キー:参照環境には /sbin/cifs.idmap upcall ヘルパーが存在しない(src/poc.c と src/exp.c が事前ロードする。src/acl_key_preload.c は単体でこれを行う)。# host
python3 src/poc_smb2_acl.py # ACL_LAYOUT=cross7 for the overflow
adb reverse tcp:1468 tcp:1468
# device (root)
sh /data/local/tmp/e2e/mount_cifs.sh 127.0.0.1 share /mnt/cifs
make && make push
/data/local/tmp/poc /mnt/cifs
期待される証拠: サーバーログには
SET_INFO security: len=696 ... (cross7) が出力されます。つまり、512 バイトの
割り当てを超える長さのディスクリプタです。また、scripts/trace_acl02.sh は
chown 前後の id_mode_to_cifs_acl のエントリ/リターンを示します。
POC は脆弱なパスをトリガーするだけであり、グルーミング、リーク、権限昇格は 行いません。このトリガーはカーネルの境界外書き込みを実行します。動的な FORTIFY チェックに引っかかるレイアウトのビルドではパニックを起こす可能性が あるため、再起動可能なテストデバイス上でのみ実行してください。
EXP は参照ビルド上での完全なチェーンをエンドツーエンドで実行します:
cross7 のオーバーフローが隣接するキーペイロードの
datalen を書き換えます。keyctl_read は 64 KiB をダンプし、そこには
稼働中の pipe_buffer 配列が含まれます。リークされた
anon_pipe_buf_ops ポインタから KASLR スライドが得られ、バッファページ
P から実際の struct page ターゲットが得られます。buffer[0] が page=P、len=0x1234、ops=default_pipe_buf_ops で
書き換えられます。P が解放されます。
base でのフォールトがそれをページテーブルページとして再取得します。
これは __alloc_pages_noprof が __pte_alloc から P を返したことを
証明する kretprobe によってゲートされます。P+16 (PTE[2]) から始まります。その最初の 8 バイトは
P 自身のためのセルフマップディスクリプタです。その後ユーザーランドは
base+8 KiB を通じてページテーブルページを読み書きします。KPF_SLAB ページのバッチが新しい PTE を
通じてマップされ、このタスクの comm をスキャンします。pid/tgid、
リニアマップウィンドウ、cred->uid/euid がヒットをクロスチェックし、
新しく一度も使われていない VA が物理ページを再検証します。setresuid(2000) を実行し
(カーネルは新しい cred を割り当てます)、セルフマップを通じて
task->cred を読み取り、uid/suid/euid/fsuid のみをゼロにし、
getuid()==0 を確認し、セルフマップを解除して /system/bin/sh を
execve します。シェルは uid 0 として実行されます。make exp
make push
# on the device, as root:
sh /data/local/tmp/e2e/launch_exp.sh /mnt/cifs 10 1 r1 4000 400 2400 7 0 0 cred
# then read /data/local/tmp/e2e/e2e_status.txt and
# /data/local/tmp/e2e/root_shell.txt
cred ステージは最後の引数(cred)でオプトインします。scout は読み取り専用の検証後に停止します。EXP は PTE をプロセス内に保持し、クリーンアップは再起動です。
要件: Android NDK r29(NDK= で上書き可能)、GNU make、adb。
make # POC (arm64, API 24)
make exp # EXP
make build-all # both
make check # push and run the read-only pre-check (poc --check)
make push # push binaries and helper scripts
make run # push and run the POC against $(MNT) (default /mnt/cifs; destructive)
make host # static POC build for qemu-aarch64
make emu # host build and run
make logs # dump logcat/dmesg into logs/
make help
.
├── Makefile
├── README.md / README.zh-CN.md
├── LICENSE
├── src/
│ ├── poc.c # POC: idmap preload + chown trigger (no escalation)
│ ├── exp.c # EXP: end-to-end chain to the cred overwrite
│ ├── vuln_precheck.h # profile/environment gate
│ ├── poc_smb2_acl.py # malicious SMB2/3 ACL server (DACL layouts)
│ ├── acl_key_preload.c # standalone cifs.idmap key preload
│ └── cifs_keyring_dump.c # keyring inspection helper
├── scripts/
│ ├── mount_cifs.sh # standard-client mount of the POC share
│ ├── unmount_cifs.sh
│ ├── launch_exp.sh # device-side EXP launcher
│ ├── trace_acl02.sh # kprobe on id_mode_to_cifs_acl
│ └── derive_offsets.sh # BTF offsets for src/exp.c
└── docs/analysis.md # analysis, primitives, validation records
vmlinux/BTF と、オフセット、シンボルアドレス、vmemmap の計算式の再導出が必要である。cifsacl マウント、事前にロードされた idmap キー、そして寛容なウィンドウが必要である。これはプリミティブのデモンストレーションであり、信頼できないアプリからのエクスプロイト経路ではない。cifsacl/modefromsid を有効にしないこと。認可されたセキュリティ研究および教育のみを目的として提供される。著者は悪用について責任を負わない。所有しているデバイス、または明示的な許可を得たデバイスでのみテストすること。
MIT — LICENSE を参照。