Skip to content

Instantly share code, notes, and snippets.

@jfstenuit
Created May 5, 2026 08:20
Show Gist options
  • Select an option

  • Save jfstenuit/7a90b1d6d1116863693907e6e1cd3de4 to your computer and use it in GitHub Desktop.

Select an option

Save jfstenuit/7a90b1d6d1116863693907e6e1cd3de4 to your computer and use it in GitHub Desktop.
Check CopyFail (CVE-2026-31431)
#!/usr/bin/env bash
# ============================================================================
# CVE-2026-31431 "Copy Fail" — Vulnerability Checker
# ----------------------------------------------------------------------------
# Safe, read-only checks. Does NOT attempt exploitation.
# Targets Debian / Ubuntu but works on most Linux distros.
#
# Checks performed:
# 1. Kernel version — is it in the affected range?
# 2. Distro-specific patched kernel detection
# 3. algif_aead module status (loaded / built-in / absent)
# 4. Active mitigations (modprobe blacklist, initcall_blacklist)
# 5. Functional probe — can an AF_ALG AEAD socket actually be created?
#
# Exit codes: 0 = not vulnerable / mitigated
# 1 = likely vulnerable
# 2 = indeterminate (manual review needed)
# ============================================================================
set -euo pipefail
RED='\033[0;31m'
GRN='\033[0;32m'
YLW='\033[1;33m'
BLU='\033[0;34m'
RST='\033[0m'
pass() { printf "${GRN}[PASS]${RST} %s\n" "$*"; }
fail() { printf "${RED}[FAIL]${RST} %s\n" "$*"; }
warn() { printf "${YLW}[WARN]${RST} %s\n" "$*"; }
info() { printf "${BLU}[INFO]${RST} %s\n" "$*"; }
VULN=0 # incremented by each failing check
echo "============================================================"
echo " CVE-2026-31431 \"Copy Fail\" Vulnerability Checker"
echo "============================================================"
echo ""
# ------------------------------------------------------------------
# 1. Basic kernel version check
# ------------------------------------------------------------------
KVER=$(uname -r)
info "Running kernel: $KVER"
# Extract numeric major.minor.patch (strip -suffix)
IFS='.-' read -r KMAJ KMIN KPATCH _ <<< "$KVER"
KMAJ=${KMAJ:-0}; KMIN=${KMIN:-0}; KPATCH=${KPATCH:-0}
# Affected: 4.14 <= kernel < fixed versions
# Fixed upstream: 6.18.22+, 6.19.12+, 7.0+
# Anything before 4.14 is NOT affected.
kernel_fixed=0
if (( KMAJ < 4 )) || { (( KMAJ == 4 )) && (( KMIN < 14 )); }; then
pass "Kernel $KVER predates the vulnerable code (introduced in 4.14)."
kernel_fixed=1
elif (( KMAJ >= 7 )); then
pass "Kernel $KVER is >= 7.0 — upstream fix included."
kernel_fixed=1
elif (( KMAJ == 6 && KMIN == 19 && KPATCH >= 12 )); then
pass "Kernel $KVER is in the 6.19.x fixed range (>= 6.19.12)."
kernel_fixed=1
elif (( KMAJ == 6 && KMIN == 18 && KPATCH >= 22 )); then
pass "Kernel $KVER is in the 6.18.x fixed range (>= 6.18.22)."
kernel_fixed=1
else
fail "Kernel $KVER falls in the affected range (4.14 — pre-fix)."
(( VULN++ )) || true
fi
# ------------------------------------------------------------------
# 2. Distro-specific patched-kernel detection
# ------------------------------------------------------------------
if [ -f /etc/os-release ]; then
. /etc/os-release
info "Distribution: ${PRETTY_NAME:-$ID}"
# Ubuntu 26.04+ ships a fixed kernel
if [[ "${ID:-}" == "ubuntu" ]]; then
UVER="${VERSION_ID:-0}"
if awk "BEGIN{exit !($UVER >= 26.04)}" 2>/dev/null; then
pass "Ubuntu $UVER ships a patched kernel by default."
kernel_fixed=1
fi
fi
fi
# Check if a distro security patch was applied (Debian/Ubuntu changelog)
if dpkg -l 2>/dev/null | grep -q "^ii.*linux-image.*$(uname -r | sed 's/-[a-z].*$//')"; then
CHANGELOG=$(zcat /usr/share/doc/linux-image-"$(uname -r)"/changelog.Debian.gz 2>/dev/null || true)
if echo "$CHANGELOG" | grep -qi "CVE-2026-31431" 2>/dev/null; then
pass "Distro kernel package changelog mentions CVE-2026-31431 fix."
kernel_fixed=1
fi
fi
# ------------------------------------------------------------------
# 3. algif_aead module status
# ------------------------------------------------------------------
MODULE_STATUS="unknown"
if grep -qw algif_aead /proc/modules 2>/dev/null; then
MODULE_STATUS="loaded"
warn "algif_aead kernel module is currently LOADED."
elif grep -rqs 'algif_aead' /lib/modules/"$(uname -r)"/modules.builtin 2>/dev/null; then
MODULE_STATUS="builtin"
warn "algif_aead is compiled BUILT-IN to this kernel (cannot rmmod)."
elif modinfo algif_aead &>/dev/null; then
MODULE_STATUS="available"
info "algif_aead module exists but is not currently loaded."
else
MODULE_STATUS="absent"
pass "algif_aead module not found — attack surface absent."
fi
# ------------------------------------------------------------------
# 4. Mitigation checks
# ------------------------------------------------------------------
MITIGATED=0
# 4a. modprobe blacklist (works only when module is loadable, not built-in)
if grep -rqs 'install algif_aead /bin/false\|install algif_aead /bin/true\|blacklist algif_aead' \
/etc/modprobe.d/ 2>/dev/null; then
if [[ "$MODULE_STATUS" == "builtin" ]]; then
warn "modprobe.d blacklist found, but algif_aead is BUILT-IN — blacklist has NO effect."
else
pass "algif_aead is blacklisted via modprobe.d."
MITIGATED=1
fi
fi
# 4b. initcall_blacklist (works even for built-in modules)
if grep -q 'initcall_blacklist=.*algif_aead_init' /proc/cmdline 2>/dev/null; then
pass "algif_aead_init is blacklisted via kernel command-line (initcall_blacklist)."
MITIGATED=1
fi
# 4c. Module not present at all
if [[ "$MODULE_STATUS" == "absent" ]]; then
MITIGATED=1
fi
# ------------------------------------------------------------------
# 5. Functional probe — can AF_ALG AEAD actually be used?
# ------------------------------------------------------------------
# This is the most definitive check: try to create the exact socket
# the exploit requires. If it fails, the attack path is blocked.
PROBE_RESULT="unknown"
if command -v python3 &>/dev/null; then
PROBE_OUTPUT=$(python3 -c "
import socket, sys
try:
s = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
s.bind(('aead', 'authencesn(hmac(sha256),cbc(aes))'))
s.close()
print('OPEN')
except Exception as e:
print('BLOCKED: ' + str(e))
" 2>&1) || true
if [[ "$PROBE_OUTPUT" == "OPEN" ]]; then
fail "AF_ALG AEAD socket (authencesn) — can be created. Attack path OPEN."
(( VULN++ )) || true
PROBE_RESULT="open"
else
pass "AF_ALG AEAD socket creation blocked: ${PROBE_OUTPUT#BLOCKED: }"
MITIGATED=1
PROBE_RESULT="blocked"
fi
else
warn "python3 not found — skipping functional socket probe."
PROBE_RESULT="skipped"
fi
# ------------------------------------------------------------------
# 6. Bonus: check for active AF_ALG usage (informational)
# ------------------------------------------------------------------
if command -v ss &>/dev/null; then
ALG_SOCKS=$(ss -f alg 2>/dev/null | tail -n +2 | wc -l)
if (( ALG_SOCKS > 0 )); then
warn "$ALG_SOCKS active AF_ALG socket(s) detected (ss -f alg). Investigate."
fi
fi
# ------------------------------------------------------------------
# Verdict
# ------------------------------------------------------------------
echo ""
echo "============================================================"
if (( kernel_fixed )) || (( MITIGATED )); then
if (( kernel_fixed )); then
pass "VERDICT: Kernel appears PATCHED or not in the affected range."
else
warn "VERDICT: Kernel is in the affected range but mitigation is active."
echo " Recommend patching the kernel for a permanent fix."
fi
echo "============================================================"
exit 0
elif (( VULN > 0 )); then
fail "VERDICT: This system is LIKELY VULNERABLE to CVE-2026-31431."
echo ""
echo " Recommended actions (pick one):"
echo " 1. Patch: sudo apt update && sudo apt upgrade && sudo reboot"
echo " 2. Mitigate: echo 'install algif_aead /bin/false' | sudo tee /etc/modprobe.d/disable-algif.conf"
echo " sudo rmmod algif_aead 2>/dev/null || true"
echo " (If built-in, use initcall_blacklist instead:)"
echo " sudo grubby --update-kernel=ALL --args='initcall_blacklist=algif_aead_init'"
echo " sudo reboot"
echo "============================================================"
exit 1
else
warn "VERDICT: Could not determine status conclusively. Manual review needed."
echo "============================================================"
exit 2
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment