Created
May 5, 2026 08:20
-
-
Save jfstenuit/7a90b1d6d1116863693907e6e1cd3de4 to your computer and use it in GitHub Desktop.
Check CopyFail (CVE-2026-31431)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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