
BPF-LSMによるCVE-2026-31431(Copy Fail)の緩和策 — クラスタ全体でAF_ALGソケットの作成を拒否します
CVE-2026-31431(「Copy Fail」)および
Dirty Frag の RxRPC 亜種、ならびに
AF_ALG または AF_RXRPC ソケットファミリ経由で到達可能なカーネル側インプレース暗号パスへのユーザー空間アクセスに依存する類似の権限昇格脆弱性に対する BPF-LSM 緩和策です。
小さな DaemonSet が、すべてのノードの socket_create フックに単一の BPF-LSM プログラムをアタッチします。このプログラムは、プロセスのケーパビリティ、名前空間、seccomp プロファイルに関係なく、ユーザー空間からの socket(AF_ALG, ...) または socket(AF_RXRPC, ...) 呼び出しに対して -EPERM を返します。カーネル内部の sock_create_kern() 呼び出し元(例: fs/afs、IPsec スタック)は許可されるため、正当なカーネル内ユーザーは引き続き動作します。
Talos Linux(v1.10 以降、CONFIG_BPF_LSM=y とデフォルトの LSM スタックに bpf を同梱)でテスト済み。同じカーネル構成の任意のディストリビューションで動作します。
Copy Fail(CVE-2026-31431)は algif_aead の論理欠陥で、権限のないローカルユーザーが任意の setuid バイナリに対して 4 バイトのページキャッシュ書き込みを行い、732 バイトの Python スクリプトで root を取得できるものです。このエクスプロイトに必要なのは AF_ALG と splice() のみで、どちらもデフォルトで任意の権限のないプロセスから到達可能です。本流の修正は a664bf3d603d です。
Dirty Frag は、同じ研究系統によって 2026 年 5 月に公開された後続の脆弱性クラスです。sk_buff の frag メンバーを「汚染」する 2 つのバグ — xfrm-ESP Page-Cache Write と RxRPC Page-Cache Write — を連鎖させます。RxRPC 亜種は rxkad_verify_packet_1() 内で splice() によりピン留めされたページキャッシュページに対してインプレースの pcbc(fcrypt) 復号を実行し、ユーザー名前空間の作成を必要とせずに root に到達するため、堅牢化されたディストリビューションではチェーンのより普遍的に悪用可能な半分となります。xfrm-ESP の修正は netdev に f4c50a4034e6(2026-05-07)として入りました。執筆時点でディストリビューションはまだバックポート中であり、RxRPC には公開された修正がまだありません — 開示のタイムラインについては上流の解説を参照してください。
どちらのエクスプロイトも、影響を受けるファミリのソケットを開くことに依存しています。カーネルの修正がディストリビューションに届くまで、ユーザー空間が AF_ALG または AF_RXRPC ソケットを作成できないようにすることで攻撃対象領域を除去できます。代替案との比較:
本プロジェクトは再起動不要の選択肢です。クラスタ全体で実行し、通常のパッチサイクルで恒久的なカーネル修正を計画してください。
Dirty Frag の ESP 亜種に関する注意。 Dirty Frag の
xfrm-ESP Page-Cache Writeの半分は、この DaemonSet では閉じられません — これは専用のソケットファミリではなく XFRM netlink +UDP_ENCAP_ESPINUDPを介してトリガーされ、これに対するクリーンな BPF-LSM フィルタはホスト上の正当な IPsec を壊すか、ユーザー名前空間を認識するロジックを必要とします。現在ここでは追跡されていません — 貢献を歓迎します。権限のないユーザー名前空間をブロックする堅牢化されたディストリビューション(例: Ubuntu のデフォルト AppArmor ポリシー)では、ESP 亜種はそもそも到達不能であり、ここでの RxRPC ブロックで十分です。
bpf/blocker.c は短い BPF-LSM プログラムです:
SEC("lsm/socket_create")
int BPF_PROG(block_socket_family, int family, int type, int protocol,
int kern, int ret)
{
if (ret)
return ret;
/* kern != 0 means sock_create_kern() — let in-kernel callers through. */
if (!kern && (family == AF_ALG || family == AF_RXRPC)) // 38, 33
return -EPERM;
return 0;
}
Go ローダー(main.go、約 40 行)がプログラムをロードし、bpf(BPF_LINK_CREATE) 経由でアタッチします。リンクはポッドの存続期間中保持されます。SIGTERM でリンクが閉じられ、フックがデタッチされます。
CONFIG_BPF_LSM=y でビルドされ、アクティブな LSM スタックに bpf が含まれるカーネル(カーネルコマンドラインの lsm=...,bpf)が必要です。Talos Linux は v1.10 以降、両方をデフォルトで有効にしています。
kubectl apply -f https://raw.githubusercontent.com/cozystack/copy-fail-blocker/v0.3.0/manifests/copy-fail-blocker.yaml
main の最新コミットの場合(未リリースの変更を含む可能性があります):
kubectl apply -f https://raw.githubusercontent.com/cozystack/copy-fail-blocker/main/manifests/copy-fail-blocker.yaml
チャートは OCI アーティファクトとして公開されていません(レジストリパスはコンテナイメージと共有されています)。タグ付きチェックアウトからインストールします:
git clone --branch v0.3.0 https://github.com/cozystack/copy-fail-blocker
cd copy-fail-blocker
helm upgrade --install copy-fail-blocker charts/copy-fail-blocker \
--namespace kube-system
または Makefile のショートカットを使用:
make apply # helm upgrade --install into kube-system
make diff # preview changes against the cluster
make delete # uninstall
make manifest # regenerate manifests/copy-fail-blocker.yaml
DaemonSet は特権モードで実行する必要があります(BPF プログラムをロードし、bpffs に書き込むため)。特権 Pod Security Standard を持つ名前空間、またはデフォルトで特権である kube-system に配置してください。
対象ノード上の任意のポッドから:
python3 -c '
import errno, socket
# Pass each family with a type the family-specific create() actually
# supports (AF_ALG → SOCK_SEQPACKET, AF_RXRPC → SOCK_DGRAM) so that on
# a node WITHOUT this hook the call would either succeed (FAIL: socket
# created) or fail with a non-EPERM errno — both surface as FAIL below.
# With the hook active, security_socket_create() returns -EPERM before
# pf->create() runs, so the type does not matter; we still pass the
# correct one to keep the FAIL diagnostic unambiguous.
for name, family, stype in [("AF_ALG", 38, socket.SOCK_SEQPACKET),
("AF_RXRPC", 33, socket.SOCK_DGRAM)]:
try:
socket.socket(family, stype, 0)
print(f"FAIL: {name} socket created")
except OSError as e:
if e.errno == errno.EPERM:
print(f"OK ({name}): blocked with EPERM")
else:
print(f"FAIL: {name} got {e.errno} ({e.strerror}), expected EPERM")'
期待される出力:
OK (AF_ALG): blocked with EPERM
OK (AF_RXRPC): blocked with EPERM
他の errno(例: ESOCKTNOSUPPORT 94、EAFNOSUPPORT 97)は、そのノードでフックがアクティブでないことを意味します — 保護されていると想定する前に調査してください。
make image # docker buildx build + push
make image REGISTRY=ghcr.io/myorg TAG=v0.3.0 # custom tag
make image PUSH=0 LOAD=1 # build locally without pushing
make image は解決されたイメージダイジェストで charts/copy-fail-blocker/values.yaml を更新するため、チャートは常にダイジェストでピン留めされます。
ビルド依存関係は Containerfile にあります(clang、libbpf-dev、Go)。ローカルホストに必要なのは docker buildx、helm、yq(mikefarah)、kubectl、helm-diff のみです。
charts/copy-fail-blocker/values.yaml:
AF_ALG と AF_RXRPC が再び到達可能になる短いウィンドウ(数秒)があります。ほとんどの脅威モデルでは許容可能です。そうでない場合は、BPF リンクを bpffs にピン留めすることを検討してください(現在は未実装 — 貢献を歓迎します)。CAP_BPF と CAP_SYS_ADMIN を持つ誰でもフックをデタッチできます。これはクラスタ全体の権限制限の代替ではありません。algif_skcipher / algif_hash などはブロックしません。 プログラムは AF_ALG ファミリ全体を拒否しますが、現在悪用可能と判明しているのは algif_aead のみです。将来の CVE でより細かいフィルタ(例: bind() をフックして salg_type を検査)が必要になった場合、これは簡単に追加できます。AF_ALG または AF_RXRPC ソケットを保持するプロセスには効果がありません。 既存のソケットは閉じられるまで動作し続けます。UDP_ENCAP_ESPINUDP を介して到達されます — 現在は追跡されていません。貢献を歓迎します。の注記を参照してください。Apache License 2.0 — LICENSE を参照してください。
| 緩和策 | 適用範囲 | 再起動? | 永続的? |
|---|
カーネルコマンドライン module_blacklist=af_alg,rxrpc(algif_aead だけでなくファミリハンドラ全体) | ホスト全体 | はい | はい |
/etc/modprobe.d/*.conf に install af_alg /bin/false + install rxrpc /bin/false を記述し、既にロード済みのモジュールを rmmod(上流の Dirty Frag ガイダンスに一致 — 通常の blacklist はカーネル内の request_module() 自動ロードを停止しないことに注意。停止するのは install … /bin/false のみ) | ホスト全体 | いいえ | はい(ファイルが存在する間) |
CRYPTO_USER_API / AF_RXRPC を含まないカスタムカーネル | ホスト全体 | はい | はい |
| ポッドごとのカスタム seccomp プロファイル | ラベル付けされたワークロードのみ | いいえ | はい |
| copy-fail-blocker(本プロジェクト) | ホスト全体のユーザー空間 | いいえ | DS 実行中 |
| キー | デフォルト | 備考 |
|---|
image.repository | ghcr.io/cozystack/copy-fail-blocker | make image により自動更新 |
image.tag | vX.Y.Z@sha256:... | ダイジェストでピン留め、現在値は values.yaml を参照 |
priorityClassName | system-node-critical | デーモンが退避を生き残ることを保証 |
tolerations | [{operator: Exists}] | テイントされたノードを含む全ノードで実行 |
resources.requests | 5m CPU / 16Mi memory | アタッチ後のアイドルフットプリント |
AF_RXRPC クライアントはブロックされます。 RxRPC は AFS ネットワークプロトコルです。!kern ガードにより、ツリー内の fs/afs(kAFS)モジュールは sock_create_kern() 経由でソケットを開くため動作し続けます。socket(2) 経由で直接 AF_RXRPC ソケットを開くユーザー空間 AFS ツール(例: OpenAFS ユーザー空間デーモン)は拒否されます — そのようなツールを実行するノードにはこの DaemonSet をデプロイしないでください。