No description
  • Go 96.2%
  • Makefile 1.9%
  • C 1.1%
  • Shell 0.8%
Find a file
2026-05-01 14:28:56 -07:00
cmd/cve-check Added RHEL Official Mitigation (requires reboot) 2026-05-01 11:27:24 -07:00
internal Bug fix: update kprobe to work with more RHEL-based distros. 2026-05-01 14:28:56 -07:00
.gitignore update gitignore 2026-05-01 08:41:00 -07:00
.golangci.yml Updated comments, fixed TUI bugs, finalized version for new release. 2026-05-01 10:06:26 -07:00
BUILD Bug fix: update kprobe to work with more RHEL-based distros. 2026-05-01 14:28:56 -07:00
CONTRIBUTORS.md Update CONTRIBUTORS.md 2026-05-01 17:18:26 +00:00
FILEMAP.md created mitigation for RHEL-based, no-reboot 2026-05-01 08:28:20 -07:00
go.mod Updated comments, fixed TUI bugs, finalized version for new release. 2026-05-01 10:06:26 -07:00
go.sum Updated comments, fixed TUI bugs, finalized version for new release. 2026-05-01 10:06:26 -07:00
install.sh fixed comments 2026-05-01 12:42:39 -07:00
LICENSE.md Updated comments, fixed TUI bugs, finalized version for new release. 2026-05-01 10:06:26 -07:00
Makefile Fix kprobe 2026-05-01 10:52:46 -07:00
README.md Updated README with new Mitigation. 2026-05-01 11:55:45 -07:00
SECURITY.md Initial commit 2026-04-30 00:56:55 -07:00

CVE Checker for Copy Fail (CVE-2026-31431)

Authors/Contributors: Patrick Doyle (Author/Maintainer), Effie Renard (Co-Author), Chris F. (Support/Code Review/Testing)

AI Disclosure: Claude Code used for writing tests files, writing test data, and a code security review. All edits were manually audited by one of the contributors.

Description

Single statically-linked Go binary that classifies a Linux host's exposure to CVE-2026-31431.

The tool is passive. It does not exploit the bug. The mechanism probe (see safety statement below) issues exactly two syscalls and never touches the vulnerable code path.


What it Checks

Signal Source Flag
Kernel version vs. upstream fix uname(2) --kernel-version
Distro changelog mentions the CVE apt/rpm/apk or on-disk changelog.Debian.gz --changelog
algif_aead module status /proc/modules, modprobe.d, /lib/modules --module
Vulnerable surface reachable socket(AF_ALG) + bind(authencesn(...)) --mechanism
Compiled into vmlinux /boot/config-$(uname -r) or A (one-/proc/config.gz (part of --module)

If no check flags are given, all four run.


Output Modes

Mode Trigger Use case
Pretty styled (Default) TTY stdout, no --format override, no NO_COLOR env Interactive shell runs
Plain text non-TTY stdout, OR --format=text, OR NO_COLOR=1, OR --no-color Shell Scripts, Pipes
JSON --format=json Syslog/SIEM ingest, Shell Scripts
Quiet --quiet Exit code only (automation/scripts)

Force pretty when piped: cvecheck --pretty | tee report.txt. Force plain in a TTY: cvecheck --no-color or NO_COLOR=1 cvecheck.


Safety Statement (Mechanism Probe)

The probe issues exactly two syscalls socket(AF_ALG, SOCK_SEQPACKET, 0), and bind(fd, &SockaddrALG{Type:"aead", Name:"authencesn(hmac(sha256), cbc(aes))"}). It's then followed by close(fd). It performs no setsockopt for a key, no accept, no sendmsg, no splice, and no pipe creation. The vulnerable code path requires sendmsg of crypto data combined with a splice from a page-cache-backed file descriptor; none of those operations occur, so the bug cannot trigger.

A successful probe means the surface is reachable. Whether the kernel still contains the bug is decided by the kernel-version + changelog signals.

Side effect: a successful bind autoloads algif_aead via the kernel module autoloader. The module check runs before the mechanism probe so the loaded-state report is pre-probe. To suppress autoload entirely, blacklist algif_aead first.


Exit Codes

Code Meaning
0 PATCHED / NOT_VULNERABLE / MITIGATED / LIKELY_NOT_EXPLOITABLE
2 VULNERABLE or MECHANISM_REACHABLE (partial scan, kernel-version not checked)
3 INCONCLUSIVE (insufficient signals)

Distro Coverage

Distro Detect Kernel pkg Notes
Ubuntu / Debian / Mint / Pop!_OS apt linux-image-$(uname -r) Disk-first changelog read avoids apt changelog network fragility on signed kernels
RHEL / CentOS / Rocky / Alma / Fedora / Amazon Linux rpm kernel Ships algif_aead built-in (CONFIG_..._AEAD=y); blacklist mitigation not effective
Oracle Linux rpm kernel or kernel-uek UEK detected via uek substring in uname -r
openSUSE Leap / Tumbleweed / SLES rpm kernel-default Tumbleweed omits ID_LIKE; handled by explicit ID match
Alpine apk linux-lts / linux-virt / etc. Flavor picked from release suffix
Arch / CachyOS / Manjaro / Endeavour / Gentoo none n/a Rolling/source: relies on kernel-version check

Deploy

Automatic Install

Auto-detects your arch, downloads the right binary into $(pwd), and verifies its SHA-256 against the published SHA256SUMS:

curl -fsSL https://copyfail.pcdoyle.dev/install.sh | sh

The script exits non-zero on checksum mismatch and removes the bad file.

Download Binary (manual)

Pick the binary for your host architecture:

Architecture Binary
Intel/AMD 64-bit (x86_64) cvecheck-linux-x86_64
ARM 64-bit (aarch64) cvecheck-linux-arm64
Intel/AMD 32-bit (i?86) cvecheck-linux-x86

Verify before running:

curl -LO https://github.com/pcdoyle/copy-fail-cve-2026-31431/releases/latest/download/cvecheck-linux-x86_64
curl -LO https://github.com/pcdoyle/copy-fail-cve-2026-31431/releases/latest/download/SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS
chmod +x cvecheck-linux-x86_64

Build from Source

make build  # bin/cvecheck-linux-{x86_64,arm64,x86}
# SCP(SFTP) to Server:
scp ./bin/cvecheck-linux-x86_64 host:/tmp/  # Less than 4 MB, static, no glibc dep

Run the Program

SSH to Host

# SSH:
ssh <host>
# Run Binary:
/tmp/cvecheck-linux-x86_64  # Human friendly pretty output.
/tmp/cvecheck-linux-x86_64 --format=json # Script, Syslog, and SIEM friendly
/tmp/cvecheck-linux-x86_64 --format=text # Script, and Pipe friendly

Run as SSH Remote Command

ssh <host> /tmp/cvecheck-linux-x86_64   # If run directly defaults to --format=text
ssh <host> /tmp/cvecheck-linux-x86_64 --format=json # Syslog/SIEM-friendly

For Containers/Chroot:

Mount the host root somewhere readable and pass --root:

docker run --rm -v /:/host:ro alpine /tmp/cvecheck --root /<host-path>

--root controls all on-disk lookups (/etc/os-release, modprobe.d, /lib/modules, /boot/config-*, changelog files). The kernel-version and mechanism probes still touch the running kernel via uname(2) and socket(AF_ALG, ...).


Precedence Ladder

Resolved in this order (first match wins):

  1. PATCHED: Distro changelog references the CVE ID.
  2. NOT_VULNERABLE: Running kernel >= upstream fixed version.
  3. MITIGATED (grubby): initcall_blacklist=algif_aead_init present in /proc/cmdline.
  4. MITIGATED (kprobe): supervisor live with valid sentinel.
  5. MITIGATED (modprobe): algif_aead blacklisted and not loaded and not built into vmlinux.
  6. LIKELY_NOT_EXPLOITABLE: AF_ALG unavailable and module not on disk.
  7. MECHANISM_REACHABLE: Surface reachable but kernel version not checked.
  8. VULNERABLE: Kernel in vuln range and mechanism reachable.
  9. INCONCLUSIVE: None of the above match.

If --mechanism runs alongside --kernel-version and both signal trouble, the result is VULNERABLE with a remediation hint.


Mitigations

cvecheck can install one of three stop-gaps. Pick the right method for the host's algif_aead delivery shape and your reboot tolerance. Run cvecheck first; the verdict's remediation block calls out the correct subcommand. Or run cvecheck mitigation list for a side-by-side comparison.

Strategy comparison

Strategy Type Reboot Reversible Requires Use on
modprobe passive (file-only) no yes linux + root loadable-module hosts (Debian / Ubuntu / SUSE / Alpine / Arch)
kprobe active (supervisor) no yes linux/amd64 + root + BTF + FUNCTION_ERROR_INJECTION=y + BPF_KPROBE_OVERRIDE=y built-in algif_aead hosts (RHEL / Oracle / Amazon / CloudLinux)
grubby passive (boot arg) yes yes linux + root + /usr/sbin/grubby (RHEL-family) built-in algif_aead hosts where a reboot is acceptable

Built-in hosts on RHEL-family distros can use grubby (persistent, reboot required) or kprobe (no-reboot stop-gap, needs error injection). Built-in hosts whose kernel lacks error injection AND that cannot run grubby have no stop-gap: only a kernel upgrade fixes them.

All three methods are stop-gaps. A kernel upgrade is the durable fix.

Command surface

cvecheck mitigation                 # overview help
cvecheck mitigation list            # strategy comparison
cvecheck mitigation status          # aggregate status of every strategy
cvecheck mitigation kprobe   <verb> # install | uninstall | status
cvecheck mitigation modprobe <verb> # install | uninstall | status
cvecheck mitigation grubby   <verb> # install | uninstall | status

Every leaf has a per-command --help describing what files it writes, preflight requirements, and exit codes (e.g. cvecheck mitigation kprobe install --help).

mitigation modprobe: modprobe.d blacklist (loadable-module hosts)

Drops /etc/modprobe.d/cve-2026-31431.conf containing install algif_aead /bin/false, then rmmod algif_aead. Kernel autoloader refuses to bring it back until the conf is removed. Inert on built-in hosts.

sudo cvecheck mitigation modprobe install     # write conf + rmmod (idempotent)
sudo cvecheck mitigation modprobe status      # conf present? module loaded? built-in?
sudo cvecheck mitigation modprobe uninstall   # remove conf only (module stays unloaded)

After install, re-running cvecheck flips the verdict to MITIGATED via the existing module-blacklist rule (Blacklisted && !Loaded && !BuiltIn).

mitigation kprobe: eBPF kprobe stop-gap (built-in hosts)

Loads an eBPF kprobe at __x64_sys_socket that returns EAFNOSUPPORT to non-root callers and logs each attempt to journald. No binary copy beyond /usr/local/sbin/cvecheck, no separate artifact.

sudo /usr/local/sbin/cvecheck mitigation kprobe install     # preflight + systemd unit + enable --now
sudo cvecheck mitigation kprobe status                      # liveness, sentinel, recent journal lines
sudo cvecheck mitigation kprobe uninstall                   # stop, disable, remove unit (binary kept)
journalctl -u cvecheck-mitigation -f                        # live blocked-call audit log
Platform Linux/amd64 only
Privileges Root (CAP_BPF + CAP_SYS_ADMIN; ambient caps in the unit)
Kernel needs BTF (/sys/kernel/btf/vmlinux), CONFIG_FUNCTION_ERROR_INJECTION=y, CONFIG_BPF_KPROBE_OVERRIDE=y, __x64_sys_socket carries ALLOW_ERROR_INJECTION
Allowlist UID 0 only; hardcoded, no install flag
Persistence systemd enable --now; lives across crashes (Restart=on-failure) and reboots
Audit every blocked call hits journald: pid uid gid comm family=38 -> EAFNOSUPPORT
Scope Stop-gap. Does not patch the bug. Operators must still ship a fixed kernel.

After install, re-running cvecheck flips the verdict to MITIGATED with reason "AF_ALG kprobe mitigation active (... pid N)". Killing the supervisor or mitigation kprobe uninstall flips it back.

If preflight fails (no systemd, no BTF, no error injection support, kprobe attach refused) the install aborts non-zero with the failing gate. No fallback, a half-working install is worse than a failure in this case.

mitigation grubby: initcall_blacklist boot arg (RHEL-family, reboot required)

Appends initcall_blacklist=algif_aead_init to every kernel entry via grubby --update-kernel=ALL --args=.... This is Red Hat's documented mitigation for CVE-2026-31431: persistent across reboots, but the running boot is unaffected until the host reboots.

sudo cvecheck mitigation grubby install     # grubby --update-kernel=ALL --args=initcall_blacklist=algif_aead_init
sudo reboot                                 # required: arg only takes effect on next boot
sudo cvecheck mitigation grubby status      # 0 = arg in /proc/cmdline; 1 = pending reboot or not installed
sudo cvecheck mitigation grubby uninstall   # remove the arg from every kernel entry
Platform Linux + root
Distro RHEL-family (RHEL, CentOS, Rocky, AlmaLinux, Fedora, Amazon Linux, Oracle Linux); ships grubby preinstalled
Persistence Boot loader edit; survives reboots until uninstalled
Reboot Required. Mitigation is not active in the current boot until the host reboots.
Verify cat /proc/cmdline | grep initcall_blacklist post-reboot, or cvecheck mitigation grubby status
Scope Stop-gap. Does not patch the bug. Operators must still ship a fixed kernel.

After reboot, re-running cvecheck flips the verdict to MITIGATED with reason citing the boot arg. mitigation grubby uninstall removes the arg from grubby's stored entries; the running boot keeps it until the next reboot.

Remediation hints

  • Loadable-module distros (Debian, Ubuntu, SUSE, Alpine, Arch): block algif_aead until kernel upgrade lands. The install /bin/false form prevents kernel autoloading; a plain blacklist does not.
    echo 'install algif_aead /bin/false' | sudo tee /etc/modprobe.d/cve-2026-31431.conf
    sudo rmmod algif_aead 2>/dev/null; true
    
  • Built-in RHEL-family hosts that can reboot (RHEL, CentOS, Rocky, Alma, Fedora, Amazon, Oracle): append the boot arg via grubby and reboot. Red Hat's documented mitigation. Persistent across reboots.
    sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
    sudo reboot
    cat /proc/cmdline | grep initcall_blacklist  # verify post-reboot
    
  • Built-in hosts that cannot reboot: blacklist files are no-ops because CONFIG_CRYPTO_USER_API_AEAD=y puts the symbol inside vmlinux. Use cvecheck mitigation kprobe install if the kernel ships error injection support; otherwise only a kernel upgrade (or kpatch-style live patch) resolves exposure. The tool detects this.

Troubleshooting

Symptom Cause Fix
changelog: error="apt: exit status 100 ... Changelog unavailable for linux-signed-amd64" Debian/Ubuntu signed kernel; apt mirror does not serve the source-pkg changelog Error appears only when the on-disk file is missing in addition to the mirror being inaccessable, install apt-doc or wait for the mirror.
module: ... config_src="" No kernel config readable (no /boot/config-*, no /proc/config.gz, /boot is root-only) Run as root, or accept that BuiltIn cannot be determined. Verdict may still resolves via other signals.
mechanism: error="EAFNOSUPPORT" Kernel built without CONFIG_CRYPTO_USER_API, so the host is not vulnerable via AF_ALG Verdict will be LIKELY_NOT_EXPLOITABLE.

Sources