From 3ad1446489f23036e4f7b8ab4c8a1f865bda078e Mon Sep 17 00:00:00 2001 From: KaraZajac Date: Sat, 16 May 2026 20:33:14 -0400 Subject: [PATCH] Add cls_route4 CVE-2022-2588 module (detect-only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 11th module. net/sched cls_route4 handle-zero dead UAF — discovered by kylebot Aug 2022, fixed mainline 5.20 (commit 9efd23297cca). Bug existed since 2.6.39 → very wide attack surface. - modules/cls_route4_cve_2022_2588/iamroot_modules.{c,h}: - kernel_range thresholds: 5.4.213 / 5.10.143 / 5.15.69 / 5.18.18 / 5.19.7 / mainline 5.20+ - can_unshare_userns() probes user_ns+net_ns clone availability (the exploit's CAP_NET_ADMIN-in-userns gate) - cls_route4_module_available() checks /proc/modules - Reports VULNERABLE if kernel in range AND user_ns allowed; PRECOND_FAIL if user_ns denied; OK if patched. - Exploit stub returns IAMROOT_PRECOND_FAIL with reference to kylebot's public PoC. - Auditd rule: tc-style sendto syscalls (rough; legit traffic shaping will trip — tune by user). iamroot.c + Makefile + core/registry.h wired. CVES.md row added. Verified on kctf-mgr (6.12.86): module reports OK, total module count = 11. --- CVES.md | 1 + Makefile | 7 +- core/registry.h | 1 + iamroot.c | 1 + .../iamroot_modules.c | 171 ++++++++++++++++++ .../iamroot_modules.h | 12 ++ 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 modules/cls_route4_cve_2022_2588/iamroot_modules.c create mode 100644 modules/cls_route4_cve_2022_2588/iamroot_modules.h diff --git a/CVES.md b/CVES.md index ba0e4f1..c1cb677 100644 --- a/CVES.md +++ b/CVES.md @@ -29,6 +29,7 @@ Status legend: | CVE-2021-4034 | Pwnkit — pkexec argv[0]=NULL → env-injection | LPE (userspace setuid binary) | polkit 0.121 (2022-01-25) | `pwnkit` | 🟢 | Full detect + exploit (canonical Qualys-style: gconv-modules + execve NULL-argv). Detect handles both polkit version formats (legacy "0.105" + modern "126"). Exploit compiles payload via target's gcc → falls back gracefully if no cc available. Cleanup nukes /tmp/iamroot-pwnkit-* workdirs. **First userspace LPE in IAMROOT**. Ships auditd + sigma rules. | | CVE-2024-1086 | nf_tables — `nft_verdict_init` cross-cache UAF | LPE (kernel arbitrary R/W via slab UAF) | mainline 6.8-rc1 (Jan 2024) | `nf_tables` | 🔵 | Detect-only. Branch-backport ranges checked (6.7.2 / 6.6.13 / 6.1.74 / 5.15.149 / 5.10.210 / 5.4.269). Also checks unprivileged user_ns clone availability (the exploit's trigger gate) — reports PRECOND_FAIL if userns is locked down even when the kernel is vulnerable. Full Notselwyn-style exploit is the next nf_tables commit. | | CVE-2021-3493 | Ubuntu overlayfs userns file-capability injection | LPE (host root via file caps in userns-mounted overlayfs) | Ubuntu USN-4915-1 (Apr 2021) | `overlayfs` | 🔵 | Detect-only. **Ubuntu-specific** (vanilla upstream didn't enable userns-overlayfs-mount until 5.11). Detect: parses /etc/os-release for ID=ubuntu, checks unprivileged_userns_clone sysctl, AND with `--active` actually attempts the userns+overlayfs mount as a fork-isolated probe. Reports OK on non-Ubuntu, PRECOND_FAIL if userns locked down. Ships auditd rules covering mount(overlay) + setxattr(security.capability). | +| CVE-2022-2588 | net/sched cls_route4 handle-zero dead UAF | LPE (kernel UAF in cls_route4 filter remove) | mainline 5.20 / 5.19.7 (Aug 2022) | `cls_route4` | 🔵 | Detect-only. Branch-backport thresholds: 5.4.213 / 5.10.143 / 5.15.69 / 5.18.18 / 5.19.7. Bug exists since 2.6.39 — very wide surface. Detect also probes user_ns+net_ns clone availability; locked-down hosts report PRECOND_FAIL. Full exploit (kylebot-style: tc filter add+rm + spray + cred overwrite) follows. | | CVE-TBD | Fragnesia (ESP shared-frag in-place encrypt) | LPE (page-cache write) | mainline TBD | `_stubs/fragnesia_TBD` | ⚪ | Stub. Per `findings/audit_leak_write_modprobe_backups_2026-05-16.md`, requires CAP_NET_ADMIN in userns netns — may or may not be in-scope depending on target environment. | ## Operations supported per module diff --git a/Makefile b/Makefile index 3faf55c..ba20084 100644 --- a/Makefile +++ b/Makefile @@ -56,10 +56,15 @@ OVL_DIR := modules/overlayfs_cve_2021_3493 OVL_SRCS := $(OVL_DIR)/iamroot_modules.c OVL_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(OVL_SRCS)) +# Family: cls_route4 (CVE-2022-2588) +CR4_DIR := modules/cls_route4_cve_2022_2588 +CR4_SRCS := $(CR4_DIR)/iamroot_modules.c +CR4_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(CR4_SRCS)) + # Top-level dispatcher TOP_OBJ := $(BUILD)/iamroot.o -ALL_OBJS := $(TOP_OBJ) $(CORE_OBJS) $(CFF_OBJS) $(DP_OBJS) $(EB_OBJS) $(PK_OBJS) $(NFT_OBJS) $(OVL_OBJS) +ALL_OBJS := $(TOP_OBJ) $(CORE_OBJS) $(CFF_OBJS) $(DP_OBJS) $(EB_OBJS) $(PK_OBJS) $(NFT_OBJS) $(OVL_OBJS) $(CR4_OBJS) .PHONY: all clean debug static help diff --git a/core/registry.h b/core/registry.h index 4e13ce3..f07f000 100644 --- a/core/registry.h +++ b/core/registry.h @@ -26,5 +26,6 @@ void iamroot_register_entrybleed(void); void iamroot_register_pwnkit(void); void iamroot_register_nf_tables(void); void iamroot_register_overlayfs(void); +void iamroot_register_cls_route4(void); #endif /* IAMROOT_REGISTRY_H */ diff --git a/iamroot.c b/iamroot.c index 2f94401..6bbdaab 100644 --- a/iamroot.c +++ b/iamroot.c @@ -345,6 +345,7 @@ int main(int argc, char **argv) iamroot_register_pwnkit(); iamroot_register_nf_tables(); iamroot_register_overlayfs(); + iamroot_register_cls_route4(); enum mode mode = MODE_SCAN; struct iamroot_ctx ctx = {0}; diff --git a/modules/cls_route4_cve_2022_2588/iamroot_modules.c b/modules/cls_route4_cve_2022_2588/iamroot_modules.c new file mode 100644 index 0000000..9f4cd86 --- /dev/null +++ b/modules/cls_route4_cve_2022_2588/iamroot_modules.c @@ -0,0 +1,171 @@ +/* + * cls_route4_cve_2022_2588 — IAMROOT module + * + * net/sched cls_route4 dead UAF: when a route4 filter with handle==0 + * is removed, the corresponding hashtable bucket may keep a stale + * pointer to the freed filter. Subsequent traffic-class lookup + * follows the dangling pointer → kernel UAF. + * + * Discovered by kylebot / xkernel (Aug 2022). Mainline fix + * 9efd23297cca "net_sched: cls_route: remove from list when handle + * is 0" (Aug 2022). Bug existed since 2.6.39 — very wide + * vulnerability surface. + * + * STATUS: 🔵 DETECT-ONLY. Public exploits exist; porting is + * follow-up. + * + * Exploitation preconditions: + * - cls_route4 module compiled in / loadable (CONFIG_NET_CLS_ROUTE4) + * - CAP_NET_ADMIN (usually obtained via user_ns + map-root-to-uid) + * - unprivileged_userns_clone=1 if going the userns route + * + * Affected kernel ranges (vulnerable < these): + * 5.4.x : K < 5.4.213 + * 5.10.x : K < 5.10.143 + * 5.15.x : K < 5.15.69 + * 5.18.x : K < 5.18.18 + * 5.19.x : K < 5.19.7 + * Mainline 5.20+ / 6.0+ : patched (the fix landed before 5.20-rc) + */ + +#include "iamroot_modules.h" +#include "../../core/registry.h" +#include "../../core/kernel_range.h" + +#include +#include +#include +#include +#include +#include + +static const struct kernel_patched_from cls_route4_patched_branches[] = { + {5, 4, 213}, + {5, 10, 143}, + {5, 15, 69}, + {5, 18, 18}, + {5, 19, 7}, + {5, 20, 0}, /* mainline */ +}; + +static const struct kernel_range cls_route4_range = { + .patched_from = cls_route4_patched_branches, + .n_patched_from = sizeof(cls_route4_patched_branches) / + sizeof(cls_route4_patched_branches[0]), +}; + +static bool cls_route4_module_available(void) +{ + /* Check /proc/modules for currently-loaded cls_route4. Even when + * not loaded, autoload may bring it in on first tc qdisc add — we + * conservatively treat "not loaded now" as "potentially available". */ + FILE *f = fopen("/proc/modules", "r"); + if (!f) return false; + char line[512]; + bool found = false; + while (fgets(line, sizeof line, f)) { + if (strncmp(line, "cls_route4 ", 11) == 0) { found = true; break; } + } + fclose(f); + return found; +} + +static int can_unshare_userns(void) +{ + pid_t pid = fork(); + if (pid < 0) return -1; + if (pid == 0) { + if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) _exit(0); + _exit(1); + } + int status; + waitpid(pid, &status, 0); + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +} + +static iamroot_result_t cls_route4_detect(const struct iamroot_ctx *ctx) +{ + struct kernel_version v; + if (!kernel_version_current(&v)) { + fprintf(stderr, "[!] cls_route4: could not parse kernel version\n"); + return IAMROOT_TEST_ERROR; + } + + /* Bug-introduction predates anything we'd reasonably scan; if the + * kernel is below the oldest LTS we model (5.4), still report + * vulnerable. */ + bool patched = kernel_range_is_patched(&cls_route4_range, &v); + if (patched) { + if (!ctx->json) { + fprintf(stderr, "[+] cls_route4: kernel %s is patched\n", v.release); + } + return IAMROOT_OK; + } + + /* Module + userns preconditions. */ + bool nft_loaded = cls_route4_module_available(); + int userns_ok = can_unshare_userns(); + + if (!ctx->json) { + fprintf(stderr, "[i] cls_route4: kernel %s in vulnerable range\n", v.release); + fprintf(stderr, "[i] cls_route4: cls_route4 module currently loaded: %s\n", + nft_loaded ? "yes" : "no (may autoload)"); + fprintf(stderr, "[i] cls_route4: unprivileged user_ns + net_ns clone: %s\n", + userns_ok == 1 ? "ALLOWED" : + userns_ok == 0 ? "DENIED" : "could not test"); + } + + /* If userns is locked down, unprivileged-LPE path is closed. + * Kernel still needs patching though — report PRECOND_FAIL so the + * verdict isn't "VULNERABLE" but the issue isn't masked. */ + if (userns_ok == 0) { + if (!ctx->json) { + fprintf(stderr, "[+] cls_route4: user_ns denied → unprivileged exploit unreachable\n"); + } + return IAMROOT_PRECOND_FAIL; + } + + if (!ctx->json) { + fprintf(stderr, "[!] cls_route4: VULNERABLE — kernel in range AND user_ns allowed\n"); + } + return IAMROOT_VULNERABLE; +} + +static iamroot_result_t cls_route4_exploit(const struct iamroot_ctx *ctx) +{ + (void)ctx; + fprintf(stderr, + "[-] cls_route4: exploit not yet implemented in IAMROOT.\n" + " Status: 🔵 DETECT-ONLY. Reference: kylebot's public PoC.\n" + " Exploit: tc filter add ... route4 handle 0; then remove;\n" + " spray to refill the freed slot; trigger via traffic class\n" + " lookup; cred overwrite or modprobe_path hijack.\n"); + return IAMROOT_PRECOND_FAIL; +} + +static const char cls_route4_auditd[] = + "# cls_route4 dead UAF (CVE-2022-2588) — auditd detection rules\n" + "# Flag tc filter operations with route4 classifier from non-root.\n" + "# False positives: legitimate traffic-shaping setup. Tune by user.\n" + "-a always,exit -F arch=b64 -S sendto -F a3=0x10 -k iamroot-cls-route4\n"; + +const struct iamroot_module cls_route4_module = { + .name = "cls_route4", + .cve = "CVE-2022-2588", + .summary = "net/sched cls_route4 handle-zero dead UAF → kernel R/W", + .family = "cls_route4", + .kernel_range = "2.6.39 ≤ K, fixed mainline 5.20; backports: 5.4.213 / 5.10.143 / 5.15.69 / 5.18.18 / 5.19.7", + .detect = cls_route4_detect, + .exploit = cls_route4_exploit, + .mitigate = NULL, /* mitigation: blacklist cls_route4 module OR disable user_ns */ + .cleanup = NULL, + .detect_auditd = cls_route4_auditd, + .detect_sigma = NULL, + .detect_yara = NULL, + .detect_falco = NULL, +}; + +void iamroot_register_cls_route4(void) +{ + iamroot_register(&cls_route4_module); +} diff --git a/modules/cls_route4_cve_2022_2588/iamroot_modules.h b/modules/cls_route4_cve_2022_2588/iamroot_modules.h new file mode 100644 index 0000000..0d7cdef --- /dev/null +++ b/modules/cls_route4_cve_2022_2588/iamroot_modules.h @@ -0,0 +1,12 @@ +/* + * cls_route4_cve_2022_2588 — IAMROOT module registry hook + */ + +#ifndef CLS_ROUTE4_IAMROOT_MODULES_H +#define CLS_ROUTE4_IAMROOT_MODULES_H + +#include "../../core/module.h" + +extern const struct iamroot_module cls_route4_module; + +#endif