Dirty Pipe(CVE-2022-0847)
たった一つの初期化漏れフラグが、読み取り専用ファイルへの書き込みを許し一般ユーザーをrootにした事例から、パイプとページキャッシュの内部動作と状態管理の原則を学べる。
- Linux 5.8でpipe_bufferのflags初期化が漏れ、splice後もPIPE_BUF_FLAG_CAN_MERGEが残った。読み取り専用ファイルをパイプ経由で上書きでき、CVSS 7.8の権限昇格に至る。
- 書き込みは物理ページ上のデータを直接書き換えるだけで、ディスク上のファイルサイズやi_size、権限チェックを一切経由しない。/etc/passwd等を改変してrootを得る。
- 修正はpipe_buffer確保時にflagsを明示的に0クリアするだけ。教訓は、状態を持つ構造体は割り当て時に全フィールドを初期化し、キャッシュ層と権限層の責務境界を混同しないこと。
何が起きたか(影響範囲・深刻度)
Dirty Pipe(CVE-2022-0847)は、2022年3月に Max Kellermann が公開した Linux カーネルの権限昇格脆弱性です。攻撃者が読み取り権限しか持たないファイルであっても、その内容をカーネルのページキャッシュ上で書き換えられてしまいます。/etc/passwd や setuid バイナリのような特権ファイルを改変すれば、一般ユーザーが root へ昇格できます。
CVSS v3.1 は 7.8(High、AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)。混入は Linux 5.8(2020年、pipe: merge anon_pipe_buf*_ops のリファクタリング)で、修正は 5.16.11 / 5.15.25 / 5.10.102 として配布されました。ローカル権限が必要ですが、エクスプロイトは短く安定して動作し、コンテナからホストのイメージ実体を書き換える経路にもなり得たため、広範な緊急パッチ適用が行われました。
2014年の Dirty COW(CVE-2016-5195)になぞらえた命名です。どちらも「読み取り専用のはずのメモリ/ファイルへ書き込めてしまう」点は共通しますが、後述のとおり原因となるカーネル機構はまったく異なります。
脆弱性の原理(なぜ起きるか)
Linux のパイプは pipe_buffer の配列(リングバッファ)で表現されます。各 pipe_buffer は、データを載せた物理ページ page と、その振る舞いを決める操作ベクタ ops、そして状態を表す flags を持ちます。flags の一つが PIPE_BUF_FLAG_CAN_MERGE で、これが立っていると「直前に書き込んだこのバッファの空き領域へ、次の write を追記(マージ)してよい」ことを意味します。
問題は flags の初期化漏れです。splice() でファイルをパイプへ送ると、カーネルはデータをコピーせず、ページキャッシュ上のページへの参照をそのまま pipe_buffer に載せます(copy_page_to_iter_pipe())。この経路で新しい pipe_buffer を確保する際に flags を設定し忘れており、直前に別の用途で使われた際の値が残ります。リングバッファは再利用されるため、先に通常の書き込みで PIPE_BUF_FLAG_CAN_MERGE を立てておけば、その値がページキャッシュ参照側のバッファに引き継がれてしまいます。
splice で載るのはファイルの実体そのものであるページキャッシュのページです。マージ書き込みはそのページへ直接 memcpy するだけで、「開いた fd が書き込み可能か」「そのバイト範囲を書けるか」という VFS の権限判定を一切通りません。書き換えはメモリ上で完結し、後でカーネルが通常のライトバック経路でディスクへ反映します。
copy_page_to_iter_pipe() が flags を明示初期化しないバグ自体は 4.9 から存在していましたが、5.8 のリファクタリングで匿名パイプの ops が統合され PIPE_BUF_FLAG_CAN_MERGE が導入されたことで、初めて悪用可能になりました。「未初期化フィールド」と「マージ最適化」という別々の要素が交差した瞬間に脆弱性が生まれた点が本質です。
エクスプロイトの流れ
横にスクロール
攻撃は概念的には次の手順で成立します(完全なペイロードは示しません)。
1. pipe を作る
2. パイプを一杯まで書いて全 pipe_buffer に
PIPE_BUF_FLAG_CAN_MERGE を立てる
3. パイプを空になるまで読み、バッファを解放(フラグは残留)
4. 標的ファイルを O_RDONLY で開く
5. splice() で「改変したいオフセットの直前1バイト」を
パイプへ送る(ページキャッシュ参照が載る)
6. パイプへ write する
→ 残留フラグによりマージ書き込みが発動し、
ページキャッシュ上の標的ページを直接上書き
手順2〜3で「フラグを立てて→解放する」ことで、次に確保される pipe_buffer に汚染された flags を仕込みます。手順5でわざと1バイトだけ splice するのは、書き込み位置を狙ったオフセットへ合わせるためです。制約は三つあります。標的ファイルは読める必要があること、書き込みの開始位置がページ境界(4KiB 境界)であってはならないこと、そして一度の書き込みがページ境界をまたげないこと。ファイルサイズや i_size は変わらないため、既存バイトの上書きに限られます。
既存バイトの上書きしかできない制約下でも、/etc/passwd の該当ユーザー行のパスワードハッシュ欄を既知ハッシュへ上書きすれば、行長を変えずに root ログインを奪えます。setuid バイナリの命令列を書き換える亜種も報告されました。いずれも「行やフィールドの長さを保ったまま意味を変える」古典的テクニックです。
修正と対策
修正は驚くほど小さく、pipe_buffer を確保する箇所で flags を明示的に 0 に初期化するだけです。copy_page_to_iter_pipe() と push_pipe() の該当行が対象で、残留した PIPE_BUF_FLAG_CAN_MERGE がページキャッシュ参照バッファへ引き継がれる余地を断ちました。
| 観点 | 修正前 | 修正後 |
|---|---|---|
| flags の扱い | 確保時に未設定(前回値が残留) | 確保時に 0 で明示初期化 |
| マージ書き込み | 汚染フラグで意図せず発動 | ページキャッシュ参照では不発 |
| 変更規模 | — | 実質数行、挙動変更なし |
運用面の緩和策は、影響カーネル(5.8〜、5.10.102 / 5.15.25 / 5.16.11 未満)への速やかなパッチ適用が第一です。回避策としての無効化手段は無く、splice を一般ユーザーから奪うのも現実的ではありません。コンテナ環境では、ホストと共有される読み取り専用マウントやイメージレイヤの実体が書き換わり得るため、ホストカーネルの更新が本質的な対策になります。seccomp で不要なシステムコール表面を絞る一般的な多層防御も有効ですが、本件の主対策はあくまでカーネル更新です。
教訓(一般化できる原則)
Dirty Pipe が教えるのは、派手なメモリ破壊ではなく地味な状態管理の失敗こそ致命的になり得るという点です。
- 状態を持つ構造体は割り当て時に全フィールドを初期化する:
pipe_bufferのようにプールで再利用される構造体では、未初期化フィールドが前回値を保持する。C 言語では「使う直前に設定するから大丈夫」という前提が、確保経路が複数あると破綻する。ゼロ初期化を既定にするか、確保関数に初期化を集約するのが安全側の設計です。 - 性能最適化は新しい状態を導入し、状態は不変条件を増やす: マージ最適化(
CAN_MERGE)自体は正当だが、それが成り立つ前提(「このバッファは書き込み可能なパイプ由来である」)を全経路で保証できていなかった。最適化を入れるたびに、その前提が破れる経路が無いかを問う必要があります。 - キャッシュ層と権限層の責務を混同しない: ページキャッシュはファイル実体の写しであり、そこへの書き込み経路が VFS の権限判定を迂回できると、キャッシュが権限バイパスの近道になる。層をまたぐ書き込みには、その層で改めて権限を確認する設計が求められます。
同種のメモリ・状態にまつわる欠陥やカーネルの隔離境界についてはセキュリティやOSの各トピックが、投機実行を悪用する別系統のハードウェア脆弱性はSpectre / Meltdownが参考になります。
脆弱性の解剖の記事ガイド
Dirty Pipe(CVE-2022-0847)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Linux
比較で見る軸
難易度: advanced / カテゴリ: 脆弱性の解剖 / タグ数: 6
導入後に効く点
書き込みは物理ページ上のデータを直接書き換えるだけで、ディスク上のファイルサイズやi_size、権限チェックを一切経由しない。/etc/passwd等を改変してrootを得る。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- 脆弱性の解剖
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Linux / カーネル」に近いか確認する。
- 強みである「Linux 5.8でpipe_bufferのflags初期化が漏れ、splice後もPIPE_BUF_FLAG_CAN_MERGEが残った。読み取り専用ファイルをパイプ経由で上書きでき、CVSS 7.8の権限昇格に至る。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。