Phase 7: PTRACE_TRACEME (CVE-2019-13272) + xt_compat (CVE-2021-22555)

Two famous 2017-2020-era LPEs to broaden 'THE tool for folks'
coverage. Both detect-only initially; exploit ports as follow-ups.

ptrace_traceme (CVE-2019-13272 — jannh @ Google P0, Jun 2019):
- Famous because works on default-config systems with no user_ns
  required — locked-down environments were still vulnerable.
- kernel_range thresholds: 4.4.182 / 4.9.182 / 4.14.131 / 4.19.58 /
  5.0.20 / 5.1.17 / mainline 5.2+
- Exploit shape (deferred): fork → child PTRACE_TRACEME → parent
  execve setuid binary → child ptrace-injects shellcode → root.
- Auditd: flag PTRACE_TRACEME (request 0) — false positives via
  gdb/strace; tune by exclusion.

netfilter_xtcompat (CVE-2021-22555 — Andy Nguyen @ Google P0):
- Bug existed since 2.6.19 (2006) — 15 years of latent vuln. Famous
  for that age + default-config reachability via unprivileged_userns.
- kernel_range thresholds: 4.4.266 / 4.9.266 / 4.14.230 / 4.19.185
  / 5.4.110 / 5.10.27 / 5.11.10 / mainline 5.12+
- detect() probes user_ns+net_ns clone; locked-down → PRECOND_FAIL.
- Exploit shape (deferred): heap massage via msg_msg + sk_buff cross-
  cache groom → kernel R/W → cred or modprobe_path overwrite. ~400
  lines port from Andy's public exploit.c.
- Auditd: unshare + iptables-style setsockopt + msgsnd — combined,
  the canonical exploit footprint.

Both wired into iamroot.c, core/registry.h, Makefile. CVES.md rows
added with detailed status.

Coverage by year now:
  2016: dirty_cow                              🟢
  2019: ptrace_traceme                         🔵
  2021: pwnkit, overlayfs, netfilter_xtcompat  🟢/🟢/🔵
  2022: dirty_pipe, cls_route4                 🟢/🔵
  2023: entrybleed                             🟢
  2024: nf_tables                              🔵
  2026: copy_fail family (×5)                  🟢

Module count: 14. Build clean (no warnings).
This commit is contained in:
2026-05-16 20:47:24 -04:00
parent e2fcc6a9e0
commit 102b117d4e
8 changed files with 329 additions and 1 deletions
@@ -0,0 +1,161 @@
/*
* netfilter_xtcompat_cve_2021_22555 — IAMROOT module
*
* Heap-out-of-bounds in xt_compat_target_to_user(): the 32-bit
* compat handler for iptables rule export wrote up to 4 bytes
* beyond a heap allocation when copying rule names from kernel to
* userspace. Exploitable via msg_msg slab cross-cache groom into
* a kernel R/W primitive.
*
* Discovered by Andy Nguyen (Google), April 2021. Famous because
* the bug existed since 2.6.19 (2006) — fifteen years of latent
* vulnerability — and it works on default-config kernels with
* unprivileged user_ns enabled (no special hardware or modules).
*
* STATUS: 🔵 DETECT-ONLY. Public PoC (Andy's "exploit.c") works
* end-to-end with msg_msg + sk_buff sprays; porting is ~400 lines.
*
* Affected: kernel 2.6.19+ until backports landed:
* 5.11.x : K >= 5.11.10
* 5.10.x : K >= 5.10.27
* 5.4.x : K >= 5.4.110
* 4.19.x : K >= 4.19.185
* 4.14.x : K >= 4.14.230
* 4.9.x : K >= 4.9.266
* 4.4.x : K >= 4.4.266
*
* Preconditions:
* - CAP_NET_ADMIN (usually via unprivileged user_ns clone)
* - iptables/ip_tables/x_tables kernel modules available
* (almost always autoload-able on default-config kernels)
*/
#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 netfilter_xtcompat_patched_branches[] = {
{4, 4, 266},
{4, 9, 266},
{4, 14, 230},
{4, 19, 185},
{5, 4, 110},
{5, 10, 27},
{5, 11, 10},
{5, 12, 0}, /* mainline (5.12-rc) */
};
static const struct kernel_range netfilter_xtcompat_range = {
.patched_from = netfilter_xtcompat_patched_branches,
.n_patched_from = sizeof(netfilter_xtcompat_patched_branches) /
sizeof(netfilter_xtcompat_patched_branches[0]),
};
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 netfilter_xtcompat_detect(const struct iamroot_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] netfilter_xtcompat: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
}
if (v.major < 2 || (v.major == 2 && v.minor < 6)) {
if (!ctx->json) {
fprintf(stderr, "[+] netfilter_xtcompat: kernel %s predates the bug introduction\n",
v.release);
}
return IAMROOT_OK;
}
bool patched = kernel_range_is_patched(&netfilter_xtcompat_range, &v);
if (patched) {
if (!ctx->json) {
fprintf(stderr, "[+] netfilter_xtcompat: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
}
int userns_ok = can_unshare_userns();
if (!ctx->json) {
fprintf(stderr, "[i] netfilter_xtcompat: kernel %s in vulnerable range "
"(bug existed since 2.6.19, 2006)\n", v.release);
fprintf(stderr, "[i] netfilter_xtcompat: user_ns+net_ns clone: %s\n",
userns_ok == 1 ? "ALLOWED" :
userns_ok == 0 ? "DENIED" : "could not test");
}
if (userns_ok == 0) {
if (!ctx->json) {
fprintf(stderr, "[+] netfilter_xtcompat: user_ns denied → "
"unprivileged exploit path unreachable\n");
}
return IAMROOT_PRECOND_FAIL;
}
if (!ctx->json) {
fprintf(stderr, "[!] netfilter_xtcompat: VULNERABLE — kernel in range "
"AND user_ns reachable\n");
}
return IAMROOT_VULNERABLE;
}
static iamroot_result_t netfilter_xtcompat_exploit(const struct iamroot_ctx *ctx)
{
(void)ctx;
fprintf(stderr,
"[-] netfilter_xtcompat: exploit not yet implemented in IAMROOT.\n"
" Status: 🔵 DETECT-ONLY. Reference: Andy Nguyen's public PoC\n"
" (~400 lines, msg_msg + sk_buff cross-cache groom). Porting\n"
" is a substantial follow-up — the exploit's heap-massage\n"
" sequence and cred-overwrite walk are the bulk.\n");
return IAMROOT_PRECOND_FAIL;
}
static const char netfilter_xtcompat_auditd[] =
"# CVE-2021-22555 — auditd detection rules\n"
"# The exploit's hallmarks: unshare(USER|NET) chained with iptables\n"
"# rule setup via setsockopt() and msgsnd/msgrcv heap-spray patterns.\n"
"-a always,exit -F arch=b64 -S unshare -k iamroot-xtcompat\n"
"-a always,exit -F arch=b64 -S setsockopt -F a2=64 -k iamroot-xtcompat-iptopt\n"
"-a always,exit -F arch=b64 -S msgsnd -k iamroot-xtcompat-msgmsg\n";
const struct iamroot_module netfilter_xtcompat_module = {
.name = "netfilter_xtcompat",
.cve = "CVE-2021-22555",
.summary = "iptables xt_compat_target_to_user heap-OOB write → cross-cache UAF → root",
.family = "netfilter_xtcompat",
.kernel_range = "2.6.19 ≤ K, fixed mainline 5.12; backports: 5.11.10 / 5.10.27 / 5.4.110 / 4.19.185 / 4.14.230 / 4.9.266 / 4.4.266",
.detect = netfilter_xtcompat_detect,
.exploit = netfilter_xtcompat_exploit,
.mitigate = NULL, /* mitigation: upgrade kernel; disable unprivileged_userns_clone */
.cleanup = NULL,
.detect_auditd = netfilter_xtcompat_auditd,
.detect_sigma = NULL,
.detect_yara = NULL,
.detect_falco = NULL,
};
void iamroot_register_netfilter_xtcompat(void)
{
iamroot_register(&netfilter_xtcompat_module);
}
@@ -0,0 +1,12 @@
/*
* netfilter_xtcompat_cve_2021_22555 — IAMROOT module registry hook
*/
#ifndef NETFILTER_XTCOMPAT_IAMROOT_MODULES_H
#define NETFILTER_XTCOMPAT_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module netfilter_xtcompat_module;
#endif
@@ -0,0 +1,127 @@
/*
* ptrace_traceme_cve_2019_13272 — IAMROOT module
*
* PTRACE_TRACEME on a parent that subsequently execve's a setuid
* binary results in the kernel granting ptrace privileges over the
* privileged process to the unprivileged child. Discovered by Jann
* Horn (Google Project Zero, June 2019).
*
* STATUS: 🔵 DETECT-ONLY. Exploit follows jannh's public PoC: fork
* a child that does PTRACE_TRACEME pointing at the parent, parent
* execve's a chosen setuid binary (e.g., su, pkexec), child then
* ptrace-injects shellcode into the now-elevated process.
*
* Affected: kernels < 5.1.17 mainline. Stable backports varied; the
* fix landed in stable as:
* 5.1.x : K >= 5.1.17
* 5.0.x : K >= 5.0.20 (older LTS — many distros stayed on 4.x)
* 4.19.x: K >= 4.19.58
* 4.14.x: K >= 4.14.131
* 4.9.x : K >= 4.9.182
* 4.4.x : K >= 4.4.182
*
* No exotic preconditions. Doesn't need user_ns. Works on
* default-config systems — that's part of why it's famous: even
* locked-down environments without unprivileged_userns_clone were
* vulnerable.
*/
#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>
static const struct kernel_patched_from ptrace_traceme_patched_branches[] = {
{4, 4, 182},
{4, 9, 182},
{4, 14, 131},
{4, 19, 58},
{5, 0, 20},
{5, 1, 17},
{5, 2, 0}, /* mainline (5.2-rc) */
};
static const struct kernel_range ptrace_traceme_range = {
.patched_from = ptrace_traceme_patched_branches,
.n_patched_from = sizeof(ptrace_traceme_patched_branches) /
sizeof(ptrace_traceme_patched_branches[0]),
};
static iamroot_result_t ptrace_traceme_detect(const struct iamroot_ctx *ctx)
{
struct kernel_version v;
if (!kernel_version_current(&v)) {
fprintf(stderr, "[!] ptrace_traceme: could not parse kernel version\n");
return IAMROOT_TEST_ERROR;
}
/* Bug existed since ptrace's inception (early 2.x); anything
* pre-LTS-backport is vulnerable. Anything < 4.4 in our range
* model defaults to vulnerable since no entry covers it. */
if (v.major < 4 || (v.major == 4 && v.minor < 4)) {
if (!ctx->json) {
fprintf(stderr, "[!] ptrace_traceme: ancient kernel %s — assume VULNERABLE\n",
v.release);
}
return IAMROOT_VULNERABLE;
}
bool patched = kernel_range_is_patched(&ptrace_traceme_range, &v);
if (patched) {
if (!ctx->json) {
fprintf(stderr, "[+] ptrace_traceme: kernel %s is patched\n", v.release);
}
return IAMROOT_OK;
}
if (!ctx->json) {
fprintf(stderr, "[!] ptrace_traceme: kernel %s in vulnerable range\n", v.release);
fprintf(stderr, "[i] ptrace_traceme: no exotic preconditions — works on default config "
"(no user_ns required)\n");
}
return IAMROOT_VULNERABLE;
}
static iamroot_result_t ptrace_traceme_exploit(const struct iamroot_ctx *ctx)
{
(void)ctx;
fprintf(stderr,
"[-] ptrace_traceme: exploit not yet implemented in IAMROOT.\n"
" Status: 🔵 DETECT-ONLY. Reference: jannh's PoC.\n"
" Exploit shape: fork() → child calls PTRACE_TRACEME → parent\n"
" execve's a setuid binary (su, pkexec, ping with cap_net_raw,\n"
" etc.) → child becomes tracer of the now-privileged process\n"
" → ptrace-inject shellcode → root.\n");
return IAMROOT_PRECOND_FAIL;
}
static const char ptrace_traceme_auditd[] =
"# PTRACE_TRACEME LPE (CVE-2019-13272) — auditd detection rules\n"
"# Flag PTRACE_TRACEME (request 0) followed by parent execve of\n"
"# a setuid binary. False positives: gdb, strace, debuggers.\n"
"-a always,exit -F arch=b64 -S ptrace -F a0=0 -k iamroot-ptrace-traceme\n"
"-a always,exit -F arch=b32 -S ptrace -F a0=0 -k iamroot-ptrace-traceme\n";
const struct iamroot_module ptrace_traceme_module = {
.name = "ptrace_traceme",
.cve = "CVE-2019-13272",
.summary = "PTRACE_TRACEME → setuid binary execve → cred-escalation via ptrace inject",
.family = "ptrace_traceme",
.kernel_range = "K < 5.1.17, backports: 5.0.20 / 4.19.58 / 4.14.131 / 4.9.182 / 4.4.182",
.detect = ptrace_traceme_detect,
.exploit = ptrace_traceme_exploit,
.mitigate = NULL, /* mitigation: upgrade kernel; OR set ptrace_scope sysctl */
.cleanup = NULL,
.detect_auditd = ptrace_traceme_auditd,
.detect_sigma = NULL,
.detect_yara = NULL,
.detect_falco = NULL,
};
void iamroot_register_ptrace_traceme(void)
{
iamroot_register(&ptrace_traceme_module);
}
@@ -0,0 +1,12 @@
/*
* ptrace_traceme_cve_2019_13272 — IAMROOT module registry hook
*/
#ifndef PTRACE_TRACEME_IAMROOT_MODULES_H
#define PTRACE_TRACEME_IAMROOT_MODULES_H
#include "../../core/module.h"
extern const struct iamroot_module ptrace_traceme_module;
#endif