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:
@@ -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
|
||||
Reference in New Issue
Block a user