
Análise técnica aprofundada do CVE-2026-31431 (Copy Fail) do kernel Linux, uma escalada local de privilégios via bug de scatterlist in-place no AF_ALG, incluindo causa raiz, caminho de exploração e guia de submissão de patch.
Gravidade: CVSS 7.8 (Alta)
Classe: Escalação Local de Privilégios
Introduzida: Linux 4.14 (2017)
Corrigida: commit a664bf3d603d
CVE-2026-31431 é uma falha de escalação local de privilégios no módulo algif_aead do kernel Linux — a interface de socket AEAD da API de criptografia do kernel para espaço de usuário (AF_ALG).
A vulnerabilidade permite que um usuário sem privilégios utilize a família de endereços AF_ALG na API de Criptografia do kernel Linux para realizar gravações controladas de 4 bytes no cache de páginas, que armazena as cópias em memória dos arquivos. Ao substituir código na cópia em memória de um executável legível armazenado no cache de páginas, um atacante pode escalar privilégios de usuário quando qualquer processo privilegiado executar posteriormente essa versão corrompida do arquivo.
Diferentemente do Dirty Cow (CVE-2016-5195), que exigia vencer uma condição de corrida, o Copy Fail é uma falha lógica direta. Ele é acionado sem corridas, tentativas repetidas ou janelas de tempo propensas a travamentos.
O commit 72548b093ee3 ("crypto: algif_aead - switch to in-place processing") introduziu uma otimização que definiu req->src == req->dst dentro de aead_recvmsg(), eliminando a alocação de um buffer de saída separado.
Essa otimização fez com que req->src e req->dst apontassem para um scatterlist combinado. Por causa disso, as páginas do cache de páginas da chamada splice() foram incorretamente encadeadas diretamente no scatterlist de destino gravável.
A API AEAD do kernel define um contrato de saída claro: o buffer de destino recebe AAD || plaintext, exatamente assoclen + (cryptlen - authsize) bytes. Nada na API impõe esse contrato, e nada o documenta como um requisito. O design assume que todo algoritmo AEAD confinará suas gravações ao destino pretendido. Um algoritmo não o fez.
crypto/
├── algif_aead.c ← bug do scatterlist in-place (principal)
└── authencesn.c ← gravação de scratch fora dos limites (gatilho)
crypto/algif_aead.c — A Atribuição In-Place/* VULNERÁVEL — caminho in-place pós-2017 */
static int aead_recvmsg(struct socket *sock, struct msghdr *msg,
size_t ignored, int flags)
{
/* ... */
/*
* Quando o espaço de usuário chama splice(2) em um arquivo, areq->tsgl contém
* páginas do cache de páginas desse arquivo. Definir dst = src entrega essas
* páginas do cache de páginas ao mecanismo de criptografia como um buffer
* de saída gravável.
*/
aead_request_set_crypt(req,
areq->tsgl, /* src */
areq->tsgl, /* dst — ponteiro idêntico; causa raiz */
areq->cryptlen, iv);
}
crypto/authencesn.c — A Gravação de Scratch Fora dos LimitesO template authencesn é um wrapper AEAD usado pelo IPsec para suporte a Número de Sequência Estendido (ESN). O IPsec usa números de sequência de 64 bits divididos em uma metade alta (seqno_hi, bytes 0–3 do AAD) e uma metade baixa (seqno_lo, bytes 4–7). O formato de transmissão carrega apenas seqno_lo; seqno_hi é implícito.
/* crypto/authencesn.c — gravação de scratch além do limite de saída */
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
/* ... */
/*
* authencesn armazena temporariamente seqno_hi no deslocamento
* (assoclen + cryptlen) em req->dst — uma palavra além da
* região de saída legítima.
*
* Em operação normal: atinge memória de propriedade do kernel.
* Com scatterlist in-place: atinge uma página do cache de páginas.
*/
scatterwalk_map_and_copy(&seqno_hi,
req->dst,
req->assoclen + req->cryptlen, /* deslocamento fora dos limites */
sizeof(seqno_hi), /* 4 bytes */
1 /* gravação */);
/* authencesn não restaura esses bytes no caminho de falha */
}
Correção upstream: commit a664bf3d603d — reverte a otimização de 2017.
crypto/algif_aead.c — Scatterlists Separados/* CORRIGIDO — src e dst separados */
aead_request_set_crypt(req,
areq->tsgl, /* src — pode conter páginas do cache de páginas; somente leitura */
areq->rsgl, /* dst — buffer de kernel recém-alocado; gravável */
areq->cryptlen, iv);
Efeito: As páginas do cache de páginas permanecem em src, que o mecanismo de criptografia apenas lê. A gravação de scratch do authencesn em assoclen + cryptlen agora cai em um buffer alocado pelo kernel que o processo possui. Nenhuma página do cache de páginas é jamais alcançável pelo caminho de gravação.
O seguinte descreve como a gravação de 4 bytes alcança a escalação completa de privilégios.
| Etapa | Ação |
|---|---|
| 1 | Abrir um socket AF_ALG configurado para authencesn(hmac(sha256),cbc(aes)) |
| 2 | splice() um binário setuid legível (ex.: /usr/bin/sudo) no socket — isso coloca suas páginas do cache de páginas em areq->tsgl |
| 3 | Criar assoclen + cryptlen para que o deslocamento da gravação de scratch se alinhe com um byte alvo dentro da imagem em cache do binário |
| 4 | Chamar recvmsg() — aciona o caminho de descriptografia, authencesn grava 4 bytes controlados pelo atacante no cache de páginas |
| 5 | O arquivo em disco permanece inalterado; verificadores de integridade, inotify e timestamps de inode não são afetados |
| 6 | Qualquer execução subsequente do binário mapeia as páginas do cache de páginas já modificadas — o código corrigido é executado com euid=0 |
A gravação termina dentro dos dados em cache do arquivo spliced em memória, contornando completamente as permissões do arquivo.
./scripts/get_maintainer.pl crypto/algif_aead.c
# Herbert Xu <[email protected]>
# [email protected]
./scripts/checkpatch.pl 0001-crypto-algif_aead-revert-inplace.patch
crypto: algif_aead - restore separate src/dst scatterlists in recvmsg
Commit 72548b093ee3 ("crypto: algif_aead - switch to in-place
processing") set req->src == req->dst inside aead_recvmsg(). When
the caller uses splice(2), the source scatterlist contains page
cache pages of the spliced file. With the in-place optimization,
those same pages become the writable destination passed to the
AEAD transform.
authencesn writes 4 bytes at offset (assoclen + cryptlen) into
req->dst as scratch space for Extended Sequence Number
rearrangement and does not restore them on the error path. With
page cache pages present in dst, this write bypasses the VFS
permission layer and corrupts the in-memory image of the spliced
file without modifying the on-disk inode.
An unprivileged local user can exploit this to overwrite
arbitrary bytes in the page cache of any readable setuid binary
and escalate privileges to root.
Restore separate src and dst scatterlists so that page cache
pages are confined to the read-only source path.
Fixes: 72548b093ee3 ("crypto: algif_aead - switch to in-place processing")
Cc: [email protected] # 4.14+
Reported-by: Taeyang Lee <[email protected]>
Signed-off-by: Your Name <[email protected]>
git send-emailgit send-email \
[email protected] \
[email protected] \
[email protected] \
0001-crypto-algif_aead-revert-inplace.patch
| Elemento | Propósito |
|---|---|
Assunto subsystem: file - verb phrase | Formato obrigatório; menos de 72 caracteres |
Fixes: <sha> ("título original") | Aciona ferramentas automáticas de backport estável |
Cc: [email protected] | Sinaliza o patch para backports de ramos LTS |
Reported-by: | Atribuição ao descobridor |
Signed-off-by: | Certificado de Origem do Desenvolvedor — obrigatório |
Kernels vulneráveis abrangem Linux 4.14 até 7.0-rc, todos os 6.18.x anteriores a 6.18.22, e 6.19.x anteriores a 6.19.12.
Mitigação provisória (sem necessidade de reinicialização na maioria dos sistemas):
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
Essa solução alternativa não afeta dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS ou SSH.
xint.io/blog/copy-fail-linux-distributionsnvd.nist.gov/vuln/detail/CVE-2026-31431a664bf3d603d (mainline)72548b093ee3 (Linux 4.14, 2017)