当社のAIエンジンNeutronが、UCバークレーのCyberGymベンチマークで96.75%のスコアを記録しました。 詳細を見る

セキュリティ

セキュリティ

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には、次の処理が必要です。

  1. 連鎖した暗号文の計算(すでにそのページに適用済みの以前のSTOREを考慮する)。
  2. fcrypt_decrypt(C_actual, K)が目的の平文を生成するようなKの総当たり。
  3. 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

システム情報 — Ubuntu 24.04.1、kernel 6.17.0-23-generic
図1:対象システム — Ubuntu 24.04.1、kernel 6.17.0-23-generic(未パッチ)

エクスプロイトの実行

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

DirtyFrag PoC — rootシェルの取得
図2:権限を持たないユーザーがDirtyFragを介して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