
Docker 向けの CVE-2026-31431(「Copy Fail」)緩和策。Kubernetes テンプレートも含まれます。
ホスト上のすべてのDockerコンテナに対してAF_ALGソケットの作成をブロックする冪等スクリプト。CVE-2026-31431("Copy Fail")の緩和策です。
CVE-2026-31431は、Linuxカーネルのauthencesn暗号テンプレートにおけるローカル権限昇格の脆弱性で、2017年から上流の修正(mainlineコミットa664bf3d603d)が利用可能になるまでの間にビルドされたカーネルに存在します。非特権ユーザーは、AF_ALGソケット操作をsplice()と連鎖させることで、読み取り可能な任意のファイルのページキャッシュへの制御された4バイト書き込みを実行し、setuidバイナリを標的にしてrootシェルを取得できます。732バイトのPython proof of conceptは、影響を受けるカーネルを搭載したすべての主要なLinuxディストリビューションで、レースやディストリビューションごとのオフセットなしに確実に悪用できます。
このエクスプロイトの必須の最初のステップは、AF_ALGソケット(socket(AF_ALG, SOCK_SEQPACKET, 0))を開くことです。seccompを介してこのsyscallをブロックすると、パッチが適用されていないカーネルでも悪用を防ぐことができます。Dockerの組み込みのデフォルトseccompプロファイルはAF_ALGをブロックせず、RuntimeDefaultでは不十分です。テストしたクラスターでは、PSS Restrictedの下で許可されたポッドでもAF_ALGソケットを開くことができることが示されました。
完全な技術的詳細とディストリビューションごとのパッチの入手可能性については、元の研究者のアドバイザリhttps://copy.failおよびCERT-EUのアドバイザリhttps://cert.europa.eu/publications/security-advisories/2026-005/を参照してください。
このスクリプトは、Docker Engineのグローバルデーモン設定を対象としています。Kubernetesについては、以下のKubernetesセクションを参照してください。ベアメタルまたはVMワークロード(コンテナ化されていない)の場合は、代わりにalgif_aeadカーネルモジュールを無効にしてください:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
この方法は、dm-crypt/LUKS、kTLS、IPsec/XFRM、OpenSSL、GnuTLS、NSS、SSHには影響しません。
Dockerのアクティブな組み込みseccompプロファイルを抽出します。短命コンテナのHostConfig.SecurityOptを検査することで行います。これにより、リモートURLへの依存を回避し、ベースプロファイルが実際にインストールされているDockerバージョンと一致することが保証されます。moby/profilesからのGitHubフェッチは、コンテナの検査で何も得られなかった場合のフォールバックとしてのみ使用されます。
プロファイルをパッチします。Dockerの許可リストエントリからsocketを削除し、SCMP_CMP_NEを使用してAF_ALG(値38)以外のすべてのアドレスファミリを許可する引数フィルタを付けて再追加します。他のすべてのDockerデフォルトseccomp動作は維持されます。
パッチされたプロファイルを原子的に書き込みます。/etc/seccomp/docker-block-af-alg.json(一時ファイル+リネーム)に書き込みます。ディスク上のコンテンツがすでに同一の場合はスキップされます。
/etc/docker/daemon.jsonを更新して、"seccomp-profile"をパッチされたプロファイルパスに設定します。元のファイルは最初の変更時にdaemon.json.bakにバックアップされます。すでに正しく設定されている場合はスキップされます。
dockerdをリロードします。(SIGHUP — 再起動は不要)を使用します。どちらのファイルも変更されていない場合はスキップされます。
このスクリプトは冪等です。複数回実行しても同じ結果が得られ、実際に何かが変更された場合にのみDockerをリロードします。
注:
--privilegedコンテナは、この設定に関係なくすべてのseccompプロファイルをバイパスします。Composeファイルと特権コンテナの実行コマンドを個別に監査してください。
PATH内のdocker CLIPATH内のcurl(フォールバックのみ)systemctl(systemdホスト)/etc/seccompおよび/etc/dockerへの書き込み、およびsystemctl reload dockerのためのroot / sudo# 緩和策を適用して検証(通常の使用法)
sudo python3 harden-docker-seccomp.py
# 何も書き込まず、Dockerをリロードせずに変更内容を表示
sudo python3 harden-docker-seccomp.py --dry-run
# コンテナ検証のみを再実行(設定変更なし)
python3 harden-docker-seccomp.py --verify-only
# 詳細出力
sudo python3 harden-docker-seccomp.py --verbose
INFO Written: /etc/seccomp/docker-block-af-alg.json
INFO Updated: /etc/docker/daemon.json
INFO Reloading Docker daemon (SIGHUP)…
INFO Docker daemon reloaded.
INFO Verifying AF_ALG is blocked inside a container…
OK: AF_ALG blocked — [Errno 1] Operation not permitted
INFO All done. CVE-2026-31431 (Copy Fail) mitigation is active.
INFO Profile unchanged: /etc/seccomp/docker-block-af-alg.json
INFO daemon.json already configured correctly.
INFO No changes — Docker daemon reload not required.
INFO Verifying AF_ALG is blocked inside a container…
OK: AF_ALG blocked — [Errno 1] Operation not permitted
INFO All done. CVE-2026-31431 (Copy Fail) mitigation is active.
| コード | 意味 |
|---|---|
0 | 成功 — 緩和策がアクティブ |
1 | スクリプトがrootとして実行されていない(パッチ適用時)、または回復不能なエラー |
2 | 検証失敗 — AF_ALGがブロックされていない |
Kubernetesポッドはホストカーネルを共有するため、影響を受けるノード上の任意のポッドから同じAF_ALGソケットプリミティブに到達できます。RuntimeDefault seccompでは不十分です。テストしたクラスターでは、PSS Restrictedの下で許可されたポッドでもAF_ALGソケットを開くことができることが示されました。明示的な拒否ルールを持つLocalhostプロファイルが必要です。
緩和には2つのことが必要です:プロファイルJSONがすべてのノードのファイルシステムに存在すること、そしてすべてのポッド仕様がそれを参照することです。以下のセクションでは、個々のポッド仕様を変更せずにプロファイルをグローバルに注入する方法を含め、両方を説明します。
kubeletはLocalhost seccompプロファイルを、デフォルトで/var/lib/kubelet/seccompであるseccompルートを基準に解決します。プロファイルは、ワークロードをスケジュールする可能性のあるすべてのノードのそのパスに存在する必要があります。
このリポジトリからConfigMapとDaemonSetを適用します:
kubectl apply -f templates/configmap-seccomp-profile.yaml
kubectl apply -f templates/daemonset-distribute-profile.yaml
DaemonSetは、ConfigMapからノードのkubelet seccompルートにプロファイルをコピーするinitコンテナを実行し、その後、ヘルスモニタリングのためにポッドが表示されたままになるように最小限のpauseコンテナを配置します。すべてのテイントを許容するため、コントロールプレーンノードでも実行されます。
非標準のkubelet seccompルート: RKE2は
/var/lib/rancher/rke2/agent/kubelet/seccompを使用します。適用前にDaemonSetのinitコンテナenvでNODE_SECCOMP_ROOTを設定してパスを上書きします。
ノード上にファイルが存在することを確認します:
kubectl -n kube-system exec -it \
$(kubectl -n kube-system get pod -l app.kubernetes.io/name=distribute-af-alg-seccomp \
-o jsonpath='{.items[0].metadata.name}') -- \
cat /var/lib/kubelet/seccomp/block-af-alg.json
個々のポッド仕様やHelmチャートを変更する代わりに、変更するアドミッションウェブフックを使用して、アドミッション時にseccompProfileを自動的に注入します。KyvernoとOPA Gatekeeperの2つのオプションが提供されています。
どちらのアプローチも、ポッドがすでにプロファイルを宣言していない場合にのみプロファイルを注入するため、明示的なプロファイルを持つポッドは変更されません。
重要: 既存の実行中ポッドは遡及的に変更されません。ポリシーを適用した後、デプロイメントをロールして注入されたプロファイルを適用してください:
kubectl rollout restart deployment -A
このテンプレートは、Kyverno 1.17でGAに達したMutatingPolicy API(policies.kyverno.io/v1)を使用します。レガシーのClusterPolicy API(kyverno.io/v1)はKyverno 1.17(2026年1月)で非推奨となり、1.20(2026年10月)で削除される予定です。新しいポリシーには使用しないでください。
matchConditions CEL式は、変更する前にseccompProfileが存在しないことをチェックするため、すでにプロファイルを宣言しているポッドは変更されません。
# Kyvernoをインストール(まだ存在しない場合)
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm upgrade --install kyverno kyverno/kyverno -n kyverno --create-namespace
# ポリシーを適用
kubectl apply -f templates/kyverno-mutate-seccomp.yaml
新しいポッドが注入されたプロファイルを受け取ることを確認します:
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# 期待される結果: {"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
templates/kyverno-mutate-seccomp.yamlを参照してください。
GatekeeperのAssignミューテーションCRDは、pathTests条件を使用して、spec.securityContext.seccompProfileがまだ存在しない場合にのみプロファイルを注入します。ミューテーションはGatekeeper 3.10+以降安定しています。機能フラグは不要です。
# Gatekeeperをインストール(まだ存在しない場合)
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install -n gatekeeper-system gatekeeper gatekeeper/gatekeeper \
--create-namespace
# ミューテーションを適用
kubectl apply -f templates/gatekeeper-assign-seccomp.yaml
確認:
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# 期待される結果: {"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
templates/gatekeeper-assign-seccomp.yamlを参照してください。
Gatekeeper vs. Kyverno:
Assignアプローチはフィールドレベルで動作し、Gatekeeperのミューテーションウェブフックを有効にする必要があります。KyvernoのMutatingPolicyとCELmatchConditionは、条件付きをインラインで処理します。どちらも同じ結果を達成します — クラスターにすでにデプロイされている方を優先してください。
hostPID: true、hostNetwork: true、またはsecurityContext.privileged: trueを持つポッドは、seccompだけでは完全に封じ込められない昇格されたアクセス権を持ちます。これらのワークロードを個別に監査し、可能な場合は特権を削除してください。
ConfigMapを信頼できる情報源として。 プロファイルJSONは、DaemonSetに埋め込んだり、ファイル間で複製したりする代わりに、configmap-seccomp-profile.yamlに格納されています。DaemonSetはそれをマウントしてノードにコピーします。プロファイルを更新するには、1つのConfigMapを編集してDaemonSetポッドを再起動するだけです — 他のファイルは変更されません。
DaemonSetはsystem-node-critical優先度を使用します。 これにより、保護するワークロードがスケジュールされる前に配布ポッドが退避されず、ノードにプロファイルファイルがなく、ポッドがCreateContainerErrorでスタックすることを防ぎます。
Gatekeeperはkube-systemとgatekeeper-systemを除外します。 DaemonSetのインストールより前から存在する可能性のあるシステムポッドにLocalhostプロファイルを注入すると、ファイルがノード上にまだ存在しない場合に壊れたプロファイル参照のリスクがあります。Kyvernoポリシーはウェブフックの順序をより適切に処理するためこの除外は不要ですが、必要に応じてexcludeルールをそこに追加することもできます。
条件付き注入であり、上書きではありません。 KyvernoのMutatingPolicy CEL matchConditionとGatekeeperのMustNotExistパステストの両方は、アドミッションポリシーが既存のseccompProfileを持たないポッドにのみ作用することを意味します。独自のプロファイルをすでに宣言しているワークロード — カスタム許可リストを介してAF_ALGを正当に必要とするものを含む — は変更されません。
https://gist.github.com/devstuff/3ff3ca0139e2a2da7f1ee5875802f76f
systemctl reload dockerブロックがアクティブであることを検証します。上記の手順で変更が行われたかどうかに関係なく、コンテナ内でプローブを実行して確認します。