Userspace defense against CVE-2026-31431 ("Copy Fail")
- an AF_ALG / authencesn page-cache corruption local privilege-escalation
primitive that leaves no on-disk artefacts. Ships an LD_PRELOAD shim
that blocks AF_ALG socket creation in every dynamic-linked
process, plus a comprehensive read-only host posture auditor.
Designed to be a viable primary defense for the
windows where the other rungs of the ladder cannot reach: the vendor
kernel patch is not yet available (or you cannot reboot in that
window), modprobe blacklist is a no-op because
algif_aead is builtin (the RHEL default), or systemd
RestrictAddressFamilies does not cover the threat surface
(cron, login shells, container payloads). One .so + one
ld.so.preload line, system-wide.
🔬 Read the full writeup: Copy Fail (CVE-2026-31431) on rfxn.com/research — kernel-level mechanics of the AEAD over-read, the splice-into-page-cache path that turns it into a privesc primitive, and why the userspace shim closes the practical attacker windows.
A single copyfail.repo file works for all three: dnf
expands $releasever + $basearch per host, and
v1.0.1+ RPMs are GPG-signed (full gpgcheck=1
+ repo_gpgcheck=1).
sudo curl -sSL https://rfxn.github.io/copyfail/copyfail.repo \ -o /etc/yum.repos.d/copyfail.repo sudo dnf install -y afalg-defense
After install the LD_PRELOAD shim is on disk but
not yet active. Wiring /etc/ld.so.preload
from a package post-install is too dangerous - one broken upgrade
locks every dynamic-linked binary out of dlopen. Activation is an
explicit operator action:
sudo /usr/sbin/copyfail-shim-enable # smoke-tests, then writes /etc/ld.so.preload sudo /usr/sbin/copyfail-shim-disable # reverses it
The package also ships an equally low-barrier
mitigation: a modprobe drop-in that severs
algif_aead, authenc, and authencesn
at the kernel level. When the kernel exposes AF_ALG as a loadable
module - most stock mainline kernels do - this stacks with the
shim and deserves equal weight. The shim blocks every
userspace caller at libc; the blacklist removes the kernel attack
surface entirely. Different mechanisms, both one-line operator actions.
# Decide whether the blacklist will be effective on this kernel.
ls /sys/module/algif_aead 2>/dev/null && echo "modular - blacklist effective" \
|| echo "builtin or absent - shim is your primary defense"
grep -E 'ALG_USERMODE|CRYPTO_USER_API' /boot/config-$(uname -r) 2>/dev/null
# =m -> modular (blacklist is primary) =y -> builtin (shim is primary)
# If modular, drop a blacklist into /etc/modprobe.d/ and unload
# anything already resident. The CVE-2026-31431 chain is the trio at
# the bottom; the algif_* family is added for general AF_ALG hygiene.
sudo tee /etc/modprobe.d/99-no-afalg.conf >/dev/null <<'EOF'
install af_alg /bin/false
install algif_aead /bin/false
install algif_skcipher /bin/false
install algif_hash /bin/false
install algif_rng /bin/false
install authenc /bin/false
install authencesn /bin/false
EOF
sudo rmmod algif_aead authenc authencesn 2>/dev/null || true
(The package also ships this file under
/usr/share/doc/afalg-defense/examples/no-afalg-modprobe.conf
- same content, copy that into place if you prefer the audit trail.)
Where your kernel allows it, deploy both the shim
and the blacklist - belt-and-suspenders coverage for the price of two
sudo commands.
python3 -c 'import socket; socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)' # expect: PermissionError [Errno 1] Operation not permitted
Always disable before uninstalling so RPM does not erase the .so out from under /etc/ld.so.preload:
sudo /usr/sbin/copyfail-shim-disable sudo dnf remove afalg-defense afalg-defense-shim afalg-defense-auditor
The package's %preun scriptlet also scrubs /etc/ld.so.preload on full erase as a safety net, but disabling first keeps the operation transparent.
| Package | Arch | Contents |
|---|---|---|
afalg-defense | x86_64 | meta - pulls shim + auditor |
afalg-defense-shim | x86_64 | /usr/lib64/no-afalg.so + copyfail-shim-{enable,disable} helpers |
afalg-defense-auditor | noarch | /usr/sbin/copyfail-local-check (Python, stdlib-only, read-only) |
Auditor-only is a valid install pattern for hosts that do not yet trust an LD_PRELOAD on every dynamic-linked process:
sudo dnf install -y afalg-defense-auditor
The auditor is read-only. It writes only to mkdtemp() sentinel
files, never modifies /usr/bin or /etc, and
runs unprivileged (some checks degrade gracefully without root). Five
categories: ENV, KERNEL, MITIGATION,
HARDENING, DETECTION.
copyfail-local-check # human-readable, only flags non-OK copyfail-local-check --verbose # show passing checks too copyfail-local-check --json # SIEM-friendly: posture.verdict + per-check details copyfail-local-check --skip-trigger # no live AF_ALG probe copyfail-local-check --skip-hardening # skip suid/page-cache audit copyfail-local-check --emit-remediation # print a bash script of suggested fixes
JSON output is structured for fleet ingest. The posture.verdict field is the headline; consume that, not the human report:
{
"schema_version": "1.1",
"tool": "copyfail_checker",
"cve": "CVE-2026-31431",
"checks": [ ...per-check details... ],
"posture": {
"verdict": "patched | vulnerable | vulnerable_kernel_userspace_mitigated |
kernel_likely_safe | inconclusive",
"layers": {
"kernel_patched": "ok | missing | skipped | not_evaluated",
"af_alg_unreachable": "...",
"modprobe_blacklist": "...",
"ld_preload_shim": "...",
"systemd_restriction": "...",
"user_service_dropin": "...",
"seccomp_runtime": "...",
"auditd_running": "...",
"audit_rule_af_alg": "..."
}
},
"exit_code": 0
}
Exit codes: 0 clean, 2 VULN (no userspace mitigation), 3 VULN-but-mitigated, 4 hardening recommendations only.
Each release on
github.com/rfxn/copyfail/releases
ships per-EL binary RPMs, an SRPM, the .repo file, and the
public signing key as release assets. The same files are published in
the dnf tree below.
The .repo file (works on EL8/EL9/EL10):
sudo curl -sSL https://rfxn.github.io/copyfail/copyfail.repo \ -o /etc/yum.repos.d/copyfail.repo
And the public signing key (dnf imports this automatically on first install, but you can pre-import it):
sudo curl -sSL https://rfxn.github.io/copyfail/RPM-GPG-KEY-copyfail \ | sudo rpm --import /dev/stdin
Per-EL binary RPMs are independently compiled against each
distribution's glibc (EL8: 2.28 with split libdl;
EL9/EL10: 2.34+ with merged libdl) - do not cross-install
across ELs.
| File | sha256 |
|---|---|
| afalg-defense-1.0.1-1.el8.x86_64.rpm | 3c1129956b52ca451eb928d304f9b4bd2e99eb70e8f52c03e486d1c843cbd652 |
| afalg-defense-shim-1.0.1-1.el8.x86_64.rpm | c0613d5569536edeee9f172d8b370128c0eef0081032f0745695487a7a61015e |
| afalg-defense-auditor-1.0.1-1.el8.noarch.rpm | 61e7ddefbfb0d5b03f012221cda4fdba3cf471f53c250825d7b43ea153810021 |
| afalg-defense-1.0.1-1.el8.src.rpm | 9a0aa3049508893ede159fd31a21480ccb9eb7cdede4c68bbe190a7f6721e7ad |
| File | sha256 |
|---|---|
| afalg-defense-1.0.1-1.el9.x86_64.rpm | 381fc32223e22abc6db63bf72bd2403e66ed2fe59759c0b3afe0281c37db3eab |
| afalg-defense-shim-1.0.1-1.el9.x86_64.rpm | 833f891a30f596872c393e979475cc9563d18f941ce58e038610f24bcd431fff |
| afalg-defense-auditor-1.0.1-1.el9.noarch.rpm | adf0addeddd188871fb98bbb761ac1b9bdfafc5a653cc33451f41f3dd0715181 |
| afalg-defense-1.0.1-1.el9.src.rpm | 0326696f9ee0aa7b9cb14f4ae8f2fa5774257906dc32ad7280cebd691fd8dbf7 |
| File | sha256 |
|---|---|
| afalg-defense-1.0.1-1.el10.x86_64.rpm | 046ec488b1be69f161f52b52a65ccf5bba0eccde6895eb9ec6eceebd59242b31 |
| afalg-defense-shim-1.0.1-1.el10.x86_64.rpm | 86caff2df7633da369c6e5e35c2a431c46d29d05ee13b0691f07fc3461a480eb |
| afalg-defense-auditor-1.0.1-1.el10.noarch.rpm | 2476ad91466e7716a0940d5b5080c416607bac6ea77a9e819f5b8563e3b7de30 |
| afalg-defense-1.0.1-1.el10.src.rpm | fac98d3b442178ea13d3abd449e18ed83f2e3623cb6118bc5194e5fc4aa4997a |
1.0.1 and later are signed by the Copyfail Project Signing Key:
fingerprint: 6001 1CDC EA2F F52D 975A FDEE 6D30 F32C D5E8 0F80 uid: Copyfail Project Signing Key <proj@rfxn.com> key file: /RPM-GPG-KEY-copyfail
The copyfail.repo sets both gpgcheck=1
(verifies each RPM) and repo_gpgcheck=1 (verifies
repomd.xml via the detached repomd.xml.asc
we publish alongside it). dnf imports the public key from
gpgkey= on first use.
Verify out-of-band:
curl -sSL https://rfxn.github.io/copyfail/RPM-GPG-KEY-copyfail | gpg --import gpg --fingerprint proj@rfxn.com # expect: 6001 1CDC EA2F F52D 975A FDEE 6D30 F32C D5E8 0F80 rpm --import https://rfxn.github.io/copyfail/RPM-GPG-KEY-copyfail rpm -K /path/to/afalg-defense-1.0.1-1.el9.x86_64.rpm # expect: digests signatures OK
Every layer of defense for AF_ALG-class bugs has failure modes. The case for this package is that the conditions that defeat the rungs above it are not the same conditions that defeat the shim. That asymmetry is why the shim is a viable primary defense rather than just a backup.
| Rung | Where it fails | What the shim does there |
|---|---|---|
| 1. Kernel patch (vendor) | EL7 EOL; EL8/9/10 rollout lags disclosure by days to weeks; can't always reboot in the hot window | Closes the window without a reboot. Live install, no kernel touch |
2. modprobe blacklist of algif_aead / authenc / authencesn |
Only when these are loaded as modules (not builtin) and not already resident from boot. On modular kernels (most stock mainline), this is an equally low-barrier primary defense that stacks with the shim. Becomes a no-op when algif_aead is builtin (RHEL default) |
Picks up the slack on builtin-crypto kernels - every userspace caller still goes through libc socket(2) |
3. systemd RestrictAddressFamilies |
Misses cron, sshd login shells, containers with their own pid 1, pre-restriction units | Global - /etc/ld.so.preload applies to every dyn-linked process regardless of init |
| 4. LD_PRELOAD shim (this package) | Static binaries; direct syscall instruction; SUID strip |
(see right column for coverage) |
| 5. seccomp filter | Per-unit; operationally heavy: explicit policy per service | One .so, one preload line, whole host |
Where the shim itself fails (static binaries, direct syscall
instruction, SUID stripping) is attacker engineering
territory. The other rungs fail under routine
operator reality - vendors haven't shipped yet, the kernel
was built with builtin crypto, the threat lives in a cron job. Deploy
this rung first; layer the others as they become available.
Full writeup is in the README.
Bugs, packaging problems, false positives, missing distros - please
file a
GitHub issue. Include the auditor's --json output if
the report is about detection, and the output of
uname -a + cat /etc/os-release for any
runtime question.