
Análisis técnico en profundidad de la CVE-2026-31431 (Copy Fail) del kernel de Linux, una escalada local de privilegios mediante el fallo de scatterlist in-place de AF_ALG, que incluye la causa raíz, la ruta de explotación y la guía de envío de parches.
Gravedad: CVSS 7.8 (Alta)
Clase: Escalada Local de Privilegios
Introducida: Linux 4.14 (2017)
Corregida: commit a664bf3d603d
CVE-2026-31431 es una vulnerabilidad de escalada local de privilegios en el módulo algif_aead del kernel de Linux — la interfaz de socket AEAD de la API criptográfica del kernel en espacio de usuario (AF_ALG).
La vulnerabilidad permite que un usuario sin privilegios utilice la familia de direcciones AF_ALG en la API criptográfica del kernel de Linux para realizar escrituras controladas de 4 bytes en la caché de páginas, que respalda las copias en memoria de los archivos. Al reemplazar código en la copia en memoria de un ejecutable legible almacenado en la caché de páginas, un atacante puede escalar privilegios de usuario cuando cualquier proceso privilegiado ejecute posteriormente esa versión corrupta del archivo.
A diferencia de Dirty Cow (CVE-2016-5195), que requería ganar una condición de carrera, Copy Fail es un fallo lógico de línea recta. Se activa sin condiciones de carrera, reintentos ni ventanas de tiempo propensas a fallos.
El commit 72548b093ee3 ("crypto: algif_aead - switch to in-place processing") introdujo una optimización que establecía req->src == req->dst dentro de aead_recvmsg(), eliminando la asignación de un buffer de salida separado.
Esta optimización hizo que req->src y req->dst apuntaran a una scatterlist combinada. Debido a esto, las páginas de la caché de páginas de la llamada splice() se encadenaban incorrectamente directamente en la scatterlist de destino escribible.
La API AEAD del kernel define un contrato de salida claro: el buffer de destino recibe AAD || plaintext, exactamente assoclen + (cryptlen - authsize) bytes. Nada en la API hace cumplir este contrato, y nada lo documenta como un requisito. El diseño asume que cada algoritmo AEAD confinará sus escrituras al destino previsto. Un algoritmo no lo hizo.
crypto/
├── algif_aead.c ← bug de scatterlist in-place (principal)
└── authencesn.c ← escritura scratch fuera de límites (disparador)
crypto/algif_aead.c — La Asignación In-Place/* VULNERABLE — ruta in-place posterior a 2017 */
static int aead_recvmsg(struct socket *sock, struct msghdr *msg,
size_t ignored, int flags)
{
/* ... */
/*
* Cuando el espacio de usuario llama a splice(2) en un archivo, areq->tsgl contiene
* páginas de la caché de páginas de ese archivo. Establecer dst = src entrega esas
* páginas de la caché de páginas al motor criptográfico como un buffer de salida
* escribible.
*/
aead_request_set_crypt(req,
areq->tsgl, /* src */
areq->tsgl, /* dst — puntero idéntico; causa raíz */
areq->cryptlen, iv);
}
crypto/authencesn.c — La Escritura Scratch Fuera de LímitesLa plantilla authencesn es un envoltorio AEAD utilizado por IPsec para soporte de Número de Secuencia Extendido (ESN). IPsec utiliza números de secuencia de 64 bits divididos en una mitad alta (seqno_hi, bytes 0–3 del AAD) y una mitad baja (seqno_lo, bytes 4–7). El formato de red transporta solo seqno_lo; seqno_hi es implícito.
/* crypto/authencesn.c — escritura scratch más allá del límite de salida */
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
/* ... */
/*
* authencesn almacena temporalmente seqno_hi en el desplazamiento
* (assoclen + cryptlen) en req->dst — una palabra más allá de la
* región de salida legítima.
*
* Bajo operación normal: alcanza memoria propiedad del kernel.
* Con scatterlist in-place: alcanza una página de la caché de páginas.
*/
scatterwalk_map_and_copy(&seqno_hi,
req->dst,
req->assoclen + req->cryptlen, /* desplazamiento fuera de límites */
sizeof(seqno_hi), /* 4 bytes */
1 /* escritura */);
/* authencesn no restaura estos bytes en la ruta de fallo */
}
Corrección upstream: commit a664bf3d603d — revierte la optimización de 2017.
crypto/algif_aead.c — Scatterlists Separadas/* CORREGIDO — src y dst separados */
aead_request_set_crypt(req,
areq->tsgl, /* src — puede contener páginas de la caché de páginas; solo lectura */
areq->rsgl, /* dst — buffer de kernel recién asignado; escribible */
areq->cryptlen, iv);
Efecto: Las páginas de la caché de páginas permanecen en src, que el motor criptográfico solo lee. La escritura scratch de authencesn en assoclen + cryptlen ahora aterriza en un buffer asignado por el kernel que el proceso posee. Ninguna página de la caché de páginas es alcanzable desde la ruta de escritura.
Lo siguiente describe cómo la escritura de 4 bytes logra la escalada completa de privilegios.
| Paso | Acción |
|---|---|
| 1 | Abrir un socket AF_ALG configurado para authencesn(hmac(sha256),cbc(aes)) |
| 2 | splice() un binario setuid legible (p. ej., /usr/bin/sudo) en el socket — esto coloca sus páginas de la caché de páginas en areq->tsgl |
| 3 | Diseñar assoclen + cryptlen para que el desplazamiento de la escritura scratch se alinee con un byte objetivo dentro de la imagen en caché del binario |
| 4 | Llamar a recvmsg() — activa la ruta de descifrado, authencesn escribe 4 bytes controlados por el atacante en la caché de páginas |
| 5 | El archivo en disco está sin cambios; los verificadores de integridad, inotify y las marcas de tiempo de inode no se ven afectados |
| 6 | Cualquier ejecución posterior del binario mapea las páginas de la caché de páginas ya modificadas — el código parcheado se ejecuta con euid=0 |
La escritura termina dentro de los datos en caché del archivo empalmado en memoria, evitando por completo los permisos del archivo.
./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 |
|---|---|
Asunto subsystem: file - frase verbal | Formato requerido; menos de 72 caracteres |
Fixes: <sha> ("título original") | Activa el tooling automático de backport estable |
Cc: [email protected] | Marca el parche para backports de ramas LTS |
Reported-by: | Atribución al descubridor |
Signed-off-by: | Certificado de Origen del Desarrollador — obligatorio |
Los kernels vulnerables abarcan Linux 4.14 hasta 7.0-rc, todos los 6.18.x anteriores a 6.18.22, y 6.19.x anteriores a 6.19.12.
Mitigación provisional (sin necesidad de reinicio en la mayoría de los sistemas):
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
Esta solución alternativa no afecta a dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS ni SSH.
xint.io/blog/copy-fail-linux-distributionsnvd.nist.gov/vuln/detail/CVE-2026-31431a664bf3d603d (mainline)72548b093ee3 (Linux 4.14, 2017)