Created
April 30, 2026 06:50
-
-
Save GottZ/7cb012306afcb77f31b34fb62158d9c3 to your computer and use it in GitHub Desktop.
CVE-2026-31431 ('Copy Fail') ad-hoc mitigation: blacklist algif_aead/authencesn without reboot. --apply/--check/--revert/--help.
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
| #!/bin/bash | |
| # cve-2026-31431-mitigate.sh | |
| # | |
| # Ad-hoc mitigation for CVE-2026-31431 ("Copy Fail") — an algif_aead / | |
| # authencesn page-cache scratch-write primitive in the Linux kernel crypto | |
| # userspace API. Applying this script blocks the vulnerable code paths | |
| # without requiring a kernel reboot. | |
| # | |
| # Strategy: | |
| # 1. Unload algif_aead, authencesn, authenc (refcnt permitting). | |
| # 2. Persist a modprobe blacklist + `install … /bin/false` so neither | |
| # auto-load nor explicit `modprobe` can bring them back. | |
| # 3. Verify by trying to instantiate "aead" via AF_ALG (must fail). | |
| # | |
| # Side effects on a typical server: | |
| # * dm-crypt / LUKS keep working (they use the in-kernel crypto API, not | |
| # AF_ALG). | |
| # * `cryptsetup benchmark` still works (uses algif_skcipher, not aead). | |
| # * Anything that explicitly requests AEAD via AF_ALG (rare: some | |
| # libkcapi consumers, custom IPsec userland) will get ENOENT. | |
| # | |
| # Usage: | |
| # sudo ./cve-2026-31431-mitigate.sh # apply (default) | |
| # sudo ./cve-2026-31431-mitigate.sh --check # report status, change nothing | |
| # sudo ./cve-2026-31431-mitigate.sh --revert # remove the blacklist | |
| # sudo ./cve-2026-31431-mitigate.sh --help | |
| set -euo pipefail | |
| CONF=/etc/modprobe.d/cve-2026-31431.conf | |
| TARGETS=(algif_aead authencesn authenc) | |
| c_red() { printf '\033[31m%s\033[0m\n' "$*"; } | |
| c_grn() { printf '\033[32m%s\033[0m\n' "$*"; } | |
| c_ylw() { printf '\033[33m%s\033[0m\n' "$*"; } | |
| c_dim() { printf '\033[2m%s\033[0m\n' "$*"; } | |
| usage() { | |
| awk ' | |
| NR==1 && /^#!/ { next } | |
| /^#/ { sub(/^# ?/, ""); print; next } | |
| { exit } | |
| ' "$0" | |
| exit 0 | |
| } | |
| require_root() { | |
| if [ "$(id -u)" -ne 0 ]; then | |
| c_red "[!] Must run as root (modprobe / /etc/modprobe.d write)." | |
| exit 1 | |
| fi | |
| } | |
| module_loaded() { [ -d "/sys/module/$1" ]; } | |
| module_refcnt() { | |
| if [ -r "/sys/module/$1/refcnt" ]; then | |
| cat "/sys/module/$1/refcnt" | |
| else | |
| echo "?" | |
| fi | |
| } | |
| af_alg_aead_reachable() { | |
| # Returns 0 if a userspace process can still instantiate the AEAD path. | |
| python3 - <<'PY' 2>/dev/null | |
| import socket, sys | |
| try: | |
| s = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG = 38 | |
| s.bind(("aead", "authencesn(hmac(sha256),cbc(aes))")) | |
| s.close() | |
| sys.exit(0) | |
| except OSError: | |
| sys.exit(1) | |
| PY | |
| } | |
| show_status() { | |
| echo "kernel : $(uname -r)" | |
| echo "modprobe : $CONF $( [ -f "$CONF" ] && echo '(present)' || echo '(absent)' )" | |
| echo "modules :" | |
| for m in "${TARGETS[@]}"; do | |
| if module_loaded "$m"; then | |
| printf ' %-12s loaded refcnt=%s\n' "$m" "$(module_refcnt "$m")" | |
| else | |
| printf ' %-12s unloaded\n' "$m" | |
| fi | |
| done | |
| if af_alg_aead_reachable; then | |
| c_red "AEAD path : REACHABLE via AF_ALG (system is exposed)" | |
| return 1 | |
| else | |
| c_grn "AEAD path : blocked (AF_ALG bind to authencesn fails)" | |
| return 0 | |
| fi | |
| } | |
| apply() { | |
| require_root | |
| if [ -f "$CONF" ] && ! af_alg_aead_reachable; then | |
| c_grn "[+] Already mitigated. Nothing to do." | |
| show_status || true | |
| return 0 | |
| fi | |
| echo "[*] Writing $CONF" | |
| cat >"$CONF" <<'EOF' | |
| # CVE-2026-31431 ("Copy Fail") — algif_aead / authencesn scratch-write. | |
| # Mitigation written by cve-2026-31431-mitigate.sh | |
| # Revert: delete this file, then `modprobe algif_aead` if you actually need it. | |
| blacklist algif_aead | |
| blacklist authencesn | |
| blacklist authenc | |
| install algif_aead /bin/false | |
| install authencesn /bin/false | |
| install authenc /bin/false | |
| EOF | |
| echo "[*] Unloading modules (if loaded and idle)" | |
| for m in "${TARGETS[@]}"; do | |
| if module_loaded "$m"; then | |
| rc=$(module_refcnt "$m") | |
| if [ "$rc" = "0" ]; then | |
| if modprobe -r "$m" 2>/dev/null; then | |
| c_grn " $m: removed" | |
| else | |
| c_ylw " $m: rmmod failed (will stay until next reboot, but blacklist is active)" | |
| fi | |
| else | |
| c_ylw " $m: in use (refcnt=$rc) — leaving loaded; blacklist prevents future loads" | |
| fi | |
| else | |
| c_dim " $m: not loaded" | |
| fi | |
| done | |
| echo | |
| echo "[*] Verification" | |
| if show_status; then | |
| c_grn "[+] Mitigation applied successfully." | |
| return 0 | |
| else | |
| c_red "[!] Mitigation did NOT block the AEAD path. Investigate." | |
| return 2 | |
| fi | |
| } | |
| revert() { | |
| require_root | |
| if [ -f "$CONF" ]; then | |
| rm -f "$CONF" | |
| c_grn "[+] Removed $CONF" | |
| else | |
| c_dim "[i] $CONF was not present" | |
| fi | |
| echo "[i] Modules will load on demand again. Run 'modprobe algif_aead' to test." | |
| } | |
| main() { | |
| case "${1:-apply}" in | |
| -h|--help|help) usage ;; | |
| --check|check|status) show_status ;; | |
| --revert|revert) revert ;; | |
| --apply|apply|"") apply ;; | |
| *) c_red "unknown arg: $1"; echo "try --help"; exit 1 ;; | |
| esac | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment