TechEarl

CVE-2026-23111: the nf_tables 'off by !' Linux kernel LPE (detect, patch, lab)

CVE-2026-23111 is a one-character nf_tables use-after-free that escalates any unprivileged Linux user to root through user namespaces, and a public exploit is now out. Here is how I detect it, the user-namespace mitigation that matters most, the kernel patch, and a safe VM lab to reproduce it.

Ishan Karunaratne⏱️ 22 min readUpdated
Share thisCopied
CVE-2026-23111 is a one-character nf_tables use-after-free Linux kernel LPE. Detection, the user-namespace mitigation, the kernel patch, and a safe multi-distro VM lab.

TL;DR. CVE-2026-23111 is a use-after-free in the Linux kernel's nf_tables (netfilter) subsystem. The fix removed a single character, a stray ! that inverted an element-activity check, so aborting a transaction processed live elements instead of inactive ones and never restored a reference count. An unprivileged local user can drive that into a clean root, and into a container escape, through unprivileged user namespaces. Exodus Intelligence found it in early 2025, it was patched upstream on 2026-02-05, and as of 2026-06-08 there is a fully working public exploit (a second public PoC from FuzzingLabs landed back in April). NVD scores it CVSS 7.8 (HIGH). If your kernel is below your distro's fixed build, you are exposed. Two things to do today: patch the kernel, and restrict unprivileged user namespaces (the single mitigation that defangs this whole class). This article covers detection, the mitigation, the patch, and a safe VM lab to reproduce it.

This is a local privilege escalation, not a remote bug. That sounds reassuring until you remember how many "local" shells exist: every container, every CI runner that executes third-party code, every account on a shared host. On any of those, this turns one unprivileged foothold into host root.

Why this one is worth your afternoon

Three things make CVE-2026-23111 stand out from the steady drip of kernel CVEs:

  1. It is a one-character bug in a default-on subsystem. nf_tables is the modern packet-filter backend behind nftables and iptables-nft, compiled into essentially every mainstream distro kernel. The bug is not in some exotic driver, it is in the firewall layer everyone ships.
  2. It is weaponized. Exodus Intelligence published a detailed writeup and a working exploit on 2026-06-08, reporting over 99% reliability on an idle system and around 80% under heap pressure. FuzzingLabs independently published a reproduction with a working root exploit on 2026-04-16. The gap between "PoC exists" and "script kiddies have it" is closed.
  3. The path to root runs through user namespaces. The exploit needs CONFIG_USER_NS and CONFIG_NF_TABLES, both standard, and an unprivileged user who can create a user namespace. That last precondition is the one you can take away without patching, which is the whole point of the mitigation section below.

The affected range is wide. NVD lists vulnerable kernels from the 4.19 series all the way through 6.18.x. If you are on anything older than your distro's fixed build, assume you are in scope.

Am I vulnerable?

Four quick checks. None needs root, and together they tell you whether the bug is present and whether the precondition (unprivileged user namespaces) is open.

bash
# 1. Running kernel version. Compare against your distro's fixed build (tables below).
uname -r

# 2. Is nf_tables present? (It is the attack surface.)
modinfo nf_tables 2>/dev/null | head -2 || echo "nf_tables not found as a module (may be built in)"

# 3. Can an unprivileged user create a user namespace? This is the precondition.
unshare -Ur id 2>&1 | head -1
#   uid=0(root) gid=0(root) ...     -> userns is open, precondition met
#   unshare: ... Operation not permitted  -> restricted, precondition closed

# 4. On Ubuntu/Debian, the userns knobs directly:
cat /proc/sys/user/max_user_namespaces 2>/dev/null
sysctl kernel.unprivileged_userns_clone 2>/dev/null
sysctl kernel.apparmor_restrict_unprivileged_userns 2>/dev/null   # Ubuntu 23.10+

Two things to read carefully:

  • Presence of nf_tables is the attack surface, not proof of exploitability. A patched kernel still ships nf_tables; it simply no longer carries the inverted check. The deciding signal is your kernel version against the vendor advisory, not whether the module exists.
  • If check 3 already fails, you have a strong mitigation in place. On stock Ubuntu 24.04 the AppArmor restriction on unprivileged user namespaces is enabled by default (kernel.apparmor_restrict_unprivileged_userns=1), so an unconfined unprivileged process cannot create the namespace the exploit needs. That does not fix the kernel bug, but it removes the realistic path to it for this exploit. Do not let it lull you: a confined profile with the userns permission, a CAP_SYS_ADMIN process, or a box where someone has flipped that sysctl off, is still exposed until patched.

Here is what the checks look like on an unpatched Ubuntu 24.04 host with userns open:

code
techearl@lab:~$ uname -r
6.8.0-79-generic
techearl@lab:~$ modinfo nf_tables 2>/dev/null | head -2
filename:       /lib/modules/6.8.0-79-generic/kernel/net/netfilter/nf_tables.ko.zst
license:        GPL
techearl@lab:~$ unshare -Ur id 2>&1 | head -1
uid=0(root) gid=0(root) groups=0(root)

6.8.0-79-generic is well below Ubuntu's fixed 6.8.0-107.107, nf_tables is present, and unshare -Ur succeeds, so this box has the bug and an open path to it.

Those four checks are bundled into a read-only exposure checker, check-exposure.sh, in the companion lab toolkit. It compares your kernel against the fixed build for your distro, tests the user-namespace precondition as an unprivileged user (not as root, which would give a false reading), and prints an EXPOSED / MITIGATED / fixed verdict.

NVD scores this CVSS 7.8 (HIGH), vector AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H, CWE-416 (Use After Free). It requires local access, which is why it is High rather than Critical, but "local" includes every container and CI job on the box.

Mitigate now: restrict unprivileged user namespaces

This is the part to do first, before you even schedule the reboot, because it neutralizes the realistic exploit path for this bug and for a large share of kernel LPEs generally (Google has reported that a large fraction of the in-the-wild kernel exploits they see depend on unprivileged user namespaces). There are two approaches.

Ubuntu 23.10 and newer (AppArmor-based, the right tool there). Ubuntu added fine-grained restriction so that only AppArmor profiles carrying a userns rule (or processes with CAP_SYS_ADMIN) can create unprivileged user namespaces. On 24.04 it is on by default. Confirm and persist it:

bash
# Confirm it is on (1 = restricted)
sysctl kernel.apparmor_restrict_unprivileged_userns

# Turn it on and persist
echo "kernel.apparmor_restrict_unprivileged_userns=1" | sudo tee /etc/sysctl.d/99-restrict-userns.conf
sudo sysctl --system

This is preferable to a blanket disable because legitimate users of user namespaces (the Chrome and Firefox sandboxes, Flatpak, rootless Podman, bwrap) keep working through their AppArmor profiles, while a random unprivileged process cannot open a namespace to reach the bug.

Everywhere else (blanket disable). On distros without the AppArmor mechanism, take the namespace away entirely. On Debian and Ubuntu set both knobs, because setting only one can leave a residual path on some kernel versions:

bash
cat <<'EOF' | sudo tee /etc/sysctl.d/99-restrict-userns.conf
kernel.unprivileged_userns_clone=0
user.max_user_namespaces=0
EOF
sudo sysctl --system

The blanket disable is heavier-handed: it breaks anything that relies on unprivileged user namespaces, which on a desktop is a real list (browser sandboxes, Flatpak, rootless containers). On a server that runs none of those it is usually safe and is a strong, immediate mitigation. Test in staging if you are unsure what depends on it.

Neither approach replaces the patch. They buy you a safe window and they harden you against the next bug of this shape.

Patch the kernel

Fixed versions at a glance: Ubuntu 24.04 is fixed in 6.8.0-107.107 (generic) or 6.8.0-1051.54 (linux-aws); Debian 12 (bookworm) in 6.1.174-1 and Debian 13 (trixie) in 6.12.90-2; RHEL/AlmaLinux 10 via the RHSA for your stream (the el10_1 124.x series is vulnerable, the fix ships in the 10.2 / 211.x stream). Anything below your distro's fixed build is exposed.

The fix is a normal kernel security update: upgrade the kernel package and reboot. The upstream fix simply removes the inverted check. (nf_tables is also the backend behind iptables-nft, so an iptables-based firewall is in scope too; the bug is in the shared nf_tables core, not the front-end tool.)

Ubuntu

Fixed in the generic kernel at 6.8.0-107.107 for 24.04 LTS (noble), with the same fix across the cloud and flavor kernels (linux-aws 6.8.0-1051.54, linux-azure 6.8.0-1052.58, linux-gcp 6.8.0-1053.56, linux-gke 6.8.0-1049.54, and others). Ubuntu ships many kernel flavors per release and they do not all update on the same day, so check your exact flavor at ubuntu.com/security/CVE-2026-23111 against your uname -r.

bash
sudo apt update
# Match the metapackage to your flavor: -generic, -aws, -azure, -gcp, etc.
sudo apt install --only-upgrade linux-image-generic
sudo reboot

Debian, RHEL family, others

The same shape applies, with your distro's package manager:

DistroCommandNotes
Debiansudo apt update && sudo apt install --only-upgrade linux-image-$(dpkg --print-architecture) && sudo rebootCheck security-tracker.debian.org/tracker/CVE-2026-23111 for the fixed source version per release.
RHEL / Rocky / AlmaLinuxsudo dnf update kernel && sudo rebootSee access.redhat.com/security/cve/cve-2026-23111 for the RHSA and exact NVR. Rocky and Alma track the matching RHSA within a day or two.
Amazon Linux 2 / 2023sudo dnf update kernel && sudo rebootLivepatch may be available for no-reboot mitigation; check the ALAS entry.
SUSE / openSUSEsudo zypper refresh && sudo zypper patch && sudo reboot
Archsudo pacman -Syu linux && sudo rebootCheck security.archlinux.org for the linux-lts/-hardened/-zen variant version.
Fedorasudo dnf upgrade kernel && sudo rebootRolling; current branches are past the fix.

Always check your exact running kernel flavor against your vendor's tracker before declaring a host patched. "We ran dnf update" is not the same as "this kernel is at or past the fixed build for this minor release."

Container hosts and managed Kubernetes

The kernel lives on the host, not in the container, so patch the host kernel and reboot the node, then drain and reschedule. Containers inherit the fix automatically because they share the host kernel. On EKS, GKE, and AKS, roll your node pools onto the latest patched node image (kubectl get nodes -o wide shows each node's KERNEL-VERSION to compare). A rolling node upgrade is enough; nothing inside the containers needs rebuilding.

I ran this in a throwaway VM, three ways

You cannot safely demonstrate a kernel LPE "in a Docker container," and this is worth saying plainly: a container shares the host kernel, so running a kernel exploit inside one exploits your real host, not a sandbox. This CVE's payload is itself a user-namespace container escape, which makes the point for me. The correct lab is a disposable virtual machine with its own kernel, where reaching the bug inside the throwaway VM is harmless and you can delete it afterward. I used short-lived AWS EC2 instances; Multipass, Vagrant with libvirt/VirtualBox, or any local hypervisor work the same way. Never use a container for this.

I stood up three of them, each booted onto a genuinely pre-fix kernel, and ran the same checks. The headline difference between distros is not the bug (they are all vulnerable), it is the default mitigation posture:

Distro (VM)Vulnerable kernel I bootedUnprivileged userns defaultDetector verdict
Ubuntu 24.046.8.0-1028-awsrestricted (AppArmor, on by default)MITIGATED, not fixed
Debian 126.1.0-48-cloud (6.1.172-1)openEXPOSED
AlmaLinux 10.16.12.0-124.38.1.el10_1open (SELinux unconfined)EXPOSED

Ubuntu 24.04 is the outlier in a good way: its default AppArmor restriction means an unprivileged process cannot even open the user namespace the exploit needs, so the detector lands on MITIGATED. Debian and AlmaLinux ship no equivalent restriction, so they are EXPOSED out of the box.

Exposure checker on Ubuntu 24.04 running the vulnerable 6.8.0-1028-aws kernel: it flags the kernel as below the fixed linux-aws build 6.8.0-1051, finds nf_tables present, reports the unprivileged user-namespace precondition closed by the AppArmor restriction, and returns the verdict MITIGATED, NOT FIXED.
Ubuntu 24.04 on a vulnerable 6.8.0-1028-aws kernel: vulnerable, but the default AppArmor userns restriction closes the path, so the verdict is MITIGATED, not fixed.
Exposure checker on Debian 12 running kernel 6.1.0-48-cloud (6.1.172-1): it flags the kernel as below the fixed 6.1.174-1, notes Debian has no AppArmor userns restriction, finds the precondition open, and returns the verdict EXPOSED.
Debian 12 on a vulnerable 6.1.172-1 kernel: no AppArmor userns restriction, so the precondition is open and the box is EXPOSED out of the box.
Exposure checker on AlmaLinux 10.1 running kernel 6.12.0-124.38.1.el10_1, FuzzingLabs' exact target: it flags the 124.x el10_1 series as vulnerable, finds the unprivileged user-namespace precondition open, and returns the verdict EXPOSED.
AlmaLinux 10.1 on 6.12.0-124.38.1.el10_1, FuzzingLabs' exact target kernel: EXPOSED out of the box.

Pinning a vulnerable kernel (the part with sharp edges)

Getting a genuinely pre-fix kernel that still boots took a different trick on each distro, and one of them cost me a bricked VM before I got it right:

  • Ubuntu on a cloud VM: use an old linux-aws, not linux-generic. My first attempt booted linux-image-6.8.0-79-generic and lost SSH completely, because the generic kernel does not carry the ENA network driver that AWS Nitro instances need. The fix is to pin an old linux-aws build instead (I used 6.8.0-1028-aws, below the fixed 6.8.0-1051); the cloud kernel keeps ENA, so networking survives the reboot. On local virt (Multipass/VirtualBox) the generic kernel is fine; this is a cloud-NIC gotcha.
  • Debian: the main repo only carries the current (patched) kernel. The AMI shipped 6.1.174-1, which is exactly the fixed version. To get a vulnerable one I added a pre-fix snapshot mirror (snapshot.debian.org/archive/debian/20251201T000000Z) and installed linux-image-6.1.0-48-cloud-amd64 (6.1.172-1). The cloud kernel keeps ENA, so it boots on EC2.
  • AlmaLinux: the vulnerable el10_1 kernels live in the 10.1 vault. The current AMI is 10.2 (patched, 6.12.0-211.x). I pointed dnf at vault.almalinux.org/10.1/, installed kernel-6.12.0-124.38.1.el10_1 (FuzzingLabs' target), and set it as the default with grubby --set-default. RHEL-family kernels carry ENA built in, so no driver drama.

The exact archived versions rotate as mirrors age; the lab toolkit documents the current picks in its README, alongside the exposure checker and the unprivileged-user setup script.

Create a genuinely unprivileged user (this is what makes it a real test)

Here is the catch that invalidates most "labs": multipass shell, vagrant ssh, and every cloud image drop you in as the default user, which has passwordless sudo. That user is already root for all practical purposes, so running anything as them proves nothing about escalation. A privilege-escalation demo has to start from a user that genuinely cannot become root:

bash
# As the default (sudo-capable) VM user, once. (useradd works on every distro;
# Debian's adduser takes different flags than RHEL's, so prefer useradd.)
sudo useradd -m -s /bin/bash labuser

# Drop to the unprivileged user and PROVE it is unprivileged.
sudo -u labuser -i
id                  # uid=1001(labuser) gid=1001(labuser) groups=1001(labuser)
sudo -n true        # "labuser is not in the sudoers file" -> no path to root except the bug

Everything from here runs as labuser. The screenshot below is from the Ubuntu box: labuser is genuinely unprivileged, the default AppArmor restriction blocks its unshare -Ur, and only after I relax that restriction (lab only) does the unprivileged user get uid=0 inside a new namespace, the precondition the exploit needs.

Terminal on Ubuntu 24.04: creating labuser, proving it cannot sudo, then labuser running unshare -Ur id is denied with Operation not permitted while the AppArmor restriction is on, and succeeds with uid=0 after the restriction is relaxed for the lab.
labuser is genuinely unprivileged (cannot sudo). With Ubuntu's AppArmor restriction on, its unshare is denied; relax it and the unprivileged user gets namespaced uid=0, the precondition the exploit abuses.

Triggering the bug, and what you actually see

This is the honest centre of the lab. As labuser, with the precondition open, I ran the public nft trigger from the FuzzingLabs writeup (a catchall map element with a goto verdict, then a deliberately-failing batch that forces the abort phase) inside a new user+net namespace:

Terminal: the unprivileged labuser runs the nft trigger inside unshare -Urn. The aborted batch runs the buggy abort path, but dmesg shows no nf_tables or use-after-free messages, because on a stock kernel the corruption is latent and silent.
The unprivileged user runs the buggy abort path. On a stock production kernel there is no splat: the use-after-free is latent and silent.

Here is the result I want to be exact about, because the writeups can give the wrong impression: on a stock production kernel the trigger is silent. It runs the buggy abort path, but there is no crash and no dmesg output, even when I looped it 200 times with a deliberate free-and-reuse. The dramatic KASAN: slab-use-after-free in nft_... output you see in the FuzzingLabs and Exodus writeups comes from a KASAN-instrumented debug kernel with extra print statements. To see the corruption you need that debug kernel; to weaponize the latent use-after-free you need the exploit's heap-spray-and-reallocate dance that turns it into a controlled primitive. The bare trigger proves the unprivileged user reaches the vulnerable code; it does not, by itself, hand you a root shell.

So the lab's payoff is not a screenshot of a root prompt. It is the three things above, proven on real kernels: a genuinely unprivileged user reaching the bug, Ubuntu's default AppArmor restriction blocking that path, and the detector telling you EXPOSED versus MITIGATED versus fixed. The full chain to uid=0 (spray to leak the kernel base, a read primitive, RIP control through a corrupted chain, ROP to overwrite modprobe_path or call commit_creds) is what the Exodus and FuzzingLabs writeups describe; neither ships it as a clean run-anywhere exploit, and I am not providing one (the next section explains why).

Fixed versions, and teardown

Patch targets I verified against the vendor trackers while building this lab:

DistroFixed kernelSource of truth
Ubuntu 24.04 (linux)6.8.0-107.107ubuntu.com/security/CVE-2026-23111
Ubuntu 24.04 (linux-aws)6.8.0-1051.54same
Debian 12 (bookworm)6.1.174-1security-tracker.debian.org
Debian 13 (trixie)6.12.90-2same
RHEL / AlmaLinux 10per RHSA (the el10_1 124.x series is vulnerable; the fix ships in the 10.2 / 211.x stream)access.redhat.com/security/cve/cve-2026-23111

Then throw the VM away (multipass delete --purge, vagrant destroy -f, or terminate the cloud instance). The whole point of a disposable VM is that cleanup is one command.

How it actually works

Skip this if you only want the fix. From here down is the mechanism.

nf_tables, sets, maps, and the abort phase

nf_tables is the kernel's modern netfilter engine. Userspace (the nft tool, or any program speaking the netlink API) sends transactions: batched changes to tables, chains, sets, and maps that the kernel either commits atomically or aborts as a unit. That commit-or-abort design is the setting for the bug.

A set is a collection of elements; a map is a set whose elements carry a value, often a verdict like "jump to this chain" (NFT_JUMP) or "go to this chain" (NFT_GOTO). A catchall element is the default element that matches when nothing else does. When an element holds a GOTO/JUMP verdict pointing at a chain, the kernel takes a reference on that chain (chain->use) so the chain cannot be freed while something still points at it. Reference counting like this is how the kernel keeps "is anyone still using this object?" honest.

The inverted check

When a transaction is aborted, the kernel has to undo whatever the transaction tentatively did, including putting back any references it tentatively dropped. nft_map_catchall_activate() is part of that undo path for catchall map elements. It is supposed to act on the inactive elements (the ones the aborted transaction was about to change) and restore their state.

The bug is a single inverted condition: the genmask/activity check had a stray !, so the function processed active elements instead of inactive ones. For a catchall element carrying an NFT_GOTO verdict, that means the code path that should call nft_data_hold() to restore chain->use never runs. The reference count is left one too low.

A reference count that is one too low is a classic use-after-free fuse: the kernel now believes one fewer thing is using the chain than really is, so it can free the chain while a live pointer still references it. The next access to that freed-then-reallocated object is the use-after-free.

Here is the entire fix, in nft_map_catchall_activate() on the transaction-abort path:

c
/* net/netfilter/nf_tables_api.c */

/* vulnerable: the "!" skips INACTIVE elements, so the catchall element that
   actually needs re-activating on abort is skipped, and chain->use is never restored */
if (!nft_set_elem_active(ext, genmask))
        continue;

/* fixed: drop the "!" so the abort path skips ACTIVE elements and processes the
   inactive one, matching the non-catchall sibling nft_mapelem_activate() */
if (nft_set_elem_active(ext, genmask))
        continue;

That single removed character is the whole patch (upstream commit "netfilter: nf_tables: fix inverted genmask check in nft_map_catchall_activate()"), and it is why the nickname going around is "off by !".

From a refcount bug to root (conceptual)

Turning a use-after-free into a root shell is the part the public writeups spend their pages on. At a high level, the disclosed chains do roughly this:

  1. Trigger. Create a map with a catchall element holding a GOTO verdict, then abort a batch delete (DELSET) so the inverted check skips the reference restore. The chain gets freed while still referenced: use-after-free.
  2. Leak. Reclaim the freed slot with a sprayable object (the writeups use struct seq_operations) to read back a kernel pointer and defeat KASLR (recovering the kernel base from a known function pointer).
  3. Arbitrary read. Pivot the leak into a read primitive (the FuzzingLabs chain walks init_ipc_ns and a radix tree) to locate the structures it needs.
  4. Control flow. Get the kernel to call through a pointer the attacker now controls (via a corrupted chain reached through nft_chain_validate()), turning the UAF into instruction-pointer control.
  5. Root. A short ROP chain flips the usual switches: overwrite modprobe_path (or call commit_creds(prepare_kernel_cred(0))), neutralize the LSM that is in the way, and you are root.

None of those steps is something I am going to package up as a copy-paste script here, and I want to be explicit about why.

Why there is no working exploit in this post

The exploit is already public, in detail, from the researchers who built it. Re-publishing a turn-key version on a blog does one thing the original disclosures do not: it removes friction for someone scanning for unpatched hosts to run, today, in the window before everyone patches. That is a bad trade. The mechanism above is enough to understand the bug, brief your team, and recognize it. If you need the byte-precise chain, the Exodus writeup and the FuzzingLabs reproduction are the canonical references, written by the people who did the work. Study them in the disposable VM from the lab section, not on anything you care about.

Were you already exploited?

This is harder to answer cleanly than a file-based CVE, because a successful exploit leaves its mark mostly in volatile kernel state and in whatever the attacker did with their root window. Realistic signals:

bash
# 1. Kernel oops / nf_tables warnings around the suspected window. A failed or
#    noisy exploitation attempt often leaves a trace in the ring buffer / journal.
sudo dmesg -T | grep -iE "nf_tables|netfilter|use-after-free|KASAN|general protection|BUG:"
sudo journalctl -k --since "2 weeks ago" | grep -iE "nf_tables|KASAN|BUG: unable|general protection"

# 2. If auditd is running, unprivileged user-namespace creation is a useful tell.
sudo ausearch -m all -ts recent 2>/dev/null | grep -iE "unshare|clone|userns" | tail

# 3. The usual post-root persistence sweep (this is where the real damage lives).
sudo grep -RIl "" /etc/cron.d /etc/cron.daily /etc/systemd/system 2>/dev/null | xargs ls -la 2>/dev/null
for u in $(cut -d: -f1 /etc/passwd); do sudo test -f /home/$u/.ssh/authorized_keys && echo "keys: $u"; done
sudo cat /root/.ssh/authorized_keys 2>/dev/null

If you find evidence of exploitation, assume host root: rotate every credential the host has touched, snapshot for forensics, audit persistence (SSH keys, cron, systemd units, /etc/ld.so.preload, dropped binaries in /usr/local/bin and /var/tmp), and rebuild from known-good media. A reboot clears the in-kernel state but not the attacker's persistence. My WordPress malware removal writeup covers the cleanup shape for a different root compromise, and the playbook overlaps.

For the log-grepping patterns above, my grep cheat sheet covers the syntax. One thing worth being explicit about, because it trips people up: the usual "lock down root" hardening does not defend against this bug. The exploit becomes root inside the kernel without ever touching the root login path, so disabling root SSH and locking the root password is good general hygiene but changes nothing about whether this escalation succeeds. The defenses that actually matter here are the two from earlier, restrict unprivileged user namespaces and patch the kernel, plus keeping an attacker from ever getting the unprivileged shell in the first place (tight web-app and dependency hygiene, key-only SSH behind a firewall allow-list).

Containers, CI/CD, and multi-tenant hosts

The blast radius is bigger than one box:

  • Containers. The kernel is shared. An unprivileged process inside a container reaches nf_tables the same way a host process does, unless you have blocked it with seccomp or a tight policy. If the container can create a user namespace, the precondition is met inside the container too.
  • CI/CD runners. Any self-hosted runner that executes third-party PR code is a privilege-escalation vector on an unpatched host: untrusted code runs as an unprivileged user, the exploit promotes it to root on the runner, and the runner holds secrets and registry access.
  • Multi-tenant hosts. Shared shells, web hosts, bastions: any tenant can escalate and read everyone else's data.

For all of these the answer is the same pair: patch the host kernel, and restrict unprivileged user namespaces so the precondition is gone even before the patch lands.

References

Primary sources:

Vendor trackers (check your exact release and flavor):

Mitigation background:

Lab toolkit:

Sources

Authoritative references this article was fact-checked against.

TagsCVELinuxKernelPrivilege Escalationnf_tablesnetfilterUse-After-FreeLPEUser NamespacesContainersDevOpsExodus Intelligence

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts