
Linux qdiscのレートテーブルのレースコンディションに対する概念実証(PoC)ローカル権限昇格エクスプロイト。BPFヒープグルーミングとパイプリークによりroot権限を獲得します。

このリポジトリには、Linux トラフィックコントロールの rate-table コードにおける競合である CVE-2026-68138 に対する概念実証(PoC)ローカル権限昇格エクスプロイトが含まれています。テストした QEMU 環境では、PoC は外部 UID 1000 の通常プロセスから、初期ユーザー名前空間の UID 0 のシェルへ権限を昇格させます。
警告
このコードは意図的にカーネルヒープの状態を破壊します。所有する隔離済みで使い捨て可能な VM 内でのみ使用してください。競合に失敗したり、早期にクリーンアップが行われたりすると、ゲストがパニックを起こす可能性があります。ワークステーション、サーバー、または第三者のシステム上では実行しないでください。
qdisc_get_rtab() と qdisc_put_rtab() は、プロセスグローバルな片方向リンクリスト qdisc_rtab_list と、通常のアトミックではない int refcnt を管理します。これまでの呼び出し側は RTNL ミューテックスを保持しており、リストと参照カウントへのアクセスを直列化していました。
flower 分類子は TCF_PROTO_OPS_DOIT_UNLOCKED を設定します。そのため、flower ルールに対する RTM_NEWTFILTER リクエストは、ポリスアクションに到達し、RTNL なしで qdisc rate-table ヘルパーを呼び出す可能性があります:
tc_new_tfilter()
-> fl_change()
-> tcf_exts_validate_ex()
-> tcf_action_init()
-> tcf_police_init()
-> qdisc_get_rtab()/qdisc_put_rtab()
同じレートテーブルを使用する並行リクエストは、グローバルリストとその参照カウントを競合させる可能性があります。その結果、テストした x86-64 カーネルでは kmalloc-2k から割り当てられる 1056 バイトのオブジェクトである struct qdisc_rate_table の use-after-free または double-free が発生します。
このリストはネットワーク名前空間ごとではなくグローバルであるため、異なるネットワーク名前空間からのリクエストでも同じオブジェクトを競合させます。
Linux CNA レコードは、Linux 5.1 でリリースされた導入コミット 470502de5bdb を特定しています。
ディストリビューションカーネルは、上記のアップストリームバージョンに変更せずに修正をバックポートすることがよくあります。システムが使用する正確なカーネルソースに、いずれかの修正コミット—または同等の qdisc rate-table スピンロック変更—が存在するかどうかを確認してください。
エクスプロイトは、メインライン修正の直接の親である脆弱なコミット 92d3817649df2b0b6a008a686c8275c88d7ef594 に対して開発、検証されました。修正済みカーネルの対照実験では f43ee0c0730d6191629b5ee1ceae27b1ebfdc047 を使用しました。
2026-08-12 現在、Ubuntu CVE トラッカー検索 にはこの CVE のエントリは返されませんでした。したがって、以下の表は直接のソース調査であり、Canonical のセキュリティステータス判定ではありません。リンクされた各 Ubuntu タグには、依然としてロックされていない qdisc_rtab_list があり、修正用の qdisc_rtab_lock がありません。
その調査時点では、修正済みの Ubuntu パッケージは確認されませんでした。今後の Ubuntu パッケージは、バージョン番号だけで判断するのではなく、リンクされたアップストリーム修正と同等のバックポートがあるかどうかを確認する必要があります。
ビルド固有の Ubuntu 22.04 エクスプロイト、QEMU ラボ、正確なイメージチェックサム、および条件は ubuntu/README.md に文書化されています。これは、メモリ cgroup アカウンティングを有効にした公式 5.15.0-187-generic #197-Ubuntu カーネルに対して検証されました。
根本的なバグとこの特定のエクスプロイトチェーンには異なる要件があります。PoC は以下で検証されました:
CONFIG_USER_NS=y と CONFIG_NET_NS=y;CONFIG_NET_CLS=y、CONFIG_NET_CLS_FLOWER=y、
CONFIG_NET_CLS_ACT=y、および CONFIG_NET_ACT_POLICE=y;simple_xattr ヒープスプレー用の CONFIG_TMPFS_XATTR=y;CONFIG_MODULES=y と使用可能な /sbin/modprobe;CONFIG_MEMCG=n(このチェーンが使用する qdisc/BPF/pipe/xattr の割り当てが、想定どおり kmalloc-2k キャッシュを共有します);CONFIG_SLAB_BUCKETS=y、フリーリストのランダム化、およびフリーリストのハードニングは、成功したテストカーネルで有効でした。KASLR はハードコードされたアドレスで本質的にバイパスされるわけではありません。PoC は必要なページと操作(ops)ポインタをパイプリークから取得します。提供されたラボでは、デバッグを簡素化するために nokaslr を使用しました。
メモリ cgroup アカウンティングが有効なカーネルや、異なるアロケータ/キャッシュレイアウトでは、別のリクレーム戦略が必要です。PoC は、任意のディストリビューション構成間での移植性を主張することを意図的に拒否します。
別の Ubuntu バリアントがその異なるリクレーム戦略を実装しています。その要件は意図的に狭く設定されており、ubuntu/README.md に記載されています。
4 つのワーカースレッドがそれぞれ異なるネットワーク名前空間に入り、ポリスアクションを持つ flower フィルタを作成します。3 つのワーカーは成功するアクションパスをたどり、1 つのワーカーは両方の rate-table 参照を取得した後に意図的に無効な estimator を供給してクリーンアップパスを強制します。この組み合わせにより、ワーカー間で flower 分類子の状態を共有せずに、並行する参照カウントとリスト操作を再現可能にします。
qdisc_rate_table をリクレームする各 netlink リクエストの後、同じ CPU が直ちに 133 命令のクラシック BPF フィルタをアタッチします。その 1064 バイトの命令配列は kmalloc-2k から割り当てられ、qdisc_rate_table.next と qdisc_rate_table.refcnt に重なるバイトが最初は有効なままになるように整形されています。
最後の命令にはソケットごとのマーカーが含まれています。SO_GET_FILTER により、PoC はあるソケットの orig_prog->filter ポインタが別の生存中の BPF 割り当てにリダイレクトされたことを検出できます。これにより、同じ命令バッファを参照する 2 つのソケットオブジェクトが得られます。
一方の所有者を閉じると、sk_filter_release_rcu() によって実際の解放が延期されます。猶予期間(グレース期間)の後、PoC は 32 スロットのパイプリングを割り当てます:
32 * sizeof(struct pipe_buffer) = 32 * 40 = 1280 bytes -> kmalloc-2k
一方のリングがエイリアスされた BPF バッファをリクレームします。もう一方のソケットを SO_GET_FILTER で読み取ると、生存中の pipe_buffer が開示され、その page ポインタと ops ポインタ、および /sbin/modprobe のページキャッシュエントリのオフセットが含まれます。
2 番目のソケットを閉じると、別の RCU グレース期間の後に生存中のパイプリングが解放されますが、対応する pipe_inode_info は依然としてそれを参照しています。simple_xattr スプレーが 1280 バイトのリングをリクレームし、リークした pipe_buffer を PIPE_BUF_FLAG_CAN_MERGE が設定された期待されるスロットに配置します。
各候補パイプへの書き込みは、最終的に /sbin/modprobe の ELF エントリポイントをバックするページキャッシュページに攻撃者が制御するバイトを追加します。PoC はファイルを読み戻し、正確な上書きを確認した後にのみ進行します。
最近のカーネルは、search_binary_handler() における従来の無効な binfmt モジュール自動ロードのフォールバックを使用しなくなりました。この PoC は代わりに、サポートされていないプロトコル 253 で AF_INET データグラムソケットを作成します。inet_create() が欠落している net-pf-2-proto-253-type-2 モジュールを要求し、カーネルに上書きされた /sbin/modprobe を初期名前空間の root として実行させます。
注入されたエントリポイントペイロードは、証明ファイルを書き込み、QEMU シリアルコンソールで root シェルを開きます。
ゲストバイナリは静的リンクされています:
./build.sh
同等のコマンド:
gcc -O2 -static -pthread -Wall -Wextra -Werror \
-o build/exploit exploit.c
生成されたバイナリは、ゲスト内で /exploit/CVE-2026-68138 として公開する必要があります。テスト済みラボでは QEMU の読み書き可能な 9p 共有を使用しました。root ヘルパーは、初期マウント名前空間からその共有が /chroot/exploit にあることを期待します。ラボが別のレイアウトを使用する場合は、exploit.c の helper_command を調整してください。
関連するテストカーネルオプションは config.fragment に記録されています。これはフラグメントであり、完全な本番カーネル構成ではありません。
非特権ゲストシェルから:
id
/exploit/CVE-2026-68138
競合は確率的です。失敗すると PoC は次のように出力します:
[-] no BPF alias found in this attempt
再試行する前に VM を再起動してください。同じゲスト内で再実行しないでください。グローバル qdisc リストがすでに破損している可能性があります。
実行が成功すると、次の出力に到達します:
[+] confirmed dangling orig_prog->filter pointer
[+] pipe leak: page=<kernel pointer> ops=<kernel pointer> off=0x60f len=1 flags=0
[+] page-cache overwrite via dangling pipe
[+] controlled page-cache write confirmed; triggering helper
[+] CVE-2026-68138 initial-namespace root shell
uid=0(root) gid=0(root) groups=0(root)
[+] ROOT PROOF: uid=0(root) gid=0(root) groups=0(root)
完全なサニタイズ済みトランスクリプトは docs/example-output.txt にあります。
完全なチェーンは、脆弱な VM の新しい起動から 3 回完了しました。最終バイナリは次の出力を生成しました:
outer identity: uid=1000(user)
alias: classic-BPF orig_prog->filter overlap
leak: live pipe_buffer page and ops pointers
write: verified /sbin/modprobe page-cache modification
final identity: uid=0(root) gid=0(root) groups=0(root)
同じバイナリを、同じ 4 vCPU トポロジで修正済みコミットに対して実行しました。約 126,000 回の競合リクエストが BPF エイリアスなしで完了し、パイプまたはページキャッシュのステージには到達しませんでした。
Ubuntu 固有のバリアントも、正確な公式 5.15.0-187.197 generic カーネルに対して実行されました。ゲストは 4 つの vCPU、通常のメモリ cgroup アカウンティング、およびエクスプロイト固有のカーネル引数として nokaslr のみを使用して起動しました。
Ubuntu カーネルは再コンパイルされていません。テストでは、ディストリビューションの変更されていない vmlinuz-5.15.0-187-generic と対応する公式モジュールを使用しました。Ubuntu 前提条件テーブル は、ストックカーネルの機能と、ランタイムおよび QEMU ラボの調整を区別しています。
開始アカウントには sudo アクセスも補助グループもありませんでした:
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
新しいスナップショットから、エクスプロイトは BPF エイリアスを取得し、cgroup-v2 の simple_xattr オブジェクトとブロッキング select() ビットマップを介してそれをリクレームし、/proc/sys/kernel/modprobe に /tmp/x が含まれていることを確認し、次の出力に到達しました:
[+] CVE-2026-68138 Ubuntu initial-namespace root shell
uid=0(root) gid=0(root) groups=0(root)
root@cve-2026-68138-jammy:/# id
uid=0(root) gid=0(root) groups=0(root)
実際の QEMU トランスクリプト と Ubuntu ラボの手順 を参照してください。このバリアントは、このパッケージの modprobe_path アドレスをハードコードしているため、nokaslr が必要です。KASLR バイパスや他の Ubuntu ビルドとの互換性を主張するものではありません。
このパッチはすでに Linux カーネルの一部であるため、このリポジトリには複製されていません。標準の安定版およびメインラインの修正コミットは、以下の参照先にリンクされています。
研究およびエクスプロイト実装: A. Ramos <[email protected]> (Twitter: @aramosf).
| 系列 | 状態 | コミット/バージョン |
|---|
| Linux 5.1 以前 | 影響なし | 導入変更が存在しない |
| Linux 5.1 から 7.1.5 | ベンダーのバックポートが存在しない限り影響あり | 470502de5bdb から安定版修正の直前のコミットまで |
| Linux 7.1.y | 修正済み | 7.1.6, fb29e1b41052 |
| Linux 7.2 開発シリーズ | rc5 より前は影響あり | rc1 から rc4 |
| メインライン | 修正済み | 7.2-rc5, f43ee0c0730d |
| Ubuntu 系列 | 調査したパッケージ/タグ | ソース調査結果 |
|---|
| Ubuntu 22.04 GA | 5.15.0-187.197 | 脆弱なコードが存在; QEMU で完全なエクスプロイトを再現 |
| Ubuntu 22.04 HWE | 6.8.0-136.136~22.04.1 | 脆弱なコードが存在; エクスプロイトチェーンは未テスト |
| Ubuntu 24.04 HWE | 7.0.0-28.28~24.04.1 | 脆弱なコードが存在; このエクスプロイトチェーンはそのアロケータのハードニングと互換性がない |
| Ubuntu 26.04 | 7.0.0-28.28 | 脆弱なコードが存在; このエクスプロイトチェーンはそのアロケータのハードニングと互換性がない |