
BPF LSM blocker para CVE-2026-31431 (Copy Fail) - remediação sem reinicialização para OpenShift 4
A CVE-2026-31431 ("Copy Fail") é uma vulnerabilidade de escalonamento de privilégios no kernel Linux
na interface criptográfica algif_aead. Um atacante usa sockets AF_ALG com
o algoritmo authencesn e splice() para corromper arquivos arbitrários no cache de páginas
do kernel — incluindo binários setuid como /usr/bin/su.
Este documento fornece uma correção sem reinicialização usando um DaemonSet BPF LSM
que bloqueia todos os binds AEAD AF_ALG — o subsistema explorado pelo Copy Fail. Isso
impede bypasses via aninhamento de templates criptográficos (ex.: pcrypt(authencesn(...))).
Outros usos de AF_ALG (hash, skcipher) não são afetados. Testado de ponta a ponta em três
clusters OCP 4.22 separados.
# 1. Verifique se o BPF LSM está habilitado (RHEL CoreOS 9.8 o tem por padrão)
oc debug node/<qualquer-nó> -- chroot /host cat /sys/kernel/security/lsm
# Deve conter "bpf"
# 2. Implante o namespace e conceda o SCC privilegiado
oc apply -f daemonset.yaml
# 3. Os pods do DaemonSet iniciarão automaticamente em todos os nós
# 4. Verifique
oc get pods -n block-copyfail # Todos os nós devem mostrar Running
oc logs -n block-copyfail -l app=block-copyfail
# Esperado: "block-copyfail: blocker active — all AF_ALG AEAD binds blocked"
Sem reinicializações. Sem drenagem de nós. Sem reinícios de pods. A proteção é imediata e cobre todos os processos em todos os nós (100% de cobertura).
O exploit encadeia três recursos do kernel:
socket(AF_ALG, SOCK_SEQPACKET, 0)authencesn(hmac(sha256),cbc(aes)), um algoritmo específico
de criptografia autenticadaO atacante corrompe /usr/bin/su no cache de páginas (sem acesso de escrita ao
arquivo) e depois o executa para obter root.
Crie um novo namespace cve-2026-31431-test no seu cluster e execute o script de teste aplicando os manifests no diretório test:
oc apply -f test
Verifique os resultados:
oc wait pod/cve-test -n cve-2026-31431-test \
--for=jsonpath='{.status.phase}'=Succeeded --timeout=120s
oc -n cve-2026-31431-test logs -l app=cve-2026-31431-test
Em um cluster vulnerável você verá:
=== CVE-2026-31431 Vulnerability Test ===
Target: /usr/bin/su
Original SHA256: 8969560ae8e6e21c6184c1451f59418822ee69dd5d946d71987b55236bbc0feb
Attempting splice + AF_ALG page-cache corruption (160 bytes in 40 chunks)...
After SHA256: 30b0f5b5a054c4df65b48ca792863bf7054b4d793f15f57163792ba6c2b151ae
PAGE CACHE CORRUPTION: YES - /usr/bin/su was modified in the page cache
Attempting to execute corrupted /usr/bin/su ...
exit code: 0
RESULT: PARTIALLY MITIGATED
Page-cache corruption succeeded (kernel is vulnerable)
Privilege escalation blocked (allowPrivilegeEscalation=false)
oc delete namespace cve-2026-31431-test
A abordagem BPF LSM intercepta socket_bind no nível do kernel e bloqueia todos
os binds AEAD AF_ALG independentemente do aninhamento de templates. É baseada em
block-copyfail, reescrita em C
com libbpf para implantação em OCP.
O BPF LSM deve estar habilitado. O RHEL CoreOS 9.8 (OCP 4.22) o tem habilitado por padrão. Verifique com:
oc debug node/<qualquer-nó> -- chroot /host cat /sys/kernel/security/lsm
A saída esperada inclui bpf:
lockdown,capability,landlock,yama,selinux,bpf
Se bpf não estiver presente, um MachineConfig único é necessário (este é o
único cenário que exige reinicialização):
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
labels:
machineconfiguration.openshift.io/role: worker
name: 99-enable-bpf-lsm
spec:
kernelArguments:
- lsm=lockdown,capability,selinux,bpf
Crie um novo namespace block-copyfail, conceda o SCC e implante o DaemonSet aplicando o manifest daemonset.yaml.
O SCC privilegiado deve ser concedido antes que os pods do DaemonSet sejam criados,
caso contrário a criação dos pods falhará com erros de validação de SCC.
oc apply -f daemonset.yaml
oc get pods -n block-copyfail -o wide
Esperado: um pod por nó, todos Running:
NAME READY STATUS AGE NODE
block-copyfail-2jhzf 1/1 Running 34s ci-...-master-2
block-copyfail-4dfq7 1/1 Running 34s ci-...-master-1
block-copyfail-c2ts8 1/1 Running 34s ci-...-worker-c
block-copyfail-ctblk 1/1 Running 34s ci-...-worker-a
block-copyfail-m26sx 1/1 Running 34s ci-...-worker-b
block-copyfail-xsh6d 1/1 Running 34s ci-...-master-0
oc logs -n block-copyfail -l app=block-copyfail
Esperado:
block-copyfail: blocker active — all AF_ALG AEAD binds blocked
Execute novamente o mesmo teste de exploit da seção Confirmando a Vulnerabilidade.
Após implantar o DaemonSet BPF LSM, a saída será:
=== CVE-2026-31431 Vulnerability Test ===
Target: /usr/bin/su
Original SHA256: 30b0f5b5a054c4df65b48ca792863bf7054b4d793f15f57163792ba6c2b151ae
Attempting splice + AF_ALG page-cache corruption (160 bytes in 40 chunks)...
AF_ALG bind failed: [Errno 1] Operation not permitted
RESULT: CANNOT TEST - AF_ALG or splice not available/permitted
Os logs do DaemonSet mostrarão a tentativa bloqueada:
oc logs -n block-copyfail -l app=block-copyfail
block-copyfail: blocker active — all AF_ALG AEAD binds blocked
block-copyfail: BLOCKED pid=16777 comm=python3 time=2026-05-01 16:37:23
Execute verify-algos.py em um nó para confirmar que todos os algoritmos AEAD estão bloqueados
enquanto outros tipos de AF_ALG (hash, skcipher) continuam funcionando:
oc debug node/<qualquer-nó> -- chroot /host python3 -c "
import socket
tests = [
('aead', 'gcm(aes)'),
('aead', 'ccm(aes)'),
('aead', 'rfc4106(gcm(aes))'),
('hash', 'sha256'),
('skcipher', 'cbc(aes)'),
('aead', 'authencesn(hmac(sha256),cbc(aes))'),
]
for t, n in tests:
s = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)
try:
s.bind((t, n))
print(f' ALLOWED {t}/{n}')
except OSError as e:
print(f' BLOCKED {t}/{n} -- {e}')
finally:
s.close()
"
Saída esperada:
BLOCKED aead/gcm(aes) -- [Errno 1] Operation not permitted
BLOCKED aead/ccm(aes) -- [Errno 1] Operation not permitted
BLOCKED aead/rfc4106(gcm(aes)) -- [Errno 1] Operation not permitted
ALLOWED hash/sha256
ALLOWED skcipher/cbc(aes)
BLOCKED aead/authencesn(hmac(sha256),cbc(aes)) -- [Errno 1] Operation not permitted
Isso confirma que o BPF LSM bloqueia todos os binds AEAD enquanto mantém outros tipos de AF_ALG funcionais.
O código-fonte do bloqueador BPF LSM está em block-copyfail/:
block-copyfail/
block_copyfail.bpf.c # BPF kernel program (LSM hook)
block_copyfail.c # Userspace loader (libbpf skeleton)
block_copyfail.h # Shared event struct
Makefile # Build pipeline
Dockerfile # Multi-stage build
daemonset.yaml # Namespace + DaemonSet manifest
trigger-test.py # Quick validation script
Compile e envie:
cd block-copyfail/
podman build -t quay.io/<org>/block-copyfail:latest .
podman push quay.io/<org>/block-copyfail:latest
O Dockerfile usa uma compilação multi-estágio: Fedora com clang/bpftool/libbpf-devel para compilação, UBI 9 minimal para a imagem de runtime (~122 MB).
Excluir o DaemonSet remove imediatamente a mitigação em todos os nós:
oc delete -f daemonset.yaml
# ou
oc delete namespace block-copyfail
O programa BPF é desanexado automaticamente quando o processo do carregador é encerrado. Nenhuma reinicialização ou reinício de pod é necessário.