A 732-byte Python script that escalates to root on most Linux kernels built since 2017. No race condition, no per-distro offsets, no tuning. It works on the first try on Ubuntu, Debian, RHEL, Rocky, Alpine, and everything in between. If you run Docker containers on an unpatched Linux host, this vulnerability — CVE-2026-31431, nicknamed “Copy Fail” — is also a container escape.

An attacker with non-root access inside any container on your host can break out to root on the host kernel. That container could be a compromised web app, a CI runner executing untrusted code, or a multi-tenant service you exposed to the internet.

This guide covers how Copy Fail works, how to check if you’re exposed, and most importantly — how to mitigate it in a Docker homelab with kernel patching, seccomp profiles, and verification.

Understanding CVE-2026-31431 — Why Copy Fail Is Dangerous

The Linux kernel exposes cryptographic primitives to userspace through the AF_ALG socket family. The algif_aead module handles authenticated-encryption-with-associated-data (AEAD) requests. Back in 2017, a performance optimization changed AEAD to work in-place — the destination scatterlist pointed at the same pages as the source. Faster and less memory, until it collides with the page cache.

When a process calls splice() to feed file data through the crypto pipeline, the kernel uses the actual page-cache page of that file rather than a copy. With the in-place optimization, that same page becomes the destination. The kernel writes the AEAD authentication tag directly into the page-cache copy of a file the user only has read access to.

The exploitation path is deterministic:

  1. Open an AF_ALG socket with authencesn(hmac(sha256),cbc(aes))
  2. splice() a setuid binary’s pages (like /usr/bin/su) into the crypto context
  3. Craft a sendmsg() with specific associated-data bytes that produce the write
  4. Execute the now-corrupted binary → root shell

CVSS 7.8 High. No race, no spray, no offset hunting. A 732-byte Python script works identically across every major distro on any CPU architecture.

For Docker users, the impact is worse: it’s a container escape. Unless you block AF_ALG at the runtime level, any non-root process inside a container can reach through to the host kernel and gain root.

Checking Your Exposure

Before applying mitigations, verify your current state.

Check Your Kernel Version

1
uname -r

Vulnerable range: All kernels built from roughly 2017 (4.14+) up to the fix landing in April 2026.

Fixed mainline versions: 7.0, 6.19.12, 6.18.22.

Check if algif_aead Is Loaded

1
lsmod | grep algif

If this returns the algif_aead module, the vulnerable code path is available on your host. If disabled or blacklisted, you’ll see no output.

Check Docker Engine Version

Docker Engine 29.4.3 includes seccomp updates that reduce exposure. Check your version:

1
docker version --format '{{.Server.Version}}'

If you’re running 29.4.3 or later, the default seccomp profile still does not block AF_ALG — you need the custom profile below.

Quick Container Escape Test

Run this inside any container to check if AF_ALG is accessible:

1
2
docker run --rm alpine:latest sh -c \
  "python3 -c \"import socket; s=socket.socket(38, 1, 0); s.bind(('',)); print('AF_ALG available - VULNERABLE')\"" 2>/dev/null || echo "AF_ALG blocked - SAFE"

Expected output on a vulnerable host:

AF_ALG available - VULNERABLE

Immediate Mitigation — Blacklist the Kernel Module

The fastest fix that doesn’t require a reboot is blacklisting algif_aead. This has no impact on dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS, or SSH — none of them use algif_aead.

1
2
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf
sudo rmmod algif_aead 2>/dev/null || true

Verify the module is gone:

1
lsmod | grep algif

This is effective but only covers the host kernel. For Docker containers, you need the runtime mitigation below.

Docker Mitigation — Custom Seccomp Profile to Block AF_ALG

Docker’s built-in default seccomp profile does not block the AF_ALG socket family. Pod Security Standards Restricted also allows it through.

The fix: create a custom seccomp profile that blocks socket family 38 (AF_ALG) while preserving every other Docker default behavior.

Step 1: Extract Docker’s Current Seccomp Profile

1
2
3
4
5
sudo mkdir -p /etc/seccomp
docker run --rm alpine:3.20 cat /proc/1/status > /dev/null
CONTAINER_ID=$(docker run -d --rm alpine:3.20 sleep 30)
docker inspect "$CONTAINER_ID" --format '{{index .HostConfig.SecurityOpt 0}}'
docker kill "$CONTAINER_ID" > /dev/null 2>&1

The default profile is embedded in the Docker binary. The patch below defines the custom profile directly.

Step 2: Create the Custom Seccomp Profile

Create /etc/seccomp/docker-block-af-alg.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "architectures": [
    "SCMP_ARCH_X86_64",
    "SCMP_ARCH_X86",
    "SCMP_ARCH_AARCH64"
  ],
  "syscalls": [
    {
      "names": ["socket"],
      "action": "SCMP_ACT_ALLOW",
      "args": [
        {
          "index": 0,
          "value": 38,
          "op": "SCMP_CMP_NE"
        }
      ]
    }
  ]
}

This allows the socket syscall for every address family except AF_ALG (value 38). Every other Docker default syscall remains permitted. If you prefer to keep the full default profile and patch the socket entry, use the harden-docker-seccomp script which extracts and patches Docker’s actual profile.

Step 3: Configure Docker Daemon

1
2
3
4
5
sudo tee -a /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "seccomp-profile": "/etc/seccomp/docker-block-af-alg.json"
}
EOF

If your daemon.json already has content, merge carefully. Backup first:

1
sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak

Step 4: Reload Docker

1
sudo systemctl reload docker

This sends SIGHUP — no container downtime, no restart.

Step 5: Verify the Block Works

1
2
docker run --rm alpine:latest sh -c \
  "python3 -c \"import socket; s=socket.socket(38, 1, 0); s.bind(('',)); print('BLOCKED')\"" 2>&1

You should see a permission error or socket failure. If it says “BLOCKED”, the seccomp filter needs adjustment.

AppArmor Alternative

If you use AppArmor instead of seccomp, create a profile that denies af_alg:

profile docker-custom flags=(attach_disconnected,mediate_deleted) {

  # Include Docker's default AppArmor profile
  include <abstractions/docker>

  # Deny AF_ALG socket creation
  deny capability sys_admin,
  deny @{PROC}/sys/net/ r,
}

Load it and apply to containers:

1
2
sudo apparmor_parser -r -W /etc/apparmor.d/docker-custom
docker run --rm --security-opt apparmor=docker-custom alpine:latest sh

Kernel Patch Strategy

The seccomp profile is a runtime bandage. The real fix is patching the kernel.

Check Your Distro

Distro Advisory Fixed Kernel
Ubuntu 26.04 Ships unaffected 7.0+
Ubuntu 24.04 / 22.04 kmod mitigation Pending kernel update
Debian 12 / 11 Backported Check apt changelog linux-image-*
RHEL 9 / Rocky 9 / Alma 9 RHSA advisory Updated 5.14.0+
Fedora 40+ Updated kernel 6.19.12+

Check for available kernel updates:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Debian / Ubuntu
apt list --upgradable 2>/dev/null | grep linux-image

# RHEL / Rocky / Alma / Fedora
dnf check-update kernel

# Check what you're running
uname -r
dpkg -l | grep linux-image-$(uname -r)  # Debian
rpm -q kernel                             # RHEL

Apply and Reboot

1
2
3
4
5
6
# Debian / Ubuntu
sudo apt update && sudo apt upgrade -y linux-image-$(uname -r | cut -d. -f1-2)-amd64
sudo reboot

# RHEL / Rocky / Alma
sudo dnf update kernel -y && sudo reboot

Verify After Reboot

1
2
uname -r
lsmod | grep algif

Both checks should show the new kernel and algif_aead either absent or blacklisted.

Verification Checklist

After applying mitigations, confirm everything works:

1
2
3
4
5
6
echo "=== Host kernel ===" && uname -r
echo "=== algif_aead module ===" && lsmod | grep algif || echo "OK - not loaded"
echo "=== Docker seccomp profile ===" && docker info --format '{{.SecurityOptions}}' | grep -o 'seccomp=.*'
echo "=== Container AF_ALG test ==="
docker run --rm alpine:latest sh -c \
  "python3 -c 'import socket; s=socket.socket(38,1,0); s.bind((chr(0)*16,)); print(\"AF_ALG accessible - VULNERABLE\")'" 2>&1 || echo "AF_ALG blocked - OK"

Expected output:

=== Host kernel ===
6.19.12-amd64
=== algif_aead module ===
OK - not loaded
=== Docker seccomp profile ===
seccomp=/etc/seccomp/docker-block-af-alg.json
=== Container AF_ALG test ===
AF_ALG blocked - OK

Long-Term Hardening for Docker Homelabs

CVE-2026-31431 is a reminder that Docker’s default security posture leaves gaps. Use it as a catalyst to level up your container security:

  • Update Docker Engine to 29.4.3+ and keep it current
  • Run rootless Docker for an extra isolation layer around the daemon socket
  • Avoid --privileged wherever possible — it bypasses seccomp, AppArmor, and capabilities
  • Drop capabilities at the container level — don’t run containers with SYS_ADMIN, NET_ADMIN, or SYS_PTRACE unless required
  • Use --security-opt no-new-privileges:true to prevent binary privilege escalation inside containers
  • Consider runtime detection with Falco or Tracee for behavioral monitoring of anomalous syscalls
  • Keep your kernel updated — subscribe to your distro’s security feed and schedule quarterly maintenance windows

Conclusion

CVE-2026-31431 is not a theoretical vulnerability. The exploit is published, deterministic, and works across every major Linux distribution. For Docker users, it’s a container escape — the most serious class of vulnerability in containerized environments.

The fix is a two-layer approach:

  1. Patch the kernel to remove the vulnerable code path. This is the real fix.
  2. Deploy a custom seccomp profile blocking AF_ALG for all containers. This is the belt-and-suspenders that protects you until you reboot.

Both take about 15 minutes in a homelab. The custom seccomp profile takes five minutes to deploy with no container downtime. The kernel update takes another ten plus a reboot.

Your homelab’s containers share a kernel with the host. A vulnerability in that kernel is a vulnerability in every container. Mitigate Copy Fail today, and use it as a forcing function to review your broader Docker security posture.