
Ansible playbook to detect and apply kernel cmdline mitigation for CVE-2026-31431 (Copy Fail) across Debian/Ubuntu/RHEL fleets, with read-only detection and idempotent apply.
Detect and apply the kernel-cmdline mitigation for CVE-2026-31431 ("Copy Fail") across a Debian / Ubuntu / RHEL-family fleet using Ansible.
The CVE is a local privilege-escalation flaw in the Linux kernel's
algif_aead AF_ALG interface. Any unprivileged local user — including
service accounts like www-data, mysql, or processes inside an RCE'd web
app — can chain it to root in a few syscalls. The bug affects every major
distro shipped since 2017.
initcall_blacklist=algif_aead_init
to the kernel command line — when invoked with -e apply_mitigation=true.The kernel cmdline blacklist works on both built-in (RHEL family) and
modular (Debian/Ubuntu) configurations, which the often-cited
modprobe blacklist algif_aead workaround does not.
algif_aead_init() is the kernel function that registers the aead AF_ALG
algorithm with the crypto socket subsystem at boot. The cmdline parameter
initcall_blacklist=algif_aead_init instructs the kernel to skip that
initcall. The vulnerable code is still in the kernel binary, but
socket(AF_ALG, ..., "aead") returns ENOENT, so the exploit's first
syscall fails. No reachable surface, no privilege escalation.
Once vendors ship patched kernels and you reboot into them, the mitigation is no longer needed and can be removed.
# 1. Detect (read-only) — produces a per-host status report
ansible-playbook -i inventory check_cve_2026_31431.yml
# 2. Stage the mitigation on a single host first (no auto-reboot)
ansible-playbook -i inventory --limit <ip-or-host> \
-e apply_mitigation=true check_cve_2026_31431.yml
# 3. Reboot that host through your usual mechanism
# 4. Re-detect; confirm "Mitigation active: yes"
# 5. Roll out to the rest of the fleet in batches
A vulnerable host's report looks like this:
═══════════════════════════════════════════
Host: 192.168.1.42
Distro: Ubuntu 24.04
Kernel: 6.8.0-60-generic
Mitigation active: no
Mitigation staged: no
═══════════════════════════════════════════
⚠️ 192.168.1.42: CVE-2026-31431 mitigation is NOT active.
Out of scope for this recipe. Manual one-liners:
# Debian / Ubuntu
sudo sed -i 's/ initcall_blacklist=algif_aead_init//' /etc/default/grub
sudo update-grub
sudo reboot
# RHEL family
sudo grubby --update-kernel=ALL --remove-args="initcall_blacklist=algif_aead_init"
sudo reboot
The detect path is strictly read-only (uname, slurp /proc/cmdline,
slurp /etc/default/grub, grubby --info=ALL). Probes use
failed_when: false so unexpected output never aborts the play.
ignore_unreachable: true means hosts you can't reach get a clear
"UNREACHABLE — treat as VULNERABLE" warning instead of failing the run.
The apply path is wrapped in block/rescue so a single bad host never
stops the rest. The grub edit is idempotent and gated on
mitigation_staged so re-runs are no-ops.
bash tests/run-checks.sh all
Stages: lint (yamllint + ansible-lint) → syntax
(ansible-playbook --syntax-check) → dry-run (executes the playbook in
--check mode against localhost with -c local, both detect and apply
paths). The same script is invoked by the GitHub Actions matrix across
debian:12, ubuntu:22.04, ubuntu:24.04, and almalinux:9 containers
on every push and pull request.
What the dry-run does not verify:
update-grub, or trigger a reboot./proc/cmdline inside a CI container shows the runner host's cmdline,
not the container's, so mitigation_active in CI output is meaningless.
CI proves the playbook runs cleanly; it does not prove a host is
mitigated.See tests/README.md for more.
Vulnerability discovered by Taeyang Lee (Theori) and weaponized into a full exploit chain by the Xint Code Research Team, who deserve the credit for making the security community pay attention. This recipe just automates the mitigation they recommended.
Beerware. See LICENSE. TL;DR: do whatever you want with this;
if we ever meet, buy me a beer.
AESTECHNO — Hugues Orgitello — electronics design house, Montpellier, France.