Add cls_route4 CVE-2022-2588 module (detect-only)

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.
This commit is contained in:
2026-05-16 20:33:14 -04:00
parent fe33400f94
commit 3ad1446489
6 changed files with 192 additions and 1 deletions
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h>
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);
}
@@ -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