1 ポイント 投稿者 GN⁺ 2 시간 전 | 1件のコメント | WhatsAppで共有
  • CVE-2026-31431 Copy Failは、ローカルの非特権ユーザーがrootシェルを取得できるようにし、Podmanのルートレスコンテナ内でもコンテナ内部のroot権限昇格が可能になる
  • Podmanのルートレスコンテナは、ユーザーネームスペース、UID分離、Linux capabilitiesを組み合わせて、コンテナ内部のrootをホストの非特権ユーザーにマッピングし、ホスト権限を制限する
  • テストでは、rootless non-rootコンテナのfooユーザーはCopy Failの実行後にコンテナ内部でrootになれたが、権限はホストの非特権ユーザーbarが可能な範囲に制限され、ホスト上のroot所有ファイルは読み取れなかった
  • --security-opt=no-new-privileges--cap-drop=allを適用すると、Copy Fail実行後もシェルはfooかつcapabilities noneの状態に維持され、即座のrootシェル取得とcapability昇格を防げる
  • Copy Failの影響はコンテナのライフサイクルを超えて残る可能性があるため、カーネルのパッチ適用と再起動が必要であり、読み取り専用ルートファイルシステム、cgroupsのリソース制限、薄いランタイムイメージ、ファイアウォールといった多層防御を併用すべきである

Copy FailとPodmanルートレスコンテナの露出範囲

  • CVE-2026-31431は4月29日にcopy.failで公開され、公開済みのPythonスクリプトを実行すると、ローカルの非特権ユーザーがrootシェルを取得できる
  • Copy FailはLinuxコンテナ内でも悪用可能で、Podmanルートレスコンテナでもコンテナ内部のrootシェル取得が可能である
  • テストでは、コンテナrootはホスト側ではコンテナを実行した非特権ユーザーbarの権限範囲に制限された
  • Podmanのルートレス実装は、ユーザーネームスペースUID分離Linux capabilitiesを組み合わせて、コンテナプロセスのホスト権限を制限する
  • Copy Failは、ルートレスコンテナもこの脆弱性に対して無縁ではない一方、Podmanの設定によって侵害後の攻撃範囲を縮小できることを示している

ルートレスコンテナの仕組み

  • 基本例: 非特権ユーザーbarがHTTPサーバーを実行

    • 例の環境は、UID 1001の非特権ユーザーbarがPodmanでubuntu:latestベースのイメージをビルドし、python3 -m http.serverを実行する構成である
    • ホストでpsを見ると、python3プロセスはユーザーbar所有で実行されている
    • Podmanはfork/execモデルを使用するため、コンテナプロセスはpodman runプロセスの子孫となり、一般的なUID分離によってコンテナプロセスをホストrootや他ユーザーから分離できる
    • 一般的なDocker構成では、非特権ユーザーがdocker runを実行しても、Dockerクライアントはroot権限のデーモンと通信し、デーモンが最終的にコンテナプロセスを生成するため、ホスト上ではコンテナプロセスがrootとして見える場合がある
  • ルートレス rootful

    • コンテナイメージは、明示的なUSERディレクティブや--userフラグがない場合、通常はコンテナコマンドを内部rootとして実行する
    • podman topの出力では、HTTPサーバープロセスはホストユーザー1001にマッピングされるが、コンテナ内部ユーザーとしてはrootで実行される
    • この構成は、ホストでは非特権ユーザーとして実行される一方、コンテナ内部ではrootであるrootless rootful状態である
  • ユーザーネームスペース

    • Podmanルートレスコンテナはユーザーネームスペースを使って、コンテナ内外のUID/GIDを異なる形でマッピングする
    • 例では、コンテナ内部UID 0rootは、ホストのUID 1001barにマッピングされる
    • /etc/subuidbar:165536:65536設定は、barのネームスペースプロセスに割り当て可能なUID範囲を定める
    • 例では、barのUID 1001に加えて、165536から231072までのUIDがbarプロセスに割り当て可能である
    • コンテナ内部ユーザーwww-datasleepを実行すると、内部ではwww-dataだが、ホストでは165568と表示される
    • podman unshareでユーザーネームスペースに入ると、ホストでbar:bar所有のホームディレクトリが、ネームスペース内部ではroot:rootとして見える
    • Dockerもユーザーネームスペースをサポートするが別途設定が必要で、かつ1つのユーザーネームスペースしか許可されない一方、Podmanは各UNIXユーザーのルートレスコンテナをそのユーザーネームスペース内で実行する
  • 権限操作とLinux capabilities

    • PodmanはLinux capabilitiesを使って、コンテナプロセスに細分化されたroot権限を付与する
    • イメージビルド中のapt installのような処理は、chowndac_overridefownersetgidsetuidnet_bind_servicesys_chrootといったcapabilityの組み合わせで可能になる
    • podman build --cap-drop=allで全capabilityを削除すると、aptsetgroupssetegidseteuidchownなどに失敗し、イメージビルドが失敗する
    • 必要なcapabilityだけを追加する方式も可能で、例ではCAP_SETUID,CAP_SETGID,CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNERを追加してパッケージインストールを行っている
    • デフォルト実行状態のHTTPサーバーはコンテナ内部rootとして動作し、CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOTのような多くのeffective capabilitiesを持つ
    • HTTPサーバーにはこのような権限は不要なので、podman run --cap-drop=allで全capabilityを削除でき、このときpodman topではeffective capabilitiesがnoneと表示される
  • ルートレス non-root

    • コンテナ内部でも非特権ユーザーとしてHTTPサーバーを実行するには、既存の/etc/passwdのユーザー、たとえばwww-dataを使うか、イメージビルド中に専用ユーザーを作成できる
    • 例では、UID 1002fooユーザーとグループを作成し、/var/www/htmlに読み取り権限を付与したうえでUSER foo:fooを設定する
    • このイメージを--cap-drop=allで実行すると、プロセスはコンテナ内部でfoo、ホストUID 166537、effective capabilities noneの状態になる
    • コンテナプロセスは必要最小限の権限で実行すべきであり、たとえばfooが特権ポート80にバインドする必要があるなら--cap-add=CAP_NET_BIND_SERVICEを追加すべきである
    • コンテナの実行形態は4つに分けられる
      • rootホストユーザー + コンテナroot: root rootful
      • rootホストユーザー + コンテナ非特権ユーザー: root non-root
      • 非特権ホストユーザー + コンテナroot: rootless rootful
      • 非特権ホストユーザー + コンテナ非特権ユーザー: rootless non-root
    • Podmanはrootless rootfulコンテナの実行を容易にし、コンテナプロセスを非特権ユーザーとして実行できるのであれば、rootless non-root構成も比較的容易に構築できる

バインドマウントとUID分離

  • ホストディレクトリをコンテナにマウントすると、ホスト root、ホスト bar、名前空間 foo が所有するファイルにアクセスできるかどうかは、UIDマッピングによって異なる
  • 例では、/var/lib/bar/test ディレクトリにホスト root 所有の root.txt、ホスト bar 所有の bar.txt を作成し、コンテナから /test として読み書き可能でマウントする
  • コンテナを foo として実行すると、ホスト bar 所有のファイルはコンテナ内部では root:root に見え、ホスト root 所有のファイルは名前空間にマッピングされないため nobody:nogroup に見える
  • コンテナ内部の foobar.txtroot.txt を読めず、rootless non-root は rootless rootful より追加の分離を提供する
  • foo がマウントディレクトリに作成した foo.txt は、ホストでは UID 166537 所有と表示され、ホストユーザー bar はそのファイル内容を読めない
  • コンテナを内部 root として実行すると、名前空間 root はホスト bar 所有ファイルと foo 所有ファイルを読めるが、ホスト root 所有ファイルは読めない
  • 内部 root として実行しつつ --cap-drop=all を適用すると、foo のファイルも読めなくなり、ホスト bar 所有ファイルだけを読める

Copy Failテスト

  • テスト条件

    • Copy Failテストでは、もともと公開されていたコミット 8e918b5 のエクスプロイト版が使用される
    • 例示用コンテナイメージは、既存のHTTPサーバーイメージに curl を追加し、コンテナ内でエクスプロイトスクリプトをダウンロードできるようにしている
    • イメージ名は copyfail としてビルドされる
    • テストカーネルは Debian の 6.12.74+deb13+1-amd64 であり、Debian基準では最近のバージョンのうち 6.12.85 未満であれば、まだパッチ未適用のカーネルとして使用可能とみなせる
    • 一般に、非特権ユーザー foosu を呼び出すと、root パスワードが要求される
    • 各テストでは、コンテナユーザーが /tmp に Copy Failスクリプトをダウンロードして実行し、root シェルを得たら sleep を呼び出す
    • Copy Failはコンテナのライフサイクルを超えて持続するため、各テストの前にVMを再起動する
  • rootless rootfulでの結果

    • --user=root でコンテナを実行すると、コンテナ内部プロセスはすでに root である
    • この状態でCopy Failスクリプトを実行して su を呼び出すと uid=0(root) シェルを得るが、root ユーザーはもともとパスワードなしで su により別のrootシェルを開けるため、Copy Failが実質的に追加するものはない
    • podman top では /bin/bashpython3 copy_fail_exp.pysusleep はすべてコンテナ内部では root、ホストではユーザー 1001 と表示される
    • 同一のcapabilityセットが維持され、CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT が見える
    • 内部 root はマウントされた /testbar.txtfoo.txt は読めるが、ホスト root 所有の root.txt は読めない
  • rootless non-rootでの結果

    • コンテナを foo として実行した後にCopy Failスクリプトを実行して su を呼び出すと、コンテナ内部 root へ権限昇格する
    • 結果のシェルの iduid=0(root) gid=1002(foo) groups=1002(foo) と表示される
    • podman top では、初期の /bin/bash、エクスプロイト実行プロセス、su 呼び出しは、ホストUID 166537、コンテナユーザー foo、capabilities none の状態で見える
    • 権限昇格後の [sh]sleep は、ホストではユーザー 1001、コンテナではユーザー root と表示され、rootless rootful と同じcapabilityセットを得る
    • 権限昇格したコンテナ root でも、ホスト root 所有の root.txt は読めない
    • この状態ではコンテナは侵害されているが、攻撃範囲はコンテナと、ホストの非特権ユーザー bar が可能な範囲に制限される
  • no-new-privileges 適用時の結果

    • Podmanでは --security-opt=no-new-privileges によって、コンテナプロセスが起動時点より多くの権限を得られないようにできる
    • rootless non-rootコンテナにこのオプションを適用してCopy Failを実行すると、シェルは開くが依然として uid=1002(foo) のままである
    • podman top でも、すべてのプロセスはホストUID 166537、コンテナユーザー foo、capabilities none のまま維持される
    • マウントされた /test でも、foo は自分のファイルだけ読め、bar.txtroot.txt は読めない
    • コンテナは侵害されているが、内部の非特権ユーザー foo と capabilityなしの状態に制限される
  • --cap-drop=all 適用時の結果

    • rootless non-rootコンテナを --cap-drop=all で実行しても、foo はもともとcapabilityを持たない
    • この状態でCopy Failを実行して su を呼び出すと、開いたシェルは uid=1002(foo) のまま維持される
    • podman top でも /bin/bash、エクスプロイト実行、su、シェル、sleep はすべて foo かつ capabilities none の状態である
    • エクスプロイトは root シェルの取得に失敗し、foo/test で自分のファイルだけ読める
    • この結果は no-new-privileges テストと似ており、両方の対策を併用することで capability 露出を効果的に減らせる
  • エクスプロイトの持続性

    • 即時の root シェルと capability取得は no-new-privileges--cap-drop=all で防げたが、エクスプロイト自体の効果は残る
    • その後、capability制限なしで新しいコンテナを実行すると、非特権コンテナユーザー foosu を呼び出すだけでコンテナ root になれてしまう
    • したがって、カーネルのパッチ適用と再起動は依然として必要である

多層防御戦略

  • 読み取り専用イメージ

    • podman run--read-only を追加すると、コンテナのルートファイルシステムが読み取り専用でマウントされる
    • Podman はデフォルトで /tmp/run/var/tmp のような一部ディレクトリを書き込み可能でマウントするため、完全に読み取り専用にするには --read-only-tmpfs=false も追加する必要がある
    • 読み取り専用コンテナが侵害された場合、システムへの書き込みが許可されないため、エクスプロイト後の一部攻撃を制限できる
    • ただし curl の出力を python3 にパイプできるため、読み取り専用設定だけではエクスプロイトの実行自体は防げない
    • 例の python3 HTTP サーバーはファイルシステムへの書き込みを必要としないため、このオプションを安全に使える
    • 多くの事前ビルド済みイメージは特定ディレクトリへの書き込みアクセスを前提としているため、読み取り専用ルートファイルシステムでは正常に動作しない可能性がある
    • 読み取り専用ルートファイルシステムは、コンテナに接続された書き込み可能ボリュームとは独立しており、侵害時にもそのマウントディレクトリには引き続き書き込める
  • リソース制限

    • Docker と Podman は cgroups を使って、コンテナに提供されるリソースを制限できる
    • コンテナに無制限のメモリ、CPU、PID は必要ない
    • podman stats でコンテナのリソース使用量を確認し、それに合わせて制限を適用できる
  • 利用可能なバイナリの制限

    • 例では簡略化のために ubuntu イメージを使っているが、ubuntu イメージには侵害時に攻撃者が利用できる多くのバイナリが含まれている
    • HTTP サーバーの実行には、そのようなバイナリの大半は不要である
    • ランタイムイメージは可能な限り薄く構成するのが望ましい
    • マルチステージビルドを使えば、ビルド時環境とランタイム環境を分離できる
    • python3 のような用途別イメージ、Debian の -slim バリアント、alpine のようなより小さなディストリビューションをベースにできる
    • コンテナプロセスと互換性があるなら、distroless imagesscratch を使って、シェル、パッケージマネージャー、システムユーティリティのないランタイムを作れる
  • ファイアウォール

    • iptablesnftables を使って、コンテナプロセスをファイアウォールで制限 できる
    • コンテナプロセスに本当に必要な受信・送信接続だけを許可すべきである
    • HTTP サーバーの例では、DNS やローカル・リモートサーバーへの接続は不要なため、確立済みの受信接続から来る tcp パケットだけを許可するように制限できる

運用上の意味

  • 標準的な Podman ルートレスコンテナは、標準的な Docker コンテナ構成よりも優れた隔離手段をデフォルトで提供する
  • Docker でも rootless 実行非特権ユーザー名前空間の利用 は可能だが、Podman より多くの設定作業が必要であり、アーキテクチャの違いも影響する
  • Docker は依然として広く使われており、DokkuKamalCoolifyDokploy のようなセルフホスティングツールもデフォルトで Docker を使っている
  • Docker Hub のイメージを十分に精査せずに実行したり、ロックダウン措置を適用しなかったりすると、必要以上に広い攻撃面を持つ状態でサービスが稼働することになる
  • コンテナイメージの実装詳細を理解する必要がある
    • どのユーザー、または複数ユーザーのうち誰がコンテナプロセスを実行するのか把握する必要がある
    • コンテナプロセスがルートファイルシステム上のどのディレクトリに依存しているか把握する必要がある
    • 必要な Linux capabilities と不要な capabilities を区別する必要がある
  • Podman とコンテナが提供する複数のメカニズムを組み合わせることで、コンテナを強化し、侵害時の影響範囲を縮小できる
  • ワークロードによっては、コンテナを唯一のセキュリティ境界として頼るべきではない
  • コンテナと別個の物理・仮想マシンを併用すれば、効果的に分離できる
  • Podman は、同一ホスト内でも各ワークロードを別々の非特権ユーザーと独自のユーザー名前空間で実行して隔離する方法を提供する

追加資料

1件のコメント

 
GN⁺ 2 시간 전
Lobste.rs の意見
  • 公開されたエクスプロイトよりも、脆弱性を成立させている根本的な挙動に注目すべき
    この脆弱性は読み取り専用かどうかに関係なくページキャッシュへ書き込めるため、悪意あるコンテナが overlayfs のベースイメージファイルに属するページを改変でき、コンテナのデプロイ方法によってはその影響が別のコンテナにも及びうる
    ここでの rootless 構成であれば、ホストシステム上で同じユーザーとして実行されている別のコンテナが対象になる
    別のエクスプロイト手法としては、すでに使われていることが分かっているベースイメージベースのコンテナを起動または発見し、そのコンテナ内のページキャッシュを改変したうえで、同じランタイムと overlayfs データを共有する別のコンテナにそのコードを実行させることができる
    rootless とユーザーネームスペースは重要だが、この件ではあまり役に立たず、copy.fail サイトが述べているようにコンテナでは seccomp で socket(AF_ALG, ...) システムコールをブロックすることを検討すべき

    • 根本的な挙動自体までは深く考えておらず、侵害されたコンテナの露出範囲を評価するために、rootless コンテナが提供するネームスペースや capability の整理により注意を向けていた
      「コンテナのデプロイ方法によっては」が具体的に何を意味するのか、もう少し説明してもらえるとうれしい
      rootless Podman の利点は、ワークロード次第ではホスト上で同じユーザーとしてコンテナを動かす必要がない点にある
      メインのワークステーションユーザーとして複数の rootless コンテナを動かすケースを指しているなら同意するが、サーバーではそれぞれを別ユーザーに分離でき、同じコンテナイメージでも異なる非特権ユーザーで実行できる
      ほとんどを root で動かす Docker のデフォルトとはかなり異なるが、これが究極のセキュリティ境界ではないことも記事の末尾に書いたし、複数の非特権ユーザーに rootless コンテナを分散して使う方式が適切かどうかは用途による
      特定のワークロードは VM に分離して使っている
      rootless とユーザーネームスペースがここで役に立たないというのは、エクスプロイト防止の話を指しているのか気になる
      seccomp については、まだコンテナに明示的なポリシーを書いたことがないので扱わなかったが、さらに調べる良いきっかけになった
  • Podman と rootless コンテナは好きだが、CopyFail を見て兄弟コメントと同じ結論に至った
    podman+rootless の追加的なアクセス制御の利点があっても、結局のところコンテナはセキュリティ境界ではないという古典的な助言を再確認することになり、カーネルエクスプロイトが 1 つあればすべて破られうる
    趣味でシステム管理をしている程度だが、この分野の新しい流れとして libkrun backend for crun with podman を見た
    ほとんどのコンテナ化されたワークロードをそのまま扱いつつ、内部的には別個のゲストカーネルを持つMicroVMで実行されるという触れ込みだが、成熟度・実運用での検証・セキュリティ監査の水準はよく分からず、一部はかなり最先端に見える
    MicroVM は LLM コーディングツールで積極的に採用されているので、そうした状態が長く続くかもしれない
    podman machine も有望に見えたが、残念ながら開発者ワークステーション用途しか想定しておらず、ホストシステムごとにコンテナ実行 VM を 1 つだけ置くモデルだった
    それでも「コンテナはセキュリティ境界ではない」という言い方は単純化しすぎだと思う。コンテナは確かにセキュリティ境界だが、私たちが信じたいほど強固ではないだけだ

    • クラウドのコンテナデプロイの大半が VM を使う理由も同じだ。VM は防御可能な境界である
      ローカルデプロイではこの線引きはやや曖昧になる
      ハードウェアの観点では、VM がプロセスより本質的に安全というわけではないが、3 つの理由で境界としてより防御しやすい
      VM エスケープはシステムコールよりも一般的ではないため、性能低下なしにより多くのサイドチャネル緩和策を適用する余地がある
      VM のホストインターフェースははるかに単純だ。ブロックデバイスにはブロック単位の読み書きインターフェースがあり、ネットワークデバイスはフレームを送受信する
      Linux や *BSD がソケットに提供する setsockopt 呼び出しは、大半のエミュレーション・準仮想化ドライバよりはるかに大きな攻撃面であり、それですらカーネル全体の攻撃面のごく一部にすぎない
      VM インターフェースは状態もずっと少ない。リクエスト・レスポンス型のリングに進行中トランザクションがあるくらいで、そのほかはほとんどない
      認証情報、UID、GID、ファイルディスクリプタテーブルのようなものはカーネルに状態ベースの複雑さを加え、バグがあればプロセスがそれを悪用できる
      ワークステーション向けバリエーションの難しさは、こうした複雑さを再び持ち込んでしまう点にある
      たとえばコンテナのベースレイヤーは不変ファイルシステムを格納したブロックデバイスとして公開できるが、ボリュームや共有フォルダはおそらく 9pfs や VirtIO-FS、つまり VirtIO 上の 9p または FUSE としてマウントされるだろう
      すると攻撃面は再び広がる
      運が良ければエクスプロイトチェーンが必要になる
      FreeBSD 側のほうに詳しいのだが、通常は準仮想化・エミュレーションデバイスを提供するコンポーネントを Capsicum でサンドボックス化するので、まずホストプロセスを乗っ取り、その後で VM がアクセス権を持たなかったものにアクセスするにはカーネルまで突破しなければならない
      しかしこの追加のサンドボックス化をしなければ、コンテナエスケープはユーザーができることをすべて行える世界に戻ってしまい、デスクトップで root が破られたのと大差なくなる
    • Kata Containers と Firecracker はすでにかなり長い歴史のある技術で、研究者たち検証してきたので、十分に成熟していると見てよいと思う
      個人的には gVisor のほうが好みだ。VMM ランタイムではないが何年も存在しており、Tencent のような企業でも使われていて、すべてのコンテナをすでに Proxmox VM の中で動かしている自分の環境にはよく合う
      もう 1 つ試しているのが syd-oci で、MicroVM や gVisor という定番の推奨先に比べるとやや注目度が低いように思う
    • その表現は私の経験にも合っていて、この記事を書く過程もその事実を受け入れる練習に近かった
      libkrun の参考資料もありがとう。有望な可能性に見える
    • LLM コーディングツール分野での積極的な採用は、MicroVMをさらに成熟させ、実運用での検証や強化につながる可能性が高い
      セキュリティ監査につながる可能性も高そうだ