DirtyFrag:ページキャッシュ書き込みによるLinux汎用ローカル権限昇格
DirtyFragの技術解説です。これは2つのLinuxカーネルのローカル権限昇格の脆弱性(CVE-2026-43284およびCVE-2026-43500、CVSS 7.8 HIGH)であり、権限を持たないあらゆるローカルユーザーが主要なLinuxディストリビューションのほとんどでrootを取得できます。xfrm-ESPとRxRPCのインプレース復号経路の不備を連鎖させ、いずれもDirty PipeおよびCopy Failと同じページキャッシュ書き込みプリミティブに根ざしており、このエクスプロイトはレースコンディションなしで読み取り専用のページキャッシュページを上書きし、ほぼ100%の信頼性を実現します。
DirtyFrag
ページキャッシュ書き込みによるLinux汎用ローカル権限昇格
2026年5月7日 · CVSS 7.8 HIGH · Linuxカーネル(2017〜2026年)
| CVE ID | CVSS | コンポーネント | 影響を受けるバージョン | 修正 |
|---|---|---|---|---|
| CVE-2026-43284 | 7.8 HIGH | xfrm / ESP (esp4, esp6) | kernel ≥ cac2661c53f3 (2017-01-17) | f4c50a4034e6 (2026-05-08) |
| CVE-2026-43500 | 7.8 HIGH | RxRPC (rxrpc.ko) | kernel ≥ 2dc334f1a63a (2023-06-08) | aa54b1d27fe0 (2026-05-10) |
DirtyFrag概要:Dirtyページキャッシュ書き込みファミリーの新たな一員
DirtyFragはHyunwoo Kim (@v4bel)によって発見・報告された権限昇格手法であり、Linuxディストリビューションの大多数でレースコンディションなしにrootを取得します。これはDirty PipeおよびCopy Failと同じ脆弱性クラスに属します。攻撃者はsplice()を用いて読み取り専用のページキャッシュページをカーネルのデータ構造にピン留めし、その後カーネルコードがそのページの上にインプレース書き込みを行うことで、攻撃者が読み取りしかできないファイルのRAM上のコピーを恒久的に破壊します。
この名前は「dirtied(汚される)」側のメンバーを指します。Dirty Pipeはstruct pipe_bufferを破壊しますが、DirtyFragはstruct sk_buffのfragメンバーを破壊します。
DirtyFragは、互いの盲点を補い合うために2つの独立した脆弱性を連鎖させます。
- CVE-2026-43284(xfrm-ESPページキャッシュ書き込み) — IPsec ESP入力経路を通じて発火する、強力な任意4バイトSTOREプリミティブです。ほとんどのディストリビューションで利用可能ですが、ユーザー名前空間を作成する権限(
unshare(CLONE_NEWUSER))が必要です。 - CVE-2026-43500(RxRPCページキャッシュ書き込み) — RxRPCのインプレース復号経路を通じて発火する8バイトSTOREです。名前空間の権限は不要ですが、
rxrpc.koに依存します。これはほとんどのディストリビューションには同梱されていませんが、Ubuntuではデフォルトでロードされます。
ESPバリアントを先に実行し、名前空間の作成がブロックされている場合(例:AppArmorでハードニングされたUbuntu)にはRxRPCバリアントへフォールバックすることで、単一のバイナリがあらゆる主要ディストリビューションでrootを取得します。
CVE-2026-43284:xfrm-ESPページキャッシュ書き込み
根本原因
ESPペイロードに対してインプレースAEAD復号を実行する前に、esp_input()は非線形なskbに対してskb_cow_data()を呼び出し、プライベートなカーネルバッファを割り当ててそこにfragデータをコピーすべきです。しかし、skbがクローンされておらず、frag_listも持たない場合、この関数は復号へと直接ショートカットします。
static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
{
[...]
if (!skb_cloned(skb)) {
if (!skb_is_nonlinear(skb)) { // [1]
nfrags = 1;
goto skip_cow;
} else if (!skb_has_frag_list(skb)) {
nfrags = skb_shinfo(skb)->nr_frags;
nfrags++;
goto skip_cow; // [2] vulnerable path
}
}
err = skb_cow_data(skb, 0, &trailer);
[...]
[2]では、fragがコピーオンライトをバイパスします。攻撃者がspliceを通じてページキャッシュページをそのfragにピン留めしていた場合、ESPサブシステム内部のAEADエンジンはそのページ上で直接動作します
(注:これはalgif_aeadとは別物であり、ここにAF_ALGは関与しません)。
復号関数crypto_authenc_esn_decrypt()は、ESNシーケンス番号の前処理ステップ中、認証が実行される前に4バイトSTOREを行います。
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
[...]
scatterwalk_map_and_copy(tmp, src, 0, 8, 0);
if (src == dst) {
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // [3] 4-byte STORE
dst = scatterwalk_ffwd(areq_ctx->dst, dst, 4);
[...]
[3]で書き込まれる4バイトの値は、ESNシーケンス番号の上位32ビットであり、攻撃者がSA登録時にXFRMA_REPLAY_ESN_VAL netlink属性を介して指定する値です。つまり攻撃者は、書き込み先のファイルオフセット(spliceによる位置決めを通じて)と書き込まれる4バイト(SAのseq_hiを通じて)の両方を制御できます。AEAD認証は-EBADMSGで失敗しますが、STOREはすでにページキャッシュへコミットされています。
エクスプロイト戦略(ESPバリアント)
対象は/usr/bin/suです。このエクスプロイトは、そのページキャッシュコピーの先頭192バイトを、最小限のrootシェルELFで上書きします。
- このELFはPT_LOADを介して、vaddr
0x400000に0xb8バイトをR+Xとしてマップします。 - エントリポイント
0x400078はsetgid(0); setuid(0); setgroups(0, NULL); execve("/bin/sh", ...)を呼び出します。 - PAMのフローは完全にバイパスされます。
192バイトは48個の4バイトチャンクに分割され、それぞれが個別のXFRM SAによって配送されます。各SAのseq_hiが対象のシェルコードバイトを運びます。
子プロセスはまずunshare(CLONE_NEWUSER | CLONE_NEWNET)を呼び出して新しい名前空間内でCAP_NET_ADMINを獲得し、その後48個のSAを一度に登録します。各トリガーは、偽造したESPワイヤヘッダーと/usr/bin/suページの16バイトを(vmsplice + spliceを介して)、UDP_ENCAP_ESPINUDPを設定したループバックUDPソケットへパイプします。結果として生成されるskbはページキャッシュページをfrags[0]として運び、esp_inputを流れ、4バイトSTOREを受け取ります。48回の反復がすべて完了すると、完全なELFがRAM上で組み立てられ、親プロセス(initの名前空間)からのexecve("/usr/bin/su")がrootシェルを起動します。
パッチ(CVE-2026-43284)
この修正は、IPv4/IPv6のデータグラム追加経路でspliceを介して到着するページfragにSKBFL_SHARED_FRAGフラグを付与し、esp_input / esp6_inputのskip_cowガードを拡張してこのフラグをチェックするようにします。
-} else if (!skb_has_frag_list(skb)) {
+} else if (!skb_has_frag_list(skb) &&
+ !skb_has_shared_frag(skb)) {
攻撃者がピン留めしたページキャッシュページは、常にskb_cow_data()に到達するようになり、インプレースAEADのdst SGLに入ることはできなくなりました。
CVE-2026-43500:RxRPCページキャッシュ書き込み
根本原因
rxkad_verify_packet_1()は、rxrpcペイロードの先頭8バイトに対してインプレースのpcbc(fcrypt)復号を行うことで、RXRPC_SECURITY_AUTHレベルのRxRPC DATAパケットを検証します。
sg_init_table(sg, ARRAY_SIZE(sg));
ret = skb_to_sgvec(skb, sg, sp->offset, 8);
[...]
skcipher_request_set_crypt(req, sg, sg, 8, iv.x); // [4] src == dst: in-place
ret = crypto_skcipher_decrypt(req); // [5] 8-byte STORE
[4]では、srcとdstのSGLが同一です。skb_to_sgvec()はskbのfragを直接SGLへ変換するため、攻撃者がspliceを介してピン留めしたページキャッシュページがsrcとdstの両方になります。[5]の8バイトSTOREはfcrypt_decrypt(C, K)を書き込みます。これは、fragオフセットの暗号文を攻撃者のセッションキーKで復号した結果です。
セッションキーはadd_key("rxrpc", ...)を介して、何の権限もなく自由に植え付けられます。ESPバリアントとは異なり、ユーザー名前空間は不要です。
エクスプロイト戦略(RxRPCバリアント)
STOREの値は直接制御できる値ではなくfcrypt_decrypt(C, K)であるため、攻撃者は目的の8バイト平文が得られるまで、ユーザー空間でKを総当たりします。fcryptは56ビット鍵のAFS暗号であり、ユーザー空間移植版はおよそ18 M/sで動作するため、制約の緩い各ブロックについて約5ミリ秒で鍵が得られます。
対象は/etc/passwdの1行目です。このエクスプロイトは、ファイルオフセット4、6、8の位置に3つの重なり合う8バイトSTOREを適用し、"root:x:0:0:root:/root:/bin/bash"を"root::0:0:GGGGGG:/root:/bin/bash"へと作り変えます。passwdフィールドは空文字列になり、nullok付きのpam_unix.soはそれを受け入れ、パスワードを求めることなくPAM_SUCCESSを返します。
各位置でのSTOREには、次の処理が必要です。
- 連鎖した暗号文の計算(すでにそのページに適用済みの以前のSTOREを考慮する)。
fcrypt_decrypt(C_actual, K)が目的の平文を生成するようなKの総当たり。add_keyによる鍵の登録、ループバックのAF_RXRPCハンドシェイクのセットアップ、ワイヤチェックサムの事前計算、そしてvmsplice+spliceを介した偽造DATAパケットの配送。
このバリアントはunshare()を呼び出しません。すべてのシステムコール(add_key、socket(AF_RXRPC)、socket(AF_ALG)、splice、recvmsg)は、権限を持たないユーザーが利用できます。
パッチ(CVE-2026-43500)
この修正は、call_event.cおよびconn_event.cにおけるインプレース復号前のガードを、単一のskb_clonedチェックから、共有skbおよびfrag-list skbも捕捉するように拡張します。
-if (skb_cloned(skb)) {
+if (skb_cloned(skb) || skb_has_frag_list(skb) ||
+ skb_has_shared_frag(skb)) {
外部からピン留めされたページ化fragを運ぶskbは、復号シンクに到達する前にskb_copy()によって隔離されるようになりました。
DirtyFrag:エクスプロイトチェーン — 単一バイナリ、汎用root
連鎖のロジックは単純です。
1. Attempt ESP variant (child: unshare USER+NET → register XFRM SAs → splice → patch /usr/bin/su)
2. Read back the first shellcode byte at the entry offset of /usr/bin/su.
- Success → parent: forkpty + execve("/usr/bin/su") → root shell.
3. Failure (unshare denied, esp4.ko absent, or SA registration fails):
- Fall back to RxRPC variant:
K brute-force × 3 → splice triggers → /etc/passwd line 1 cleared
forkpty + execve("/usr/bin/su") → PAM nullok → root shell.
この組み合わせた連鎖は、次の環境で確認されています。Ubuntu 24.04.4(kernel 6.17.0-23)、RHEL 10.1(6.12.0-124.49.1)、openSUSE Tumbleweed(7.0.2-1)、CentOS Stream 10、AlmaLinux 10、そしてFedora 44です。レースコンディションは関与せず、失敗してもカーネルはパニックを起こさず、成功率はほぼ100%です。
DirtyFragの概念実証(PoC)
公開されているPoCは単一のCファイルです。次のコマンドでビルドして実行します。
git clone https://github.com/V4bel/dirtyfrag.git && cd dirtyfrag && gcc -O0 -Wall -o exp exp.c -lutil && ./exp
テスト環境
このエクスプロイトは、未パッチのカーネルでUbuntu 24.04.1を実行するローカルのVirtualBox VM上でテストされました。
Linux aziz-VirtualBox 6.17.0-23-generic #23~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Apr 14 16:11:48 UTC 2 x86_64 GNU/Linux

エクスプロイトの実行
エクスプロイトチェーンはまずESPバリアントを実行し(このカーネルではユーザー名前空間の作成が許可されています)、/usr/bin/suのページキャッシュをrootシェルELFで上書きし、1秒未満でrootシェルを返しました。

uid=0(root) gid=0(root) groups=0(root)を取得
当面の緩和策
カーネルアップデートが利用可能になるまでは、脆弱なモジュールを無効化し、ページキャッシュをフラッシュします。
sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' \
> /etc/modprobe.d/dirtyfrag.conf; \
rmmod esp4 esp6 rxrpc 2>/dev/null; \
echo 3 > /proc/sys/vm/drop_caches; true"
修復
- バックポートが利用可能になり次第、お使いのディストリビューションのカーネルアップデートを適用してください。
- IPsecやAFSを必要としないシステムでは、
esp4、esp6、rxrpcモジュールがロードされていないことを確認してください(lsmod | grep -E 'esp4|esp6|rxrpc')。 - Ubuntuでは、権限を持たないユーザー名前空間の作成に関するAppArmorポリシーを見直してください。これはESP経路をブロックしますが、RxRPC経路はそれとは無関係に開いたままです。
- 機微なsetuidバイナリや
/etc/passwdについて、ページキャッシュの異常を監視してください。
参考資料
| リソース | リンク |
|---|---|
| GitHub PoC (V4bel/dirtyfrag) | https://github.com/V4bel/dirtyfrag |
| カーネルパッチ CVE-2026-43284 (f4c50a4034e6) | https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f4c50a4034e62ab75f1d5cdd191dd5f9c77fdff4 |
| カーネルパッチ CVE-2026-43500 (aa54b1d27fe0) | https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=aa54b1d27fe0c2b78e664a34fd0fdf7cd1960d71 |
| NVD CVE-2026-43284 | https://nvd.nist.gov/vuln/detail/CVE-2026-43284 |
| Red Hat Advisory RHSB-2026-003 | https://access.redhat.com/security/vulnerabilities/RHSB-2026-003 |
| Ubuntu Security Notice | https://ubuntu.com/blog/dirty-frag-linux-vulnerability-fixes-available |