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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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