
Reproduces CVE-2023-32233, a Linux kernel nf_tables use-after-free leading to local privilege escalation. Includes PoC source, root-cause analysis, exploitation chain breakdown, and defensive detection/mitigation notes.
What this repo is: my hands-on reproduction and study of CVE-2023-32233, a use-after-free in the Linux kernel Netfilter nf_tables subsystem that allows local privilege escalation, publicly disclosed in May 2023 by Patryk Sondej and Piotr Krysiuk.
Credit: the PoC (exploit.c) and the original write-up are by
@Liuk3r. My work in this repo
is reproducing the exploit in my own lab (Ubuntu 23.04, kernel 6.2.0-20)
and documenting the root cause and exploitation chain below. All testing
was done on dedicated lab machines.
nf_tables processes configuration updates as an atomic batch. The validation of each operation against the state changes of previous operations in the same batch is insufficient. Concretely:
nft_rule containing a lookup expression on an
anonymous nft_set that holds some elements.NFT_MSG_DELRULE — deletes the rule, which implicitly deletes the
lookup expression and the anonymous nft_set;NFT_MSG_DELSETELEM — deletes an element of the already deleted
anonymous set.nf_tables_commit_release() queues resources
onto nf_tables_destroy_list, processed later by
nf_tables_trans_destroy_work():
first nft_commit_release() → nf_tables_rule_destroy() →
nft_lookup_destroy() → nft_set_destroy() → kvfree() frees the
nft_set;
then, for NFT_MSG_DELSETELEM, nf_tables_set_elem_destroy() calls
nft_set_elem_ext() which dereferences the freed nft_set:
If set->ops->elemsize is corrupted, an attacker-chosen memory location
is interpreted as an nft_set_ext — the primitive everything else builds
on.
nf_tables_trans_destroy_work() running on a
background worker thread: insert a large set-destroy operation as a
controlled delay, pin other CPUs busy, and reallocate the freed
nft_set chunk from the same CPU with an nft_set of a different
type (different elemsize) → type confusion.nft_set_ext headers with out-of-range offsets so
nf_tables_set_elem_destroy() walks adjacent chunks as a list of
nft_expr to destroy.nft_log expressions with controlled NFTA_LOG_PREFIX;
nft_log_destroy() frees priv->prefix, giving an overlapping
allocation primitive in kmalloc-{8..192} (NULL-byte-limited at first).nft_object->udata to lift the NULL-byte restriction
on the dangling read.Notably, the primitives chosen avoid anything CFI would block — no indirect-call hijacking needed at any step.
SLAB_FREELIST_HARDENED), INIT_ON_FREE, and per-CPU slab reuse
restrictions all attack step 1; CFI (where present) constrains step 3+.nf_tables batch netlink messages
containing DELRULE+DELSETELEM on anonymous sets; crash triage for
nft_set_elem_ext / nf_tables_trans_destroy_work in the stack.nftables requires CAP_NET_ADMIN, but
unprivileged user namespaces grant it — this is why
kernel.unprivileged_userns_clone=0 is a meaningful hardening knob.Tested under Ubuntu 23.04 (Lunar Lobster), kernel 6.2.0-20-generic.
sudo apt install gcc libmnl-dev libnftnl-dev
gcc -Wall -o exploit exploit.c -lmnl -lnftnl
The built-in profile targets the Ubuntu 23.04 binary kernels
(linux-image-6.2.0-20-generic 6.2.0-20.20). To test other kernels,
extract symbols into a profile file:
modprobe nf_tables
egrep ' (nft_counter_ops|nft_counter_destroy|free_percpu|modprobe_path)(\s|$)' /proc/kallsyms > profile
Machine-code layout of nft_counter_destroy() varies with compiler and
options; see ORIGIN.md for the full parameter reference
(nft_counter_destroy_call_offset/mask/check) and race-tuning knobs
(race_lead_sleep etc.). Reported success probability is ≥80% on idle
bare-metal Intel systems; some microarchitectures (e.g. Alder Lake) need
extra tuning.
The PoC leaves the kernel in an unstable state with corrupted memory after a successful run. Only test on a dedicated, disposable system or VM snapshot — never on anything with data you care about.
exploit.c — PoC source (by @Liuk3r)ORIGIN.md — original vulnerability & exploitation write-up (by Liuk3r)README.md — this file: reproduction + my analysis notesResearch conducted for defensive/educational purposes in an isolated lab environment.
static inline struct nft_set_ext *nft_set_elem_ext(const struct nft_set *set,
void *elem)
{
return elem + set->ops->elemsize;
}
nft_dynset element spray for two stateful expr types:
nft_counter — leaks nft_counter_ops → base of nf_tables.ko
(defeats KASLR for the module);nft_quota — consumed pointer gives arbitrary read
(NFT_MSG_GETSETELEM → nft_quota_do_dump() → NFTA_QUOTA_CONSUMED)
and arbitrary write (nft_overquota() adds skb->len to
*priv->consumed on loopback traffic).modprobe_path ("/sbin/modprobe" → "//tmp/modprobe") →
root process execution with attacker-controlled content.