FlawOpen

flawopen.com/Teardowns/cve-2024-21626-runc-container-breakout-leaky-vessels

● CVE-2024-21626 · CVSS 8.6[1], [5] · 高
セキュリティ研究 · FlawOpen

CVE-2024-21626:runc が Leaky Vessels を修正した方法

CVE-2024-21626 は、runc 内部のファイルディスクリプタ漏えいと chdir 後の境界確認不足が組み合わさった問題です。起動経路によって、コンテナがホストの名前空間にある作業ディレクトリで起動したり、関連する exec 経路からホストファイルに到達したりする可能性がありました。[1], [2], [3]

💡 わかりやすい解説 (ELI5)

runc はコンテナ用の隔離された部屋を準備します。内部ハンドルの漏えいにより、選ばれた作業ディレクトリが部屋の外を指す可能性がありました。最後の境界確認と内部ハンドルのクローズが、この越境を防ぎます。[1], [2]

主要な概念と専門用語

runc
コンテナを作成して起動する OCI ランタイムです。[1]
内部ファイルディスクリプタ
開いたファイルやディレクトリを参照する OS ハンドルです。子プロセス起動時にも開いたままだと、プロセス境界を越えてアクセスが残ることがあります。[1], [3]
cwd
プロセスの作業ディレクトリです。マウント名前空間のルート外にある場合、Linux の Getwd は ENOENT を返すことがあります。[1], [2]
コンテナ脱出
コンテナプロセスが隔離境界の外にあるホスト資源へ到達する経路です。[1]

フレームワーク対応

  • このガイドで説明する弱点の CWE 参照: CWE-403

根本原因の分析 (Root Cause)

runc 1.1.12 より前では、内部ファイルディスクリプタが runc init に漏れることがありました。これは runc init 内部の漏えいであり、コンテナプロセスに単純に継承されたわけではありません。攻撃1と2では、設定または指定された作業ディレクトリがホストのマウント名前空間を参照するハンドル経由で解決される可能性があり、runc は chdir 後に最終 cwd がコンテナのルート内にあるか確認していませんでした。別の亜種では execve のパス処理を利用し、ホストのバイナリを上書きしました。1.1.12 は Linux の Getwd/ENOENT 動作による cwd 検証、execve 直前の内部ディスクリプタのクローズ、既知の漏えい修正、runc init 起動前の非 stdio ディスクリプタへの close-on-exec 設定という4層を追加しました。[1], [2], [3], [4]

CVE-2024-21626:攻撃バリアントと runc 1.1.12 の対応策

アドバイザリのバリアント必要な条件と境界対応する修正または一時対策
攻撃1 — runc run による悪意あるイメージ特権ユーザーに未信頼イメージを起動させる必要があります。設定された作業ディレクトリがコンテナのマウント名前空間外に解決される場合があります。chdir 後の unix.Getwd() 検査は cwd が名前空間ルート外にあると失敗します。一時対策として未信頼イメージの cwd は / のみにします。
攻撃2 — コンテナ内プロセスと runc execコンテナ内のプロセスが、管理者による runc exec --cwd で / 以外が選ばれることを予測し、実行前に該当パスを準備する必要があります。同じ chdir 後の cwd 検査によりコンテナ外での実行を拒否します。修正前は --cwd を / に制限します。
攻撃3a — イメージ起動を介したホストバイナリ上書き攻撃1を応用したもので、特権ユーザーが悪意あるイメージを起動する条件が残ります。この亜種は execve 境界を越えてホストバイナリに到達します。execve の直前に runc の内部ディスクリプタを閉じ、確認済みの漏えいを修正して O_CLOEXEC を設定します。runc 1.1.12 以降へ更新します。
攻撃3b — runc exec を介したホストバイナリ上書き攻撃2を応用し、攻撃者が影響を与えられる稼働中コンテナの悪意あるプロセスから始まります。ただし、特権を持つ管理者がそのコンテナに対して通常の runc exec を実行する必要があります。アドバイザリによると、イメージ経由よりユーザー操作は少なくて済みます。exec 前のクローズと close-on-exec がこの別経路を保護します。メンテナーは実用的な回避策を示していないため、更新してください。

ステップ・バイ・ステップの攻撃フロー

ステップ 1

内部ハンドルの漏えい

初期化中、ホストのマウント名前空間を参照する runc 内部ディスクリプタが runc init から利用可能なまま残ることがありました。[1], [3]

ステップ 2

作業ディレクトリ経路

悪意あるイメージ、または特定条件を満たす runc exec の流れにより、要求された cwd がコンテナルート外に解決される可能性がありました。[1], [2]

ステップ 3

境界確認の欠落

修正前のコードは chdir 後の cwd を検証していませんでした。関連する亜種は execve のパス処理を利用してホストバイナリを上書きしました。[2], [3]

ステップ 4

影響と条件

アドバイザリは、4つの亜種でホストファイルシステムへのアクセスとホスト制御が可能になると説明しています。イメージ経由の攻撃には、特権ユーザーが未信頼イメージを起動する必要があります。攻撃3bでも、管理者が攻撃者の影響下にあるコンテナで通常の runc exec を実行する必要があります。[1]

ソースコード比較:脆弱 vs 堅牢化

脆弱な実装
// 修正前の動作を簡略化した表現。runc の逐語的なソースではありません
if err := os.Chdir(config.Cwd); err != nil {
    return err
}
// 不足していた処理:最終 cwd が名前空間ルート内か確認する
堅牢化されたセキュアパッチ
// runc 1.1.12 の境界確認(upstream 抜粋)
if wd, err := unix.Getwd(); errors.Is(err, unix.ENOENT) {
    return errors.New("cwd はコンテナのマウント名前空間ルート外です")
} else if err != nil {
    return fmt.Errorf("cwd の安全性を確認できません: %w", err)
} else if !filepath.IsAbs(wd) {
    return fmt.Errorf("cwd が絶対パスではありません: %q", wd)
}

エンジニアリング&システム堅牢化チェックリスト

参考資料