Articles on: ggRock

Linux Kernel Local Privilege Escalation (LPE) Vulnerability Mitigations

Linux Kernel Local Privilege Escalation - Fragnesia, Dirty Frag, Copy-Fail, ssh-keysign-pwn, nf_tables Typo, CIFSwitch, DirtyClone, SCTPhantom, ebtables SNAT

Severity: Critical | Last Updated: 2026-08-14 | CVEs: CVE-2026-46300, CVE-2026-43284, CVE-2026-31431, CVE-2026-46333, CVE-2026-23111, CVE-2026-46243, CVE-2026-43503, CVE-2026-64564, CVE-2026-53266

CVE

Codename

Disclosed

CVSS

Patch Available

CISA KEV

CVE-2026-46300

Fragnesia

2026-05-13

7.8 High

Yes

No

CVE-2026-43284

Dirty Frag

2026-05-13

7.8 High

Yes

No

CVE-2026-31431

Copy-Fail

2026-04-29

7.8 High

Yes

Yes - deadline 2026-05-15

CVE-2026-46333

ssh-keysign-pwn

2026-05-15

7.8 High

Yes

No

CVE-2026-23111

nf_tables Typo

2026-06-08

7.8 High

Yes

No

CVE-2026-46243

CIFSwitch

2026-05-27

7.8 High

Yes

No

CVE-2026-43503

DirtyClone

2026-06-26

8.8 High

Yes

No

CVE-2026-64564

SCTPhantom

2026-08-06

8.5 High (v4.0)

Yes

No

CVE-2026-53266

ebtables SNAT

2026-06-25

8.8 High

Yes

No


Summary

Nine Linux kernel local privilege escalation vulnerabilities have been publicly disclosed in rapid succession, each allowing an unprivileged local user to gain root access. Public proof-of-concept exploits or full technique write-ups exist for all eight. All should be treated as actively exploitable.
They fall into a few overlapping groups by mechanism. The first three (Fragnesia, Dirty Frag, Copy-Fail) corrupt the page cache via a network stack bug, requiring no special capabilities. ssh-keysign-pwn is a ptrace/pidfd_getfd race during process exit that steals file descriptors from dying privileged processes. The namespace-reachable CVEs (nf_tables Typo, CIFSwitch, and the ebtables SNAT flaw) are reached through unprivileged user namespaces and mitigated by a single namespace restriction. DirtyClone is a hybrid of the page-cache and namespace groups, making it the highest-severity page-cache entry at CVSS 8.8. SCTPhantom is a use-after-free in the SCTP protocol: it is reachable because the sctp module autoloads on demand for any local user, and it is mitigated by blocking that autoload. CVE-2026-53266 is an out-of-bounds write in the bridge ebtables SNAT target; like the nf_tables and CIFSwitch flaws its reachability depends on the CAP_NET_ADMIN an unprivileged user gets from a user namespace, so the same namespace restriction closes it and it needs no dedicated script.
CVE-2026-31431 (Copy-Fail) has been added to the CISA Known Exploited Vulnerabilities catalog with a mandatory remediation deadline of 2026-05-15 for US federal civilian agencies.
Workarounds are available and effective for all nine. Patched kernels are available for all supported Debian releases, with the SCTPhantom fix being the most recent (see Recommended Actions).


Am I Affected?

Yes, if all of the following are true:

  • You are running Linux
  • Your kernel predates the patched versions for your distribution (see Priority 1 below)
  • An untrusted local user can log in or execute code on the system

Cloud VMs, shared hosting environments, container hosts with untrusted workloads, and developer machines with shared access are all in scope. Systems where only trusted administrators have shell access are lower risk but should still be patched.
The namespace-reachable CVEs (nf_tables Typo, CIFSwitch, DirtyClone, and the ebtables SNAT flaw) additionally require unprivileged user namespaces to be enabled - the default on most distributions. CIFSwitch further requires cifs-utils to be installed. DirtyClone also requires esp4/esp6 modules to set up a loopback IPsec tunnel. SCTPhantom requires SCTP to be reachable, but note that the sctp module autoloads on demand when any local process opens an SCTP socket, so a host that "does not use SCTP" is still exposed unless the module is blacklisted. CVE-2026-53266 additionally requires an attacker to configure a bridge ebtables SNAT rule, which needs the CAP_NET_ADMIN that an unprivileged user obtains from a user namespace.


Vulnerability Details

CVE-2026-46300 - Fragnesia

Disclosed: 2026-05-13 | CVSS: 7.8 (High)
Affected subsystem: XFRM ESP-in-TCP (espintcp ULP)
When a TCP socket switches to espintcp upper-layer protocol mode after file data has already been spliced into its receive queue, the kernel mistakenly treats those queued file-backed pages as ESP ciphertext. This causes one byte of an AES-GCM keystream to be XORed into a read-only file's kernel page cache entry.
By constructing a lookup table of IV nonces that produce each possible keystream byte, an attacker can flip any byte in a cached file to any value - one byte per invocation. The exploit uses this to overwrite the first 192 bytes of /usr/bin/su in the page cache with a stub that calls setresuid(0,0,0) and launches a shell.

The on-disk binary is never modified. Only the in-memory page cache is affected. A compromised binary persists in RAM and will produce a root shell on every invocation until the page cache is explicitly flushed or the system is rebooted.

Discovered by: William Bowling, V12 Security Team Public PoC: https://github.com/v12-security/pocs/tree/main/fragnesia Reference: https://cybersecuritynews.com/fragnesia-linux-vulnerability/

CVE-2026-43284 - Dirty Frag

Disclosed: 2026-05-13 | CVSS: 7.8 (High)
Affected subsystem: XFRM fragment coalescing
A related logic flaw in how the kernel coalesces socket buffer fragments causes it to "forget" that a fragment is shared, corrupting memory it was not supposed to touch. The attack surface and exploitation technique are similar to Fragnesia, and the same modules are involved.
Reference: https://cybersecuritynews.com/dirty-frag-linux-vulnerability/

CVE-2026-31431 - Copy-Fail

Disclosed: 2026-04-29 | CVSS: 7.8 (High) | CISA KEV: Yes
Affected subsystem: AF_ALG kernel crypto API (algif_aead)
A logic flaw in the AEAD crypto socket path allows page cache corruption via the AF_ALG socket family (Family 38). The algif_aead module is the direct attack surface; af_alg is its parent and must also be disabled.
Reference: https://cybersecuritynews.com/linux-kernel-0-day-vulnerability-exploited/

CVE-2026-46333 - ssh-keysign-pwn

Disclosed: 2026-05-15 | CVSS: 7.8 (High)
Affected subsystem: ptrace / pidfd_getfd - process exit race
__ptrace_may_access() skips its dumpable check when the target task's mm is NULL. During do_exit(), the kernel runs exit_mm() before exit_files(), creating a window where a privileged process has dropped its memory mappings but still holds its open file descriptors. An unprivileged process running under the same UID can call pidfd_getfd(2) in that window and steal open file descriptors from the dying process.
If those descriptors point at root-owned files opened before privilege drop - such as SSH host keys opened by ssh-keysign before permanently_set_uid(), or /etc/shadow opened by chage before setreuid() - the attacker now holds a read handle on those files.
Unlike the page-cache CVEs, this vulnerability does not corrupt in-memory binaries. It is a file descriptor theft primitive.
Discovered by: Qualys Public PoC: https://github.com/0xdeadbeefnetwork/ssh-keysign-pwn Upstream fix: https://github.com/torvalds/linux/commit/31e62c2ebbfdc3fe3dbdf5e02c92a9dc67087a3a Reference: https://almalinux.org/blog/2026-05-15-ssh-keysign-pwn-cve-2026-46333/

CVE-2026-23111 - nf_tables Typo

Disclosed: 2026-06-08 (PoC) | CVSS: 7.8 (High) | Upstream patch: 2026-02-05
Affected subsystem: nf_tables (Netfilter packet filtering)
A single inverted check in nft_map_catchall_activate() causes the kernel to use the wrong sense of an activity check while aborting a failed nf_tables transaction. Each aborted transaction permanently decrements a chain's use counter; once it reaches zero, the chain is freed while a stale reference still points at it, producing a use-after-free. A fully weaponized public exploit achieves root with greater than 99% reliability across Debian Bookworm and Trixie.
nf_tables cannot be disabled on a ggRock host - it backs the firewall. The practical mitigation is denying unprivileged user namespace creation, which closes the exploit's reachability path without touching the firewall stack.
Upstream fix: https://github.com/torvalds/linux/commit/3da1fdf4efbc490041eb4f836bf596201203f8f2 Reference: https://thehackernews.com/2026/06/one-character-linux-kernel-flaw-enables.html

CVE-2026-46243 - CIFSwitch

Disclosed: 2026-05-27 | CVSS: 7.8 (High) | CVE assigned: 2026-06-01
Affected subsystem: CIFS client cifs.spnego upcall + cifs-utils helper
The kernel's cifs.spnego key type did not validate the origin of its key description. An unprivileged user could call request_key("cifs.spnego", fake_description, ...) directly, causing the default request-key rule to launch cifs.upcall as root. A forged description containing an attacker-controlled pid makes the root helper switch into the attacker's namespace, where a namespace-local nsswitch.conf can run attacker code as root - for example, dropping a sudoers.d entry.
On a diskless ggRock boot host, cifs-utils is typically not installed - if cifs.upcall is absent, the host is not exposed. The same unprivileged-user-namespace restriction that mitigates the nf_tables Typo also closes the CIFSwitch reachability path.
Discovered by: Asim Manizada Public PoC: https://github.com/manizada/CIFSwitch Reference: https://heyitsas.im/posts/cifswitch/

CVE-2026-43503 - DirtyClone

Disclosed: 2026-06-26 | CVSS: 8.8 (High - highest of the page-cache group) | Patch merged: 2026-05-21 (shipped v7.1-rc5, 2026-05-24)
Affected subsystem: Networking packet clone helpers (__pskb_copy_fclone(), skb_shift())
DirtyClone is the fourth variant in the DirtyFrag family. When the kernel clones a network packet internally, __pskb_copy_fclone() and skb_shift() drop the SKBFL_ZEROCOPY_FRAG flag that marks packet memory as shared with a file on disk. The attacker loads a privileged binary such as /usr/bin/su into the page cache, wires those pages into a network packet, forces a kernel clone, then routes the clone through a loopback IPsec tunnel they control. The AES-GCM decryption step overwrites the binary's login checks in the page cache. The next invocation of su hands over root.

The on-disk binary is never modified; a reboot restores it. File-integrity tools will not detect the attack, and it leaves no audit trail.

The exploit requires CAP_NET_ADMIN to configure the loopback IPsec tunnel. On Debian and Fedora, an unprivileged user obtains this capability by creating a user namespace - the default configuration. Because the page cache is shared at the host level, modifications made inside a namespace affect every process on the machine, which is why CVSS is 8.8 rather than 7.8.

Unlike the other page-cache CVEs in this set, the modification is host-wide - it affects every process on the machine, not just those in the attacker's namespace. Reboot after flushing the cache to fully restore affected binaries.

If harden_all.sh has already run harden_fragnesia.sh and harden_nftables.sh, both DirtyClone gates are already closed. The dedicated script applies and verifies them independently.
Discovered by: JFrog Security Research (exploit walkthrough), Hyunwoo Kim (multi-site patch) Public PoC: https://research.jfrog.com/post/dissecting-and-exploiting-linux-lpe-variant-dirtyclone-cve-2026-43503/ Upstream fix: commit 48f6a5356a33 - https://github.com/torvalds/linux/commit/48f6a5356a33 Reference: https://thehackernews.com/2026/06/new-dirtyclone-linux-kernel-flaw-lets.html

CVE-2026-64564 - SCTPhantom

Disclosed: 2026-08-06 | CVSS: 8.5 (High, CVSS v4.0) | Patch: commit 9b2854f86f0b
Affected subsystem: SCTP Dynamic Address Reconfiguration (ASCONF / RFC 5061)
SCTPhantom is an 18-year-old use-after-free in SCTP's ASCONF path (code that has been present since Linux 2.6.25 in 2007). The bug is an identity mismatch: the kernel validates a DEL-IP delete operation using the packet's source address, while a separate cached pointer relies on the address parameter used to select the actual transport. By crafting an ordered ASCONF sequence - specify an address, delete that same address, then send a wildcard delete - an attacker removes a transport while stale references to it remain in the association's active_path and primary_path pointers. A later socket operation dereferences the freed memory. The published exploit reclaims the freed transport via a packet-socket ring buffer, defeats KASLR, and chains a second use-after-free through attacker-controlled SCTP authentication key data to reach commit_creds and global root, with no shellcode or ROP. The same flaw was demonstrated to escape containers to the host.

SCTP is not used on a ggRock host, but the sctp module autoloads on demand: any unprivileged local user who opens an SCTP socket makes the kernel load the vulnerable code. "We do not route SCTP" does not make this unreachable - blocking the module autoload does. The mitigation script blacklists sctp (and the sibling autoload-LPE protocols dccp, rds, tipc), which closes this CVE and pre-empts future ones in the same family.

The flaw is local, not remote. A full technique write-up was published and root was demonstrated across Debian 13, Ubuntu 24.04, Rocky/RHEL 9, and kernels from 5.14 to a 7.2 release candidate; as of publication no drop-in public exploit code had been released and it was not listed in CISA KEV.
Discovered by: Tencent Zhuque Lab (Corvus AI) Upstream fix: commit 9b2854f86f0b - https://git.kernel.org/linus/9b2854f86f0b56e9027d68e7a3fc909d1a9b566f Reference: https://cybersecuritynews.com/18-year-old-linux-kernel-sctp-vulnerability/

CVE-2026-53266 - ebtables SNAT (bridge)

Disclosed: 2026-06-25 | CVSS: 8.8 (High) | Fixed in: 6.1.176 (Bookworm), 6.12.94 (Trixie)
Affected subsystem: netfilter bridge ebtables SNAT target (ebt_snat)
The ebtables SNAT target's optional ARP sender-hardware-address rewrite writes through skb_store_bits() at an offset relative to skb->data without first ensuring that range is writable. When the packet is a non-linear skb whose fragment is backed by a splice-imported file page, the write lands directly in that page - an out-of-bounds write into kernel memory that can be turned into local privilege escalation.

This is local, not remote. The advisory is explicit that remote ARP traffic alone cannot trigger it: an attacker needs local control of the ebtables configuration plus the ability to arrange the splice-backed shared page. Configuring the ebtables rule requires CAP_NET_ADMIN, which an unprivileged user obtains by creating a user namespace - so this is another member of the namespace-gated family. The unprivileged-user-namespace restriction that harden_nftables.sh already applies (via the privilege-selective knob, not a max cap) closes its reachability path, so no dedicated hardening script is needed for it.

One tracker reported this as newly added to the CISA KEV catalog (remediation due 2026-09-21); that could not be confirmed against the CISA catalog directly and its exploitation-probability score is low, so verify KEV status independently. At CVSS 8.8 it warrants a prompt patch regardless.
Reference: https://cve-security.com/cve/cve-2026-53266


Impact

CVE

Codename

Privilege Required

Race Condition

Root Shell

CVE-2026-46300

Fragnesia

Unprivileged local user

No

Yes

CVE-2026-43284

Dirty Frag

Unprivileged local user

No

Yes

CVE-2026-31431

Copy-Fail

Unprivileged local user

No

Yes

CVE-2026-46333

ssh-keysign-pwn

Unprivileged local user

Soft (exit window)

Yes - via host key / shadow read

CVE-2026-23111

nf_tables Typo

Unprivileged local user + userns

No

Yes - also container escape

CVE-2026-46243

CIFSwitch

Unprivileged local user + userns

No

Yes - via forged upcall + NSS

CVE-2026-43503

DirtyClone

Unprivileged local user + userns

No

Yes - host-wide page cache, CVSS 8.8

CVE-2026-64564

SCTPhantom

Unprivileged local user (+ sctp autoload)

Yes (ASCONF sequence)

Yes - local root + container escape, CVSS 8.5

CVE-2026-53266

ebtables SNAT

Unprivileged local user + userns

No

Yes - OOB write in ebt_snat, CVSS 8.8


Priority 1 - Apply the Upstream Patch (Permanent Fix)

Patched kernels are available for all supported Debian releases. The minimum patched versions for the first seven CVEs are:

Debian Release

Minimum Patched Kernel

Debian 13 Trixie

6.12.85-1

Debian 12 Bookworm

6.1.170-1

Debian 11 Bullseye

5.10.251-3

Update and reboot:

apt update && apt full-upgrade && reboot

Verify your running kernel version after reboot:

uname -r

The patched kernel is available via the standard [security] repository, which is enabled by default on all supported Debian releases. On Debian 13 Trixie, sources configuration has moved from /etc/apt/sources.list to /etc/apt/sources.list.d/debian.sources - the security repository is included in the default debian.sources file.

CVE-2026-64564 (SCTPhantom) landed later and needs a newer kernel than the table above. Debian 13 Trixie fixes it in 6.12.101-1 (2026-08-06); Debian 12 Bookworm and Debian 11 Bullseye fixes were still rolling out at the time of writing, so confirm against the security tracker. The safe rule is to run the latest security kernel for your release rather than a fixed version number. The harden_sctphantom.sh blacklist (Priority 2) closes SCTPhantom on any kernel version in the interim.

CVE-2026-53266 (ebtables SNAT) is fixed in Debian 6.1.176-1 (Bookworm) and 6.12.94-1 (Trixie). Its reachability is already closed by the userns restriction in harden_nftables.sh, so a current security kernel plus the standard hardening covers it with no extra step.

The minimum patched versions above were validated against Copy-Fail (CVE-2026-31431). The DirtyClone fix (commit 48f6a5356a33) and the nf_tables/CIFSwitch fixes landed on different dates. Confirm patch status for those CVEs against the Debian security tracker links in References if you need version-exact assurance.

Priority 2 - Apply Workaround (If Immediate Patching Is Not Possible)

Each CVE has a dedicated hardening script. The simplest approach is the master script, which fetches and runs all eight in the correct order:

wget -O - https://images.ggleap.com/ManagedServices/Scripts/harden_all.sh | bash -
This mitigation disables IPsec (ESP/RXRPC) for CVE-2026-46300, CVE-2026-43284, and CVE-2026-43503; AF_ALG crypto sockets for CVE-2026-31431; unrestricted ptrace attach for CVE-2026-46333; unprivileged user namespaces for CVE-2026-23111, CVE-2026-46243, and CVE-2026-43503; and the sctp/dccp/rds/tipc protocol modules for CVE-2026-64564. The unprivileged-user-namespace restriction additionally closes CVE-2026-53266 (ebtables SNAT), so that CVE needs no separate script. Restricting unprivileged user namespaces (via the selective knob, which leaves root systemd sandboxes working) also breaks rootless containers (Podman, rootless Docker). Review the Caveats section before applying.

The individual scripts are listed below for reference.

harden_fragnesia.sh - CVE-2026-46300

#!/bin/bash
# Fragnesia (CVE-2026-46300) Hardening Script
# Ref: https://cybersecuritynews.com/fragnesia-linux-vulnerability/

if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi

echo "--- Fragnesia (CVE-2026-46300) Hardening ---"

MODULES_IN_USE=false
if lsmod | grep -qE "^(esp4|esp6|rxrpc)"; then
echo "[!] WARNING: ESP/RXRPC modules currently loaded."
ip xfrm state 2>/dev/null | grep -q "proto esp" && \
echo "[!] Active IPsec SA detected - unloading will drop tunnels."
MODULES_IN_USE=true
fi

CONF_FILE="/etc/modprobe.d/disable-fragnesia.conf"
echo "[*] Writing blacklist to $CONF_FILE..."
cat <<EOF > "$CONF_FILE"
# Fragnesia (CVE-2026-46300) mitigation -- $(date -u +%Y-%m-%dT%H:%M:%SZ)
install esp4 /bin/false
install esp6 /bin/false
install rxrpc /bin/false
EOF
echo "[+] Blacklist written."

if [ "$MODULES_IN_USE" = true ]; then
echo "[!] Skipping module unload - active sessions detected."
else
modprobe -r esp4 esp6 rxrpc 2>/dev/null
lsmod | grep -qE "^(esp4|esp6|rxrpc)" && echo "[!] Unload failed - reboot required." || echo "[+] Modules unloaded."
fi

echo "[*] Flushing page cache..."
sync; echo 1 > /proc/sys/vm/drop_caches
echo "[+] Page cache flushed."

echo "[*] Rebuilding initramfs..."
command -v update-initramfs &>/dev/null && update-initramfs -u

echo "[*] Testing AF_RXRPC socket (Family 33)..."
python3 -c "import socket; socket.socket(33, 2, 0)" 2>/dev/null \
&& echo "[!] FAIL: socket still created - reboot required." \
|| echo "[+] VERIFIED: kernel rejects vulnerable socket type."

harden_net_frag.sh - CVE-2026-43284

#!/bin/bash
# Dirty Frag (CVE-2026-43284) Mitigation Script
# Ref: https://cybersecuritynews.com/dirty-frag-linux-vulnerability/

if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi

set -e
CONFIG_FILE="/etc/modprobe.d/dirtyfrag.conf"
VULN_MODULES=("esp4" "esp6" "rxrpc")

echo "--- Dirty Frag (CVE-2026-43284) Mitigation ---"

cat <<EOF > "${CONFIG_FILE}.tmp"
# Dirty Frag (CVE-2026-43284) mitigation -- $(date -u +%Y-%m-%dT%H:%M:%SZ)
install esp4 /bin/false
install esp6 /bin/false
install rxrpc /bin/false
EOF
mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
echo "[+] Blacklist written to $CONFIG_FILE"

for mod in "${VULN_MODULES[@]}"; do
lsmod | grep -q "^$mod " && { modprobe -r "$mod" 2>/dev/null && echo "[+] $mod unloaded." || echo "[!] $mod unload failed."; } || echo "[*] $mod not loaded."
done

echo "[*] Flushing page cache..."
sync; echo 1 > /proc/sys/vm/drop_caches

echo "[*] Rebuilding initramfs..."
command -v update-initramfs &>/dev/null && update-initramfs -u

echo "[*] Testing AF_RXRPC socket (Family 33)..."
SOCK_ERR=$(python3 -c "import socket; socket.socket(33, 2, 0)" 2>&1) || true
if ! python3 -c "import socket; socket.socket(33, 2, 0)" 2>/dev/null; then
echo "[+] VERIFIED: kernel rejects vulnerable socket type."
else
echo "[!] FAIL: socket still created - reboot required."
fi

harden_crypto.sh - CVE-2026-31431

#!/bin/bash
# Copy-Fail (CVE-2026-31431) Hardening Script
# Mitigates AF_ALG/algif_aead privilege escalation via module blacklist

if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi

echo "--- Copy-Fail (CVE-2026-31431) Hardening ---"

CONF_FILE="/etc/modprobe.d/disable-copy-fail.conf"
cat <<EOF > "$CONF_FILE"
# Copy-Fail (CVE-2026-31431) mitigation -- $(date -u +%Y-%m-%dT%H:%M:%SZ)
install algif_aead /bin/false
install af_alg /bin/false
alias net-pf-38 off
EOF
echo "[+] Blacklist written to $CONF_FILE"

echo "[*] Unloading modules (child before parent)..."
modprobe -r algif_aead 2>/dev/null
modprobe -r af_alg 2>/dev/null
lsmod | grep -qE "^(algif_aead|af_alg)" && echo "[!] Unload failed - reboot required." || echo "[+] Modules unloaded."

echo "[*] Rebuilding initramfs..."
command -v update-initramfs &>/dev/null && update-initramfs -u

echo "[*] Testing AF_ALG socket (Family 38)..."
python3 -c "import socket; socket.socket(38, 5, 0)" 2>/dev/null \
&& echo "[!] FAIL: socket still created - reboot required." \
|| echo "[+] VERIFIED: kernel rejects AF_ALG socket."

harden_sshkeysignpwn.sh - CVE-2026-46333

#!/bin/bash
# ssh-keysign-pwn (CVE-2026-46333) Hardening Script
# Ref: https://almalinux.org/blog/2026-05-15-ssh-keysign-pwn-cve-2026-46333/

if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi

echo "--- ssh-keysign-pwn (CVE-2026-46333) Hardening ---"

SYSCTL_CONF="/etc/sysctl.d/99-ssh-keysign-pwn.conf"
PTRACE_SCOPE_FILE="/proc/sys/kernel/yama/ptrace_scope"

[ ! -f "$PTRACE_SCOPE_FILE" ] && echo "[!] Yama LSM not loaded." && exit 1

PTRACE_PIDS=$(awk '/TracerPid:/ && $2 != "0" {c++} END {print c+0}' /proc/[0-9]*/status 2>/dev/null)
[ "$PTRACE_PIDS" -gt 0 ] && echo "[!] WARNING: $PTRACE_PIDS process(es) being ptraced."

echo "[*] Setting kernel.yama.ptrace_scope=3..."
sysctl -w kernel.yama.ptrace_scope=3 || { echo "[!] sysctl write failed."; exit 1; }

cat > "$SYSCTL_CONF" <<EOF
# ssh-keysign-pwn (CVE-2026-46333) mitigation -- $(date -u +%Y-%m-%dT%H:%M:%SZ)
# Scope 3: no process may ptrace any other. Use 2 if admins need debuggers.
kernel.yama.ptrace_scope = 3
EOF
echo "[+] Persistent config written to $SYSCTL_CONF"

ACTIVE_SCOPE=$(cat "$PTRACE_SCOPE_FILE")
[ "$ACTIVE_SCOPE" -ge 2 ] \
&& echo "[+] VERIFIED: ptrace_scope=$ACTIVE_SCOPE - exploit paths blocked." \
|| { echo "[!] FAIL: ptrace_scope=$ACTIVE_SCOPE."; exit 1; }

harden_nftables.sh - CVE-2026-23111

#!/bin/bash
# nf_tables Typo (CVE-2026-23111) Hardening Script
# Sole owner of the unprivileged-userns gate; harden_cifswitch.sh and
# harden_dirtyclone.sh defer here. Also blocks CIFSwitch (CVE-2026-46243), the
# DirtyClone (CVE-2026-43503) CAP_NET_ADMIN pivot, and the ebtables SNAT flaw
# (CVE-2026-53266).
#
# Uses the privilege-SELECTIVE knob, NOT user.max_user_namespaces=0: the global hard
# cap also denies namespaces to ROOT systemd sandboxes (Prometheus/Grafana,
# DynamicUser=), surfacing as a misleading ENOSPC / status=217/USER. The selective
# knob closes the unprivileged path while leaving root services untouched, and this
# script self-heals any host a previous max=0 version pinned.

if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi

echo "--- nf_tables Typo (CVE-2026-23111) Hardening ---"

SYSCTL_CONF="/etc/sysctl.d/99-nftables-userns.conf"
CLONE_KNOB="/proc/sys/kernel/unprivileged_userns_clone"
AA_KNOB="/proc/sys/kernel/apparmor_restrict_unprivileged_userns"

# Pick the available privilege-selective knob
if [ -f "$CLONE_KNOB" ]; then GATE="clone"
elif [ -f "$AA_KNOB" ]; then GATE="apparmor"
else GATE="none"; fi

command -v podman &>/dev/null || systemctl is-active --quiet docker 2>/dev/null && \
echo "[!] WARNING: container runtime detected - restricting unprivileged userns breaks rootless containers."

# Persistent config -- NEVER user.max_user_namespaces=0
{
echo "# nf_tables Typo (CVE-2026-23111) mitigation -- $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "# Blocks UNPRIVILEGED userns only; root systemd sandboxes unaffected."
echo "# DO NOT add user.max_user_namespaces=0 - it also kills root sandboxes (ENOSPC / 217)."
case "$GATE" in
clone) echo "kernel.unprivileged_userns_clone = 0" ;;
apparmor) echo "kernel.apparmor_restrict_unprivileged_userns = 1" ;;
none) echo "# no selective knob on this kernel - deploy the patched kernel" ;;
esac
} > "$SYSCTL_CONF"

sysctl --system >/dev/null 2>&1 || true
# self-heal a host an older version pinned at 0
[ "$(cat /proc/sys/user/max_user_namespaces 2>/dev/null || echo 0)" = "0" ] && [ "$GATE" != "none" ] && \
sysctl -w user.max_user_namespaces=15000 >/dev/null 2>&1

case "$GATE" in
clone) sysctl -w kernel.unprivileged_userns_clone=0 >/dev/null 2>&1 ;;
apparmor) sysctl -w kernel.apparmor_restrict_unprivileged_userns=1 >/dev/null 2>&1 ;;
none) echo "[!] No selective knob - deploy the patched kernel (refusing the max=0 sledgehammer)." ;;
esac

if ! runuser -u nobody -- unshare -U true 2>/dev/null; then
echo "[+] VERIFIED: unprivileged user namespace creation blocked."
else
echo "[!] unprivileged userns still creatable ($GATE) - deploy the patched kernel."
fi

harden_cifswitch.sh - CVE-2026-46243

#!/bin/bash
# CIFSwitch (CVE-2026-46243) Mitigation Script
# Advisory: https://heyitsas.im/posts/cifswitch/

if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi

set -e
REQKEY_RULE="/etc/request-key.d/cifs.spnego.conf"
CIFS_BLACKLIST="/etc/modprobe.d/cifswitch.conf"
KEYCTL=$(command -v keyctl 2>/dev/null || echo "/usr/bin/keyctl")

echo "--- CIFSwitch (CVE-2026-46243) Mitigation ---"

command -v cifs.upcall &>/dev/null || echo "[+] cifs.upcall not found - cifs-utils not installed (primary mitigation)."

[ -f "$REQKEY_RULE" ] && [ ! -f "${REQKEY_RULE}.cifswitch.bak" ] && cp "$REQKEY_RULE" "${REQKEY_RULE}.cifswitch.bak"
cat > "$REQKEY_RULE" <<EOF
# CIFSwitch (CVE-2026-46243) mitigation -- $(date -u +%Y-%m-%dT%H:%M:%SZ)
create cifs.spnego * * $KEYCTL negate %k 30 %S
EOF
echo "[+] Safe negate rule written to $REQKEY_RULE"

cat > "$CIFS_BLACKLIST" <<EOF
# CIFSwitch (CVE-2026-46243) mitigation -- $(date -u +%Y-%m-%dT%H:%M:%SZ)
install cifs /bin/false
blacklist cifs
EOF
echo "[+] Module blacklist written to $CIFS_BLACKLIST"

if lsmod | grep -q '^cifs '; then
grep -q ' cifs ' /proc/mounts 2>/dev/null \
&& echo "[!] Active CIFS mounts - skipping unload." \
|| { modprobe -r cifs 2>/dev/null && echo "[+] cifs unloaded." || echo "[!] cifs unload failed - reboot to complete."; }
fi

command -v update-initramfs &>/dev/null && update-initramfs -u

harden_dirtyclone.sh - CVE-2026-43503

#!/bin/bash
# DirtyClone (CVE-2026-43503) Hardening Script
# esp module blacklist + page-cache flush. The unprivileged-userns gate is owned by
# harden_nftables.sh (selective knob, not max=0); this script VERIFIES/DEFERS to it and
# writes no userns sysctl.
# Patch: commit 48f6a5356a33, merged 2026-05-21, shipped v7.1-rc5 2026-05-24

if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi

echo "--- DirtyClone (CVE-2026-43503) Hardening ---"

CONF_FILE="/etc/modprobe.d/dirtyclone.conf"

cat > "$CONF_FILE" <<EOF
# DirtyClone (CVE-2026-43503) mitigation -- $(date -u +%Y-%m-%dT%H:%M:%SZ)
install esp4 /bin/false
install esp6 /bin/false
install rxrpc /bin/false
EOF
echo "[+] Blacklist written to $CONF_FILE"

MODULES_IN_USE=false
lsmod | grep -qE "^(esp4|esp6|rxrpc)" && MODULES_IN_USE=true
if [ "$MODULES_IN_USE" = true ]; then
ip xfrm state 2>/dev/null | grep -q "proto esp" && echo "[!] Active IPsec SA detected."
echo "[!] Skipping module unload - active sessions. Schedule a reboot."
else
modprobe -r esp4 esp6 rxrpc 2>/dev/null
lsmod | grep -qE "^(esp4|esp6|rxrpc)" && echo "[!] Unload failed - reboot required." || echo "[+] Modules unloaded."
fi

# userns gate: REPORT ONLY - owned by harden_nftables.sh (set no userns sysctl here)
if runuser -u nobody -- unshare -U true 2>/dev/null; then
echo "[!] Unprivileged userns NOT blocked - run harden_nftables.sh to close the CAP_NET_ADMIN pivot."
else
echo "[+] Unprivileged userns blocked - CAP_NET_ADMIN gate closed by harden_nftables.sh."
fi

echo "[*] Flushing page cache (evicts any poisoned cached binaries)..."
sync; echo 1 > /proc/sys/vm/drop_caches
echo "[+] Page cache flushed."

command -v update-initramfs &>/dev/null && update-initramfs -u

# Verification: AF_RXRPC socket (protocol 2) must be rejected
if ! python3 -c "import socket; socket.socket(33, 2, 2)" 2>/dev/null; then
echo "[+] VERIFIED: kernel rejects AF_RXRPC socket (IPsec module path blocked)."
else
echo "[!] FAIL: AF_RXRPC socket still creatable - reboot may be required."
fi

harden_sctphantom.sh - CVE-2026-64564

#!/bin/bash
# Exotic transport-protocol hardening
# Primary: SCTPhantom (CVE-2026-64564) - SCTP ASCONF use-after-free, local -> root.
# Also blacklists the sibling autoload-LPE protocols dccp, rds, tipc.
# ggRock uses TCP/UDP only, so none of these are needed.
# Ref: https://cybersecuritynews.com/18-year-old-linux-kernel-sctp-vulnerability/

if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi

echo "--- Exotic protocol hardening (SCTPhantom CVE-2026-64564 + dccp/rds/tipc) ---"

PROTO_MODULES="sctp dccp rds tipc"
CONF_FILE="/etc/modprobe.d/blacklist-exotic-protocols.conf"

IN_USE=false
for mod in $PROTO_MODULES; do
lsmod | grep -q "^$mod " && { echo "[!] WARNING: $mod loaded (unexpected on this host)."; IN_USE=true; }
done

# 'install <mod> /bin/false' blocks both a direct modprobe and on-demand autoload
# (request_module resolves the net-pf alias to the module, then runs its install rule).
cat > "${CONF_FILE}.tmp" <<EOF
# Exotic transport-protocol mitigation -- $(date -u +%Y-%m-%dT%H:%M:%SZ)
# SCTPhantom (CVE-2026-64564) + the dccp/rds/tipc autoload-LPE family.
install sctp /bin/false
install dccp /bin/false
install rds /bin/false
install tipc /bin/false
blacklist sctp
blacklist dccp
blacklist rds
blacklist tipc
EOF
mv "${CONF_FILE}.tmp" "$CONF_FILE"
echo "[+] Blacklist written to $CONF_FILE"

if [ "$IN_USE" = true ]; then
for mod in $PROTO_MODULES; do
lsmod | grep -q "^$mod " && { modprobe -r "$mod" 2>/dev/null && echo "[+] $mod unloaded." || echo "[!] $mod in use - reboot to complete."; }
done
else
echo "[*] None loaded - nothing to unload."
fi

command -v update-initramfs &>/dev/null && update-initramfs -u

acid() { python3 -c "import socket; socket.socket($2,$3,$4)" 2>/dev/null && echo "[!] FAIL: $1 socket still created - reboot required." || echo "[+] OK: $1 socket rejected."; }
acid SCTP 2 1 132
acid DCCP 2 6 33
acid TIPC 30 1 0
acid RDS 21 5 0

Priority 3 - Incident Response (If Exploitation Is Suspected)

For page-cache CVEs: do not invoke /usr/bin/su until the page cache has been cleared - doing so may spawn a root shell for any user. For ssh-keysign-pwn: rotate SSH host keys and audit /etc/shadow immediately. For CIFSwitch: audit /etc/sudoers.d for unexpected entries. For DirtyClone: the modification is host-wide and affects all processes - reboot immediately after flushing the cache. For SCTPhantom: audit for unexpected root processes and check whether the sctp module was loaded (lsmod | grep sctp).

For page-cache CVEs (CVE-2026-46300, CVE-2026-43284, CVE-2026-31431, CVE-2026-43503):

  1. Flush the page cache immediately:
sync && echo 1 > /proc/sys/vm/drop_caches
  1. Reboot the system as soon as operationally possible.
  2. Check for unexpected root-owned processes or modified sudoers/passwd entries.
  3. Review auth logs for privilege escalation events:
grep -E "su|sudo|root" /var/log/auth.log
journalctl _COMM=su --since "24 hours ago"

For ssh-keysign-pwn (CVE-2026-46333):

  1. Rotate all SSH host keys on affected systems:
rm /etc/ssh/ssh_host_*_key /etc/ssh/ssh_host_*_key.pub
ssh-keygen -A
systemctl restart sshd
  1. Audit /etc/shadow for unexpected password hash changes.
  2. Review auth logs for unexpected authentication events.

For nf_tables Typo, CIFSwitch, and SCTPhantom (CVE-2026-23111, CVE-2026-46243, CVE-2026-64564):

  1. Audit /etc/sudoers.d/ and /etc/passwd for unexpected entries.
  2. Check for unexpected nsswitch.conf or libnss_*.so files in user-writable paths.
  3. Review logs for unprivileged unshare/nft activity, and check whether sctp (or dccp/rds/tipc) was ever loaded.
  4. Apply the workaround or patch before bringing the system back into service.


Caveats

Fragnesia / Dirty Frag / DirtyClone (CVE-2026-46300, CVE-2026-43284, CVE-2026-43503)

Blacklisting esp4 and esp6 will break kernel-mode IPsec. This affects:

  • Site-to-site VPN tunnels using strongSwan or Libreswan in kernel ESP mode
  • Any IKEv1/IKEv2 configuration that offloads ESP processing to the kernel
Userspace VPN implementations (WireGuard, OpenVPN, Tailscale) are not affected.

If you are running a VPN gateway or any host with active IPsec SAs, review your network topology carefully and coordinate a maintenance window before applying the workaround. The hardening scripts will detect active IPsec SAs and skip the module unload step, but the blacklist will still be written - a subsequent reboot will drop those tunnels.

Copy-Fail (CVE-2026-31431)

Blacklisting af_alg removes AF_ALG socket access. This affects:

  • Some hardware crypto offload paths
  • cryptsetup benchmark and similar kernel crypto benchmarking tools
  • Certain OpenSSL engine configurations that use AF_ALG explicitly
Standard software-path TLS, LUKS/dm-crypt disk encryption, and most application crypto are unaffected.

ssh-keysign-pwn (CVE-2026-46333)

Setting ptrace_scope=3 disables all ptrace attach on the system. This affects:

  • gdb attaching to a running process
  • strace -p <pid> and similar diagnostic tools
  • Any tooling that uses PTRACE_ATTACH or pidfd_getfd on a live process
Use ptrace_scope=2 instead if administrator-level debugger access is needed. Both values block all known public PoCs.

Note that ptrace_scope is a runtime change - it takes effect immediately without a reboot and does not interrupt any ptrace sessions already in progress.

nf_tables Typo / CIFSwitch / DirtyClone / ebtables SNAT (CVE-2026-23111, CVE-2026-46243, CVE-2026-43503, CVE-2026-53266)

harden_nftables.sh restricts unprivileged user namespace creation via the privilege-selective knob (kernel.unprivileged_userns_clone=0, or the AppArmor apparmor_restrict_unprivileged_userns knob) - deliberately NOT user.max_user_namespaces=0, a global cap that also strangles root systemd sandboxes (Prometheus/Grafana) and surfaces as a misleading ENOSPC / status=217/USER. Restricting unprivileged user namespaces affects:

  • Rootless containers (Podman, rootless Docker, unprivileged LXC)
  • Sandboxing tools that rely on user namespaces (Flatpak, Bubblewrap, some browser sandboxes)

The firewall stack itself is unaffected - nf_tables and bridge ebtables continue to function normally - and root systemd sandboxes keep working, because the gate is the selective knob rather than the global cap. Only unprivileged namespace creation is blocked. The single userns gate covers four CVEs, including CVE-2026-53266 (its ebtables SNAT path is reached the same way - CAP_NET_ADMIN via a user namespace). If an earlier version had set user.max_user_namespaces=0, harden_nftables.sh detects that, un-caps it, and restarts any monitoring units the cap had wedged.
CIFSwitch additionally overrides the cifs.spnego request-key rule with a safe negate and blacklists the cifs module. On diskless ggRock boot hosts these are typically no-ops since cifs-utils is usually not installed.

SCTPhantom (CVE-2026-64564)

Blacklisting sctp (and the sibling dccp, rds, tipc modules) removes those transport protocols entirely. This affects:

  • Any application that opens SCTP, DCCP, RDS, or TIPC sockets (telecom/SIGTRAN signalling, some clustering and HPC interconnect software)
ggRock uses only TCP and UDP, so none of these protocols are in use and the blacklist has no functional impact. Blocking their autoload also pre-empts future CVEs in the same module family.

The sctp module autoloads on demand: without the blacklist, a host that does not use SCTP is still exposed, because any local process opening an SCTP socket pulls the vulnerable code into the kernel. The blacklist closes that autoload path.


Verification

After applying the workaround, confirm the following on each system:

# No vulnerable modules should be loaded
lsmod | grep -E "esp4|esp6|rxrpc|algif_aead|af_alg|cifs|sctp|dccp|rds|tipc"

# Socket creation should be rejected (Fragnesia/DirtyFrag)
python3 -c "import socket; socket.socket(33, 2, 0)"

# Socket creation should be rejected (Copy-Fail)
python3 -c "import socket; socket.socket(38, 5, 0)"

# Socket creation should be rejected (DirtyClone - note protocol 2, not 0)
python3 -c "import socket; socket.socket(33, 2, 2)"

# Socket creation should be rejected (SCTPhantom)
python3 -c "import socket; socket.socket(2, 1, 132)"

# ptrace_scope should be 2 or 3
cat /proc/sys/kernel/yama/ptrace_scope

# Unprivileged user namespaces should be disabled (returns non-zero)
runuser -u nobody -- unshare -U true; echo "exit: $?"

# cifs.spnego rule should reference keyctl negate, not cifs.upcall
cat /etc/request-key.d/cifs.spnego.conf

All Python socket commands above should raise OSError: [Errno 97] Address family not supported by protocol - once the relevant module is blacklisted, the address family is unavailable. A clean exit (return code 0) means the mitigation has not taken effect and a reboot is required. ptrace_scope should be >= 2. The unshare -U test should return a non-zero exit. The cifs.spnego rule should reference keyctl negate.


Frequently Asked Questions

Does this affect containers?

Containers share the host kernel. If the host kernel is vulnerable and a container runs with sufficient privileges to open the relevant socket families, it is in scope. Rootless containers and those with restricted seccomp/AppArmor profiles that block socket(AF_RXRPC, ...), socket(AF_ALG, ...), and SCTP sockets have a reduced attack surface. CVE-2026-46333, CVE-2026-23111, CVE-2026-46243, CVE-2026-43503, CVE-2026-64564, and CVE-2026-53266 all carry container-escape potential. DirtyClone is particularly notable: page cache modifications made inside a namespace affect every process on the host machine. SCTPhantom was demonstrated escaping default-seccomp containers because it relies on per-socket SCTP options rather than privileged capabilities.

Does this affect read-only root filesystems?

For the page-cache CVEs (including DirtyClone), the exploit targets the page cache, not the filesystem. A read-only root filesystem does not prevent the attack. For CVE-2026-46333, a read-only filesystem is irrelevant - the exploit reads in-memory file descriptors, not on-disk files. CVE-2026-46243 writes to sudoers.d via NSS inside the root helper, so a writable /etc is part of its chain. SCTPhantom manipulates in-kernel SCTP structures and is unaffected by filesystem mount options.

Do I need to apply all eight scripts?

If you are patching promptly, one current kernel covers all eight. If you are applying workarounds, run all eight (the master harden_all.sh does this in the correct order). Note that harden_nftables.sh closes the user-namespace gate relied upon by CIFSwitch, DirtyClone, and the ebtables SNAT flaw (CVE-2026-53266, which needs no script of its own), and harden_fragnesia.sh blacklists the esp4/esp6 modules relied upon by DirtyClone - so ordering matters and the master script handles it correctly. harden_sctphantom.sh is independent and runs last.

Can the workaround be reversed after patching?

Yes. After confirming you are running a patched kernel (see the version guidance in Priority 1), remove the config files and revert the sysctls:

rm /etc/modprobe.d/disable-fragnesia.conf
rm /etc/modprobe.d/dirtyfrag.conf
rm /etc/modprobe.d/disable-copy-fail.conf
rm /etc/modprobe.d/cifswitch.conf
rm /etc/modprobe.d/dirtyclone.conf
rm /etc/modprobe.d/blacklist-exotic-protocols.conf
rm /etc/sysctl.d/99-ssh-keysign-pwn.conf
rm /etc/sysctl.d/99-nftables-userns.conf
# Restore the original cifs.spnego rule if Kerberos CIFS is needed:
[ -f /etc/request-key.d/cifs.spnego.conf.cifswitch.bak ] && \
mv /etc/request-key.d/cifs.spnego.conf.cifswitch.bak /etc/request-key.d/cifs.spnego.conf
update-initramfs -u
sysctl -w kernel.yama.ptrace_scope=1
sysctl --system # v3 nftables gate uses the selective knob; removing 99-nftables-userns.conf above reverts it, and no max cap was set
reboot

Note: the sctp/dccp/rds/tipc blacklist has no functional impact on a TCP/UDP-only host, so there is usually no reason to reverse it even after patching - leaving it in place keeps the whole protocol family pre-mitigated against future CVEs.


References

Updated on: 18/09/2026

Was this article helpful?

Share your feedback

Cancel

Thank you!