CopyFail (CVE-2026-31431): I Tested It On My Own Linux VM — Here's What Actually Protects You
SecuritySecurityLinuxCVEKernelDebian

CopyFail (CVE-2026-31431): I Tested It On My Own Linux VM — Here's What Actually Protects You

by paulie420·May 3, 2026

    Title: DOOM — At Doom's Gate (E1M1)

On April 29, 2026, a Linux kernel vulnerability called CopyFail (CVE-2026-31431) was publicly disclosed. The headline is alarming: any unprivileged local user can become root on virtually every mainstream Linux distribution running a kernel built since 2017. No race conditions, no timing tricks — just a 732-byte script and a few seconds.

I wanted to understand it properly, so I set up a Debian 12 VM on UTM/macOS (ARM64), reproduced the issue, tested whether a standard apt update && apt upgrade actually fixed it, and built a mitigation script that works across every major distro. Here's what I found.


What Is CopyFail?

CopyFail attack flow diagram showing the path from unprivileged user through algif_aead to root

CopyFail is a logic bug in the Linux kernel's cryptographic subsystem — specifically in the algif_aead kernel module, which exposes AEAD (Authenticated Encryption with Associated Data) cipher operations via the AF_ALG socket interface. The bug was introduced in August 2017 and has been sitting in virtually every Linux kernel built since.

The flaw allows any unprivileged local user to trigger a controlled 4-byte write into the kernel's page cache — shared memory that backs files on disk. An attacker can aim that write at a setuid binary like /usr/bin/su or /usr/bin/sudo, corrupt it in memory, and the next time it runs, it runs as root — for them.

The page cache is shared across all processes on a system, which means this vulnerability also has cross-container implications in multi-tenant environments. A single unprivileged user in one container could potentially affect processes in others on the same host.

CVSS score: 7.8 (High)
Affected: All mainstream Linux distros running kernels built from August 2017 onward, until patched
Remotely exploitable: No — requires local unprivileged access
Official disclosure: copy.fail by Theori / Xint


My Test: Debian 12 ARM64 on UTM

I ran this on a Debian 12 (Bookworm) VM running on Apple Silicon via UTM. The kernel was 6.1.0-44-arm64. From a standard unprivileged user account with no sudo access:

$ sudo ls
[sudo] password for test:
test is not in the sudoers file.

$ python3 copy_fail_exp.py
Password:
root@debian12:/home/test#

That is it. Local user to root in under five seconds.

Terminal before and after running copyfail-mitigate.sh — exploit fails after mitigation

Does apt update fix it?

This is where it gets important. I ran a full apt update && apt upgrade, then rebooted into the newly installed kernel — 6.1.0-45-arm64. Ran the exploit again:

root@debian12:/home/test#

Still vulnerable. Debian's kernel update to 6.1.0-45 on ARM64 does not contain the CopyFail fix as of May 3, 2026. Updating and rebooting is not enough on this platform right now.

You can check whether a patched kernel is available for your Debian architecture with:

apt-cache policy linux-image-arm64   # or linux-image-amd64 on x86

If "Installed" and "Candidate" show the same version, there is nothing to upgrade to yet. Track patch availability at the Debian Security Tracker.


The Mitigation: Blacklisting algif_aead

Diagram showing how the algif_aead blacklist closes the CopyFail attack path

The entire attack flows through the algif_aead kernel module. No module, no attack path. The fix — until a patched kernel lands in your repo — is to blacklist it.

A basic one-liner does the job:

printf "blacklist algif_aead\ninstall algif_aead /bin/false\n" \
  | sudo tee /etc/modprobe.d/cve-2026-31431.conf
sudo rmmod algif_aead 2>/dev/null || true

The two lines do slightly different things. blacklist algif_aead tells the module loader never to load it automatically. install algif_aead /bin/false is the more robust of the two — it means even a direct modprobe algif_aead call gets redirected to /bin/false, which immediately exits with failure. The blacklist alone can be bypassed; the install override cannot.

The rmmod line evicts the module from the currently running kernel so you are protected right now, not just after the next reboot.

But there is a catch: you also need to regenerate your initramfs. Without that step, the blacklist file exists on disk but the module could sneak in during early boot before userspace reads modprobe config. On Debian/Ubuntu that means running sudo update-initramfs -u -k all after writing the conf file.

After applying the mitigation

$ python3 copy_fail_exp.py
Traceback (most recent call last):
  ...
  a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"))
FileNotFoundError: [Errno 2] No such file or directory

The exploit dies immediately at the socket bind call. The AEAD kernel interface is gone. Nothing to attach to, nothing to corrupt.


The Full Cross-Distro Mitigation Script

I wrote a script that handles detection, blacklisting, module unloading, and initramfs regeneration automatically — and gives you the right kernel update command for your specific distro and architecture. It covers Debian, Ubuntu, Linux Mint, Kali, Arch and all Arch-based distros, Fedora, RHEL/AlmaLinux/Rocky, and openSUSE/SLES.

Run it with: sudo bash copyfail-mitigate.sh

#!/usr/bin/env bash
# CopyFail (CVE-2026-31431) Detection and Mitigation Script
# Usage: sudo bash copyfail-mitigate.sh

set -euo pipefail

CVE="CVE-2026-31431"
MODULE="algif_aead"
CONF_FILE="/etc/modprobe.d/cve-2026-31431.conf"

if [[ $EUID -ne 0 ]]; then
    echo "[ERROR] This script must be run as root. Try: sudo bash $0"
    exit 1
fi

echo "=== CopyFail (${CVE}) Detection and Mitigation ==="

DISTRO_ID=""
if [[ -f /etc/os-release ]]; then
    source /etc/os-release
    DISTRO_ID="${ID:-unknown}"
fi

KERNEL=$(uname -r)
ARCH=$(uname -m)
echo "[INFO] Kernel: ${KERNEL} / Arch: ${ARCH} / Distro: ${DISTRO_ID}"

MODULE_EXISTS=false
MODULE_LOADED=false
modinfo "${MODULE}" &>/dev/null && MODULE_EXISTS=true
lsmod | grep -q "^${MODULE}" && MODULE_LOADED=true

if [[ "${MODULE_EXISTS}" == false ]]; then
    echo "[ OK ] Module ${MODULE} not present — likely patched already."
else
    echo "[VULN] Module ${MODULE} is present."
    [[ "${MODULE_LOADED}" == true ]] && echo "[VULN] Module is currently LOADED."

    if ! grep -qs "${MODULE}" /etc/modprobe.d/*.conf 2>/dev/null; then
        printf "blacklist %s\ninstall %s /bin/false\n" "${MODULE}" "${MODULE}" > "${CONF_FILE}"
        echo "[ OK ] Written: ${CONF_FILE}"
    else
        echo "[ OK ] Blacklist already in place."
    fi

    rmmod "${MODULE}" 2>/dev/null && echo "[ OK ] Module unloaded." || true

    case "${DISTRO_ID}" in
        ubuntu|debian|linuxmint|kali) update-initramfs -u -k all ;;
        fedora|rhel|almalinux|rocky)  dracut --force ;;
        arch|manjaro|endeavouros)     mkinitcpio -P ;;
        opensuse*|sles)               dracut --force ;;
        *) echo "[WARN] Regenerate your initramfs manually." ;;
    esac
fi

echo ""
echo "[INFO] More info: https://copy.fail/"
echo "[INFO] NVD: https://nvd.nist.gov/vuln/detail/${CVE}"

What the Script Does, Step by Step

Step 1 — Detects your system. Reads /etc/os-release to identify your distro and version. Also detects your CPU architecture so it resolves the correct kernel package name for Debian/Ubuntu (e.g. linux-image-arm64 on ARM, not the wrong linux-image-amd64).

Step 2 — Checks module state. Runs modinfo algif_aead to see whether the module exists in your kernel build at all. If it does not exist, you are likely on a patched kernel already. Then checks lsmod to see whether it is currently loaded and running in memory.

Step 3 — Checks for existing mitigation. Scans /etc/modprobe.d/ to see whether a blacklist entry already exists. Idempotent — safe to run multiple times.

Step 4 — Applies the blacklist. Writes the conf file, calls rmmod to evict the live module if it is loaded, then regenerates initramfs using the right tool for your distro (update-initramfs, dracut, or mkinitcpio) so the blacklist survives every future boot.

Finally — prints the exact package manager command to install your distro's patched kernel once it is available, with an honest warning if it has not landed in your repo yet.


Quick Check Without the Script

If you just want to know whether a given machine is potentially vulnerable right now, run:

modinfo algif_aead 2>&1

If it returns module information, the module exists in your kernel and the vulnerability is present. If it returns "not found", you are either on a patched kernel or one compiled without that module. Either way you are not exposed.


What About Systems That Need algif_aead?

The blacklist trades away any software that legitimately uses algif_aead for AEAD cipher operations via the kernel crypto API. This is uncommon on desktop systems but could affect certain VPN software or specialised crypto tooling. If you have something that depends on it, the right alternative is AppArmor. Debian 12 ships with AppArmor enabled, and a deny rule for network afalg in the relevant profile will block the exploit path without removing the module. The permanent fix — once your distro ships it — is a patched kernel, at which point the module is safe to load again and you can remove the conf file.


Patch Status by Distro (as of May 3, 2026)

The upstream kernel fix landed in mainline on April 1, 2026 (commit a664bf3d603d). Vendor rollout is uneven:

  • Debian 12 arm64: Not yet patched as of my testing. Blacklist mitigation is your only option right now.
  • Ubuntu 24.04 LTS: Patched kernel available via apt. Update and reboot.
  • Ubuntu 26.04: Ships an unaffected kernel — update and you are clean.
  • Arch Linux: Rolling release. A full pacman -Syu should pull the fix.
  • Fedora: dnf update kernel and reboot.
  • RHEL / AlmaLinux / Rocky: Patches rolling out — check your vendor advisory.
  • openSUSE: zypper update kernel-default and reboot.

The script handles all of the above and will tell you exactly what to run for your system.


References