
Prova de conceito demonstrando escape de contêiner no Kubernetes via corrupção do page-cache do kernel CVE-2026-31431, alcançando execução de código no nível do nó a partir de um contêiner sem privilégios.
Uma prova de conceito demonstrando como um contêiner totalmente sem privilégios pode alcançar execução de código no nível do nó no Kubernetes ao explorar a vulnerabilidade de corrupção do cache de páginas do kernel Linux CVE-2026-31431 por meio de camadas de imagem de contêiner compartilhadas.
Aviso: Este repositório é publicado apenas para fins educacionais e defensivos. Use-o exclusivamente em sistemas que você possui ou para os quais tenha autorização explícita para testar.
CVE-2026-31431 ("Copy Fail") é uma vulnerabilidade do kernel Linux no caminho de Copy-on-Write (CoW) do cache de páginas. Uma condição de corrida de splice do AF_ALG permite que um processo sem privilégios corrompa as páginas do cache de páginas de um arquivo somente leitura. A corrupção persiste no cache de páginas do kernel e é visível para todo processo que posteriormente lê ou executa o arquivo — incluindo processos em outros contêineres ou no host.
Para detalhes completos sobre a vulnerabilidade original, consulte copy.fail.
A cadeia de ataque tem três estágios: corrupção do cache de páginas, propagação entre contêineres e execução privilegiada.
O subsistema AF_ALG (cripto) do kernel expõe uma interface baseada em soquetes para operações criptográficas no espaço do usuário. O exploit abusa de uma condição de corrida na forma como o kernel lida com splice() de um arquivo para um soquete AF_ALG:
/usr/sbin/ipset) somente leitura.authencesn(hmac(sha256),cbc(aes)).MSG_MORE, informando ao kernel que mais dados são esperados.splice() para transferir o conteúdo do arquivo alvo de um fd → pipe → soquete AF_ALG.O exploit repete isso para cada janela de 4 bytes até que todas as páginas em cache do binário alvo sejam sobrescritas com um payload personalizado.
Nenhuma permissão de escrita no arquivo é necessária. O arquivo no disco permanece inalterado — apenas o cache de páginas em memória é corrompido.
Os runtimes de contêiner (containerd, CRI-O) usam sistemas de arquivos overlay. Quando dois contêineres compartilham a mesma camada de imagem, o kernel atende às leituras de arquivos deles a partir das mesmas páginas do cache de páginas.
Esta imagem PoC é construída FROM registry.k8s.io/kube-proxy:v1.35.2. O DaemonSet kube-proxy em cada nó Kubernetes usa exatamente a mesma camada base. Como resultado, /usr/sbin/ipset em ambos os contêineres mapeia para o mesmo conjunto de páginas do cache de páginas.
Quando o contêiner PoC sem privilégios corrompe o cache de páginas do ipset, a corrupção fica imediatamente visível para o contêiner kube-proxy privilegiado no mesmo nó — com zero comunicação entre contêineres.
O kube-proxy executa como um DaemonSet privilegiado com hostNetwork: true. Ele invoca periodicamente /usr/sbin/ipset para gerenciar regras de iptables/ipset. Quando ele executa o ipset em seguida, o kernel carrega as páginas corrompidas do cache de páginas, executando o payload do atacante com todos os privilégios do kube-proxy:
O payload nesta PoC (payload/payload.c) simplesmente monta o sistema de arquivos raiz do host e grava um arquivo de marcação em /root/res como prova de execução de código no nível do nó.
┌──────────────────────────┐ ┌──────────────────────────┐
│ Contêiner PoC │ │ Contêiner kube-proxy │
│ (sem privilégios) │ │ (privilegiado) │
│ │ │ │
│ 1. Abre /usr/sbin/ipset │ │ │
│ (somente leitura) │ │ │
│ │ │ │
│ 2. Condição de corrida │ │ │
│ de splice do AF_ALG │ │ │
│ corrompe o cache │ │ │
│ de páginas │ │ │
│ │ │ │ │
└──────────┼───────────────┘ └──────────────────────────┘
│ │
▼ │
┌─────────────────────┐ │
│ Cache de Páginas │ │
│ do Kernel │ │
│ /usr/sbin/ipset │◄────────────────────┘
│ (CORROMPIDO) │ 3. kube-proxy executa ipset
│ contém os bytes │ → carrega páginas corrompidas
│ do payload do │ → payload executa como root
│ atacante │ no host
└─────────────────────┘
.
├── cmd/copyfail/main.go # Ponto de entrada; incorpora payload compilado
├── internal/
│ ├── exploit/
│ │ ├── exploit.go # Exploit principal: loop de condição de corrida de splice do AF_ALG
│ │ └── patch.go # Divide o payload em janelas de patch de 4 bytes
│ └── alg/
│ └── alg.go # Abstração do soquete AF_ALG AEAD
├── payload/
│ ├── payload.c # Payload de validação (monta fs do host, grava marcador)
│ └── nolibc/ # libc mínima do kernel para payloads estáticos sem dependências
├── deploy/
│ └── poc.yaml # Manifesto de Deployment Kubernetes
├── Dockerfile # Construído FROM kube-proxy para compartilhar camadas de imagem
├── Makefile # Orquestração de build
└── docs/ # Evidências de validação do ACK (Alibaba Cloud)
x86_64-linux-gnu-gcc)imagePullPolicy: IfNotPresent (o padrão)# Compila payload + binário Go
make build
# Compila imagem Docker
make docker-build
# Compila e envia para GHCR
make docker-push IMAGE=ghcr.io/<voce>/copy-fail-poc TAG=latest
Para alvos arm64:
make build CC=aarch64-linux-gnu-gcc GOARCH=arm64
kubectl apply -f deploy/poc.yaml
O Deployment cria um único pod sem privilégios. Ele:
/bin/copyfail -target /usr/sbin/ipset para corromper o cache de páginas.Após o kube-proxy executar o ipset novamente (isso normalmente ocorre em segundos devido ao seu loop de reconciliação, ou em seu próximo reinício), verifique o nó:
# Acesse o nó via SSH, ou use um pod de depuração privilegiado
cat /root/res
# Saída esperada: [*] success
A presença de /root/res no sistema de arquivos do host prova que o código fornecido pelo atacante foi executado com privilégios no nível do nó — gravado a partir do contexto do contêiner privilegiado do kube-proxy.
kubectl delete -f deploy/poc.yaml
# No(s) nó(s) afetado(s), remova o marcador e reinicie o kube-proxy:
rm -f /root/res
systemctl restart kubelet # ou exclua o pod kube-proxy para forçar novo pull
O kube-proxy é um alvo ideal porque:
privileged: true, hostNetwork: true.imagePullPolicy: IfNotPresent — uma vez que a imagem do atacante é baixada e compartilha a mesma camada base, as páginas do diretório inferior do overlay são compartilhadas.Qualquer DaemonSet privilegiado cuja imagem contenha um binário previsível poderia ser alvo da mesma forma.
O payload padrão (payload/payload.c) é um programa apenas de validação que grava um arquivo de marcação. Para construir um payload personalizado:
payload/payload.c. O programa é compilado contra nolibc (a biblioteca C mínima do kernel) para um binário estático e sem dependências.make payload para compilar cruzadamente.//go:embed.tools/include/nolibc/)O código Go de exploit neste repositório é fornecido como está para fins de pesquisa.
O payload (payload/payload.c) é derivado de copy-fail-c e é duplamente licenciado sob LGPL-2.1-or-later OR MIT. Consulte LICENSE-LGPL e LICENSE-MIT.