
对 CVE-2026-31431 的复现分析、C 改编的 exp。
.
├── container
│ ├── container.md # Ideias de escape de container
│ ├── expenv # Ambiente do container
│ │ ├── bin
│ │ │ ├── gscontainer
│ │ │ └── share
│ │ ├── Dockerfile
│ │ ├── run.sh
│ │ └── src
│ │ ├── gscontainer.c
│ │ └── share.c
│ ├── images
│ │ ├── container.png
│ │ ├── getshell.png
│ │ ├── host.png
│ │ ├── payload.png
│ │ └── share.png
│ ├── recode.bin # Shellcode de estágio único
│ └── stager
│ ├── shell.elf # Shellcode de estagificação incorporado no exp
│ ├── stager1.bin
│ ├── stager2.bin
│ ├── stager3.bin
│ └── stager4.bin
├── doc
│ ├── cve-2026-31431.md # Análise e resumo da xint.io
│ └── shellcode.md # Introdução ao que é shellcode
├── exp.py # curl http://copy.fail/exp -o exp.py
├── README.md
├── success
│ ├── code.py # Shellcode extraído do exp.py
│ └── exploit.c # Exp reduzido em C, com shellcode nativo de escalonamento de privilégios do msf, precisa de bypass próprio
└── test
├── exptest.c # Removidos comentários
└── test.c # Correto e com comentários, para facilitar a compreensão
Três commits individualmente inofensivos, ao se combinarem, produziram a vulnerabilidade:
splice(arquivo → pipe → AF_ALG)
↓
af_alg_sendmsg: páginas de cache de página do arquivo → SGL de TX (cópia zero)
↓
_aead_recvmsg: otimização in-place
├─ memcpy_sglist: AAD+texto cifrado copiados para o SGL de RX
├─ sg_chain: página da tag (ainda apontando para o cache de página do arquivo) encadeada no SGL de RX
└─ req->src = req->dst (= scatterlist que contém a página de cache de página do arquivo)
↓
crypto_authenc_esn_decrypt: reordenamento ESN
└─ scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1)
↓ Percorre o scatterlist de destino até a posição da tag
↓ kmap_local_page() mapeia a página de cache de página do arquivo
↓ memcpy: escreve seqno_lo na página de cache de página do arquivo
↓
HMAC falha → retorna -EBADMSG
↓
A página de cache de página do arquivo foi modificada, o kernel não a marcou como suja, o arquivo em disco permanece inalterado
O artigo afirma que "quase todas as distribuições Linux" desde 2017 são afetadas. O cache de página é compartilhado entre contêineres, portanto a vulnerabilidade é tanto um vetor de escalonamento local de privilégios (LPE) quanto de escape de contêiner.
O commit de correção no kernel principal a664bf3d603d reverte as operações AEAD do AF_ALG para o modo out-of-place (src e dst separados):
Before: aead_request_set_crypt(..., rsgl_src,
areq->first_rsgl.sgl.sgt.sgl, ...)
^ src == dst ^
After: aead_request_set_crypt(..., tsgl_src,
areq->first_rsgl.sgl.sgt.sgl, ...)
^ src ≠ dst ^ (TX SGL para src, RX SGL para dst)
Mensagem do commit: "Não há benefício em operar in-place no algif_aead, já que a origem e o destino vêm de mapeamentos diferentes."
Medidas de mitigação emergenciais:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead
Ou proibir a criação de sockets AF_ALG via política seccomp (socket(AF_ALG, ...)).
| Data | Commit | Impacto |
|---|
| 2011 | a5079d084f8b | authencesn foi adicionado, usando dst scatterlist como espaço temporário para o reordenamento ESN. Inofensivo na época — pois a interface AEAD antiga mantinha AAD separado, e o único chamador era o xfrm do kernel. |
| 2015 | 104880a6b470 | authencesn foi convertido para a nova interface AEAD, introduzindo o comportamento de "escrever além do limite de saída" em assoclen+cryptlen para gravar seqno_lo. Ainda não explorável — pois o AF_ALG usava modo out-of-place, req->src e req->dst separados, e as páginas de cache de página estavam apenas no src somente leitura. |
| 2017 | 72548b093ee3 | Otimização in-place do AF_ALG. Copia AAD+texto cifrado do SGL de TX para o buffer RX, mas encadeia a página da tag via sg_chain. Define req->src = req->dst. Agora as páginas de cache de página introduzidas pelo splice estão também no scatterlist de destino gravável. A vulnerabilidade se forma. |
| Distribuição | Versão do kernel | Afetado |
|---|
| Ubuntu 24.04 LTS | 6.17.0-1007-aws | Sim |
| Amazon Linux 2023 | 6.18.8-9.213.amzn2023 | Sim |
| RHEL 10.1 | 6.12.0-124.45.1.el10_1 | Sim |
| SUSE 16 | 6.12.0-160000.9-default | Sim |
| Característica | Verificação no código-fonte | Explicação |
|---|
| authencesn escreve seqno_lo no final | authencesn.c:134 | Escreve seqno_lo (32 bits baixos) em assoclen+cryptlen. Descrição do usuário correta. |
| Convenção de reordenamento ESN | Blog confirma (xint.io) | AAD bytes 0-3 = seqno_hi, bytes 4-7 = seqno_lo. tmp[1] (=bytes 4-7=seqno_lo) é escrito no final. |
| Caminho de descriptografia não restaura bytes sobrescritos | authencesn.c:270-273 vs 215-217 | decrypt_tail restaura ESN no offset 0-7 mas nunca restaura os bytes originais em assoclen+cryptlen. O caminho de criptografia (genicv_tail) restaura antes de escrever o ICV. |
| Criptografia/descriptografia in-place no AF_ALG | algif_aead.c:189-252 | rsgl_src e areq->first_rsgl.sgl.sgt.sgl apontam para o mesmo SGL de RX. |
| Encadeamento da tag no AF_ALG | algif_aead.c:238-244 | Durante a descriptografia, a tag é referenciada via sg_chain em vez de copiada — em cenários de splice, a página da tag ainda aponta para o cache de página do arquivo. Causa raiz da vulnerabilidade de segurança. |
| Transferência zero-copy via splice | fs/splice.c:876, af_alg.c:1049, lib/scatterlist.c:1167 | Referencia diretamente a página do pipe, zero-copy. |
| CVE-2026-31431 | Três cadeias de commits | authencesn(2011/2015) + in-place(2017) + zero-copy splice = primitiva gravável controlada no cache de página. |