
BPF-LSM mitigation pour CVE-2026-31431 (Copy Fail) — refuse la création de sockets AF_ALG à l'échelle du cluster
Atténuation BPF-LSM pour CVE-2026-31431 (« Copy Fail ») et
la variante RxRPC de Dirty Frag, ainsi
que pour les vulnérabilités d'élévation de privilèges similaires qui dépendent
d'un accès utilisateur à un chemin de chiffrement en place côté noyau,
accessible via les familles de sockets AF_ALG ou AF_RXRPC.
Un petit DaemonSet attache un unique programme BPF-LSM au hook socket_create
sur chaque nœud. Le programme renvoie -EPERM pour tout appel
socket(AF_ALG, ...) ou socket(AF_RXRPC, ...) utilisateur, quels que
soient les capacités du processus, son espace de noms ou son profil seccomp.
Les appelants internes au noyau sock_create_kern() (par ex. fs/afs, la
pile IPsec) sont autorisés, afin que les utilisateurs légitimes du noyau
continuent de fonctionner.
Testé sur Talos Linux (qui est livré avec CONFIG_BPF_LSM=y et bpf dans la
pile LSM par défaut depuis la v1.10), fonctionne sur toute distribution avec
la même configuration du noyau.
Copy Fail (CVE-2026-31431) est un défaut logique dans algif_aead qui
permet à un utilisateur local non privilégié d'effectuer une écriture de 4
octets dans le cache de pages vers n'importe quel binaire setuid, atteignant
root avec un script Python de 732 octets. L'exploit n'a besoin que de
AF_ALG + splice(), tous deux accessibles depuis tout processus non
privilégié par défaut. Le correctif principal est a664bf3d603d.
Dirty Frag est une classe de vulnérabilités de suivi divulguée en mai 2026
par la même ligne de recherche. Elle enchaîne deux bogues qui « salissent » le
membre frag de sk_buff — xfrm-ESP Page-Cache Write et RxRPC Page-Cache Write. La variante RxRPC effectue un déchiffrement pcbc(fcrypt) en place
sur une page du cache de pages épinglée par splice() dans
rxkad_verify_packet_1() et atteint root sans avoir besoin de créer un espace
de noms utilisateur, ce qui en fait la moitié de la chaîne la plus
universellement exploitable sur les distributions durcies. Le correctif
xfrm-ESP a atterri dans netdev sous le nom f4c50a4034e6 (2026-05-07) ; les
distributions sont encore en train de le rétroporter au moment de la rédaction,
et RxRPC n'a pas encore de correctif public — voir la
note de divulgation en amont
pour la chronologie de divulgation.
Les deux exploits dépendent de l'ouverture d'un socket dans la famille
concernée. Jusqu'à ce que les correctifs du noyau arrivent dans votre
distribution, la surface d'attaque peut être supprimée en empêchant
l'espace utilisateur de créer des sockets AF_ALG ou AF_RXRPC. Par rapport
aux alternatives :
| Atténuation | Couverture | Redémarrage ? | Persiste ? |
|---|---|---|---|
Ligne de commande du noyau module_blacklist=af_alg,rxrpc (gestionnaires de famille, pas seulement algif_aead) | à l'échelle de l'hôte | oui | oui |
/etc/modprobe.d/*.conf avec install af_alg /bin/false + install rxrpc /bin/false et rmmod des modules déjà chargés (conforme aux recommandations Dirty Frag en amont — notez qu'un simple blacklist n'empêche pas l'auto-chargement request_module() dans le noyau, seul install … /bin/false le fait) | à l'échelle de l'hôte | non | oui (tant que le fichier est présent) |
Noyau personnalisé sans CRYPTO_USER_API / AF_RXRPC | à l'échelle de l'hôte | oui | oui |
| Profil seccomp personnalisé par pod | uniquement les charges de travail étiquetées | non | oui |
| copy-fail-blocker (ce projet) | espace utilisateur à l'échelle de l'hôte | non | tant que le DS s'exécute |
Ce projet est l'option sans redémarrage. Exécutez-le à l'échelle du cluster, puis planifiez les correctifs permanents du noyau à votre cadence de correctifs habituelle.
Note sur la variante ESP de Dirty Frag. La moitié
xfrm-ESP Page-Cache Writede Dirty Frag n'est pas fermée par ce DaemonSet — elle est déclenchée via XFRM netlink +UDP_ENCAP_ESPINUDP, pas via une famille de sockets dédiée, et un filtre BPF-LSM propre pour celle-ci casserait soit l'IPsec légitime sur l'hôte, soit nécessiterait une logique consciente des espaces de noms utilisateur. Pas suivi actuellement ici — les contributions sont les bienvenues. Sur les distributions durcies qui bloquent les espaces de noms utilisateur non privilégiés (par ex. la politique AppArmor par défaut d'Ubuntu), la variante ESP est de toute façon inaccessible et le blocage RxRPC ici est suffisant.
bpf/blocker.c est un court programme 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 signifie sock_create_kern() — laisser passer les appelants du noyau. */
if (!kern && (family == AF_ALG || family == AF_RXRPC)) // 38, 33
return -EPERM;
return 0;
}
Le chargeur Go (main.go, ~40 lignes) charge le programme et l'attache via
bpf(BPF_LINK_CREATE). Le lien est maintenu pendant toute la durée de vie du
pod. Sur SIGTERM, le lien est fermé et le hook se détache.
Nécessite un noyau compilé avec CONFIG_BPF_LSM=y et bpf dans la pile LSM
active (lsm=...,bpf sur la ligne de commande du noyau). Talos Linux est
livré avec les deux activés par défaut depuis la v1.10.
kubectl apply -f https://raw.githubusercontent.com/cozystack/copy-fail-blocker/v0.3.0/manifests/copy-fail-blocker.yaml
Pour le dernier commit sur main (peut inclure des changements non publiés) :
kubectl apply -f https://raw.githubusercontent.com/cozystack/copy-fail-blocker/main/manifests/copy-fail-blocker.yaml
Le chart n'est pas publié en tant qu'artefact OCI (le chemin du registre est partagé avec l'image conteneur). Installez à partir d'un checkout tagué :
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
Ou via les raccourcis Makefile :
make apply # helm upgrade --install dans kube-system
make diff # prévisualiser les changements par rapport au cluster
make delete # désinstaller
make manifest # régénérer manifests/copy-fail-blocker.yaml
Le DaemonSet doit s'exécuter en privilégié (il charge des programmes BPF et
écrit dans bpffs). Placez-le dans un espace de noms avec la norme de sécurité
de pod privilégiée, ou dans kube-system, qui est privilégié par défaut.
Depuis n'importe quel pod sur un nœud couvert :