From 3eeee01f067e579451c242b5f061750e93554eda Mon Sep 17 00:00:00 2001 From: KaraZajac Date: Sat, 16 May 2026 20:22:32 -0400 Subject: [PATCH] =?UTF-8?q?Phase=207:=20overlayfs=20CVE-2021-3493=20module?= =?UTF-8?q?=20(Ubuntu=20userns=20LPE)=20=E2=80=94=20detect-only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 10th module. Ubuntu-specific userns + overlayfs LPE that injects file capabilities cross-namespace. - modules/overlayfs_cve_2021_3493/iamroot_modules.{c,h}: - is_ubuntu() — parses /etc/os-release for ID=ubuntu or ID_LIKE=ubuntu. Non-Ubuntu hosts get IAMROOT_OK immediately (the bug is specific to Ubuntu's modified overlayfs). - unprivileged_userns_clone gate — sysctl=0 → PRECOND_FAIL - Active probe (--active): forks a child that enters userns + mountns and attempts the overlayfs mount inside /tmp. Mount success on Ubuntu = VULNERABLE. Mount denied = patched / AppArmor block. Child-isolated so parent's namespace state is untouched. - Version fallback: kernel < 5.13 = vulnerable-by-inference for Ubuntu kernels; recommend --active for confirmation. - Exploit: detect-only stub. Reference vsh's exploit-cve-2021-3493 for full version (mount overlayfs in userns, drop binary with cap_setuid+ep into upper layer, re-exec outside ns). - Embedded auditd rules: mount(overlay) syscall + security.capability xattr writes (the exploit's two-step footprint). Verified end-to-end on kctf-mgr (Debian): iamroot --scan → 'not Ubuntu — bug is Ubuntu-specific' → IAMROOT_OK Module count: 10. Active-probe pattern now applies to dirty_pipe, entrybleed, and overlayfs (and copy_fail_family via existing dirtyfail_active_probes global). Detect quality across the corpus materially improved this session. --- CVES.md | 1 + Makefile | 7 +- core/registry.h | 1 + iamroot.c | 1 + .../overlayfs_cve_2021_3493/iamroot_modules.c | 238 ++++++++++++++++++ .../overlayfs_cve_2021_3493/iamroot_modules.h | 12 + 6 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 modules/overlayfs_cve_2021_3493/iamroot_modules.c create mode 100644 modules/overlayfs_cve_2021_3493/iamroot_modules.h diff --git a/CVES.md b/CVES.md index 0a43aab..ba0e4f1 100644 --- a/CVES.md +++ b/CVES.md @@ -28,6 +28,7 @@ Status legend: | CVE-2026-31402 | NFS replay-cache heap overflow | LPE (NFS server) | mainline 2026-04-03 | — | ⚪ | Candidate. Different audience (NFS servers) — TBD whether in-scope. | | 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-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 diff --git a/Makefile b/Makefile index 94491a3..3faf55c 100644 --- a/Makefile +++ b/Makefile @@ -51,10 +51,15 @@ NFT_DIR := modules/nf_tables_cve_2024_1086 NFT_SRCS := $(NFT_DIR)/iamroot_modules.c NFT_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(NFT_SRCS)) +# Family: overlayfs (CVE-2021-3493) +OVL_DIR := modules/overlayfs_cve_2021_3493 +OVL_SRCS := $(OVL_DIR)/iamroot_modules.c +OVL_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(OVL_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) +ALL_OBJS := $(TOP_OBJ) $(CORE_OBJS) $(CFF_OBJS) $(DP_OBJS) $(EB_OBJS) $(PK_OBJS) $(NFT_OBJS) $(OVL_OBJS) .PHONY: all clean debug static help diff --git a/core/registry.h b/core/registry.h index 2104797..4e13ce3 100644 --- a/core/registry.h +++ b/core/registry.h @@ -25,5 +25,6 @@ void iamroot_register_dirty_pipe(void); void iamroot_register_entrybleed(void); void iamroot_register_pwnkit(void); void iamroot_register_nf_tables(void); +void iamroot_register_overlayfs(void); #endif /* IAMROOT_REGISTRY_H */ diff --git a/iamroot.c b/iamroot.c index 179c873..699680a 100644 --- a/iamroot.c +++ b/iamroot.c @@ -222,6 +222,7 @@ int main(int argc, char **argv) iamroot_register_entrybleed(); iamroot_register_pwnkit(); iamroot_register_nf_tables(); + iamroot_register_overlayfs(); enum mode mode = MODE_SCAN; struct iamroot_ctx ctx = {0}; diff --git a/modules/overlayfs_cve_2021_3493/iamroot_modules.c b/modules/overlayfs_cve_2021_3493/iamroot_modules.c new file mode 100644 index 0000000..443da5e --- /dev/null +++ b/modules/overlayfs_cve_2021_3493/iamroot_modules.c @@ -0,0 +1,238 @@ +/* + * overlayfs_cve_2021_3493 — IAMROOT module + * + * Ubuntu-flavor overlayfs lets an unprivileged user mount overlayfs + * inside a user namespace, then set file capabilities on a file in + * the upper layer. The capabilities are NOT scoped to the userns — + * they propagate to the host view of the same inode, letting an + * unprivileged user create a file with CAP_SETUID/CAP_DAC_OVERRIDE + * that root will honor outside the namespace. + * + * Discovered by Vasily Kulikov; published April 2021. Specific to + * Ubuntu's modified overlayfs (vanilla upstream overlayfs didn't + * allow the userns-mount path at the time). The fix landed via + * Ubuntu's apparmor + an upstream Vfsmount audit. + * + * STATUS: 🔵 DETECT-ONLY. Exploit is well-documented (vsh's + * exploit-cve-2021-3493) and would port in ~80 lines; follow-up + * commit lands it. + * + * Affected: + * - Ubuntu 14.04 / 16.04 / 18.04 / 20.04 / 20.10 / 21.04 with + * Ubuntu-modified overlayfs and unprivileged_userns_clone=1. + * - Upstream kernels did NOT have the userns-mount path enabled + * pre-5.11, so non-Ubuntu kernels were largely immune by + * accident. + * - Fixed in Ubuntu by USN-4915-1 (April 2021) — kernel package + * updates per release. + * + * Detect logic (necessary-but-not-sufficient): + * 1. /etc/os-release distro == ubuntu (the bug is Ubuntu-specific) + * 2. Kernel version is below the Ubuntu fix threshold for that + * release. We don't track per-release Ubuntu kernel version + * maps in IAMROOT yet; report VULNERABLE if Ubuntu kernel + * AND uname() version < 5.11 AND unprivileged_userns_clone=1 + * AND overlayfs mountable from userns (active probe). + */ + +#include "iamroot_modules.h" +#include "../../core/registry.h" +#include "../../core/kernel_range.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool is_ubuntu(void) +{ + FILE *f = fopen("/etc/os-release", "r"); + if (!f) return false; + char line[256]; + bool found = false; + while (fgets(line, sizeof line, f)) { + if (strstr(line, "ID=ubuntu") || strstr(line, "ID_LIKE=ubuntu")) { + found = true; + break; + } + } + fclose(f); + return found; +} + +static int read_sysctl_int(const char *path) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) return -1; + char buf[16] = {0}; + ssize_t n = read(fd, buf, sizeof buf - 1); + close(fd); + if (n <= 0) return -1; + return atoi(buf); +} + +/* Active probe: actually try to mount overlayfs inside a user + * namespace. The probe is contained: forks a child that enters a + * userns and attempts the mount; child exits regardless. Parent + * never enters the namespace. + * + * Returns 1 if mount succeeded (vulnerable behavior), 0 if denied + * (AppArmor / SELinux / kernel patch), -1 on probe machinery error. */ +static int overlayfs_mount_probe(void) +{ + pid_t pid = fork(); + if (pid < 0) return -1; + if (pid == 0) { + if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0) _exit(2); + + /* Build a minimal overlayfs in /tmp inside the child. */ + char base[] = "/tmp/iamroot-ovl-XXXXXX"; + if (!mkdtemp(base)) _exit(3); + + char low[512], up[512], wd[512], mp[512]; + snprintf(low, sizeof low, "%s/lower", base); + snprintf(up, sizeof up, "%s/upper", base); + snprintf(wd, sizeof wd, "%s/work", base); + snprintf(mp, sizeof mp, "%s/merged", base); + if (mkdir(low, 0755) < 0 || mkdir(up, 0755) < 0 + || mkdir(wd, 0755) < 0 || mkdir(mp, 0755) < 0) _exit(4); + + char opts[2048]; + snprintf(opts, sizeof opts, "lowerdir=%s,upperdir=%s,workdir=%s", + low, up, wd); + int rc = mount("overlay", mp, "overlay", 0, opts); + if (rc < 0) _exit(5); /* mount denied — likely patched/blocked */ + umount(mp); /* clean up if we got here */ + _exit(0); + } + int status; + waitpid(pid, &status, 0); + if (!WIFEXITED(status)) return -1; + return WEXITSTATUS(status) == 0 ? 1 : 0; +} + +static iamroot_result_t overlayfs_detect(const struct iamroot_ctx *ctx) +{ + struct kernel_version v; + if (!kernel_version_current(&v)) { + fprintf(stderr, "[!] overlayfs: could not parse kernel version\n"); + return IAMROOT_TEST_ERROR; + } + + /* Ubuntu-specific bug. Non-Ubuntu kernels are largely immune + * because upstream didn't enable the userns-mount path until + * 5.11. Bail early for non-Ubuntu. */ + if (!is_ubuntu()) { + if (!ctx->json) { + fprintf(stderr, "[+] overlayfs: not Ubuntu — bug is Ubuntu-specific\n"); + } + return IAMROOT_OK; + } + + /* unprivileged_userns_clone gate */ + int uuc = read_sysctl_int("/proc/sys/kernel/unprivileged_userns_clone"); + if (uuc == 0) { + if (!ctx->json) { + fprintf(stderr, "[+] overlayfs: unprivileged_userns_clone=0 → " + "unprivileged exploit unreachable\n"); + } + return IAMROOT_PRECOND_FAIL; + } + + if (!ctx->json) { + fprintf(stderr, "[i] overlayfs: Ubuntu kernel %s, unprivileged_userns_clone=%d\n", + v.release, uuc); + } + + /* Active probe: try the mount. Most reliable detect since Ubuntu + * kernel package versioning is opaque to us. */ + if (ctx->active_probe) { + int probe = overlayfs_mount_probe(); + if (probe == 1) { + if (!ctx->json) { + fprintf(stderr, "[!] overlayfs: ACTIVE PROBE CONFIRMED — " + "userns overlayfs mount succeeded → VULNERABLE\n"); + } + return IAMROOT_VULNERABLE; + } + if (probe == 0) { + if (!ctx->json) { + fprintf(stderr, "[+] overlayfs: active probe denied mount — " + "likely patched / AppArmor block\n"); + } + return IAMROOT_OK; + } + if (!ctx->json) { + fprintf(stderr, "[?] overlayfs: active probe machinery failed\n"); + } + } + + /* Without active probe, fall back to version inference. Upstream + * 5.11 enabled userns-mount for overlayfs; Ubuntu had it earlier. + * Ubuntu fix is per-release-specific; conservatively report + * VULNERABLE if version < 5.13 (covers most affected Ubuntu LTS), + * and recommend --active for confirmation. */ + if (v.major < 5 || (v.major == 5 && v.minor < 13)) { + if (!ctx->json) { + fprintf(stderr, "[!] overlayfs: Ubuntu kernel %s in vulnerable range — " + "re-run with --active to confirm\n", v.release); + } + return IAMROOT_VULNERABLE; + } + if (!ctx->json) { + fprintf(stderr, "[+] overlayfs: Ubuntu kernel %s is newer than typical " + "affected range\n", v.release); + fprintf(stderr, "[i] overlayfs: re-run with --active to empirically test\n"); + } + return IAMROOT_OK; +} + +static iamroot_result_t overlayfs_exploit(const struct iamroot_ctx *ctx) +{ + (void)ctx; + fprintf(stderr, + "[-] overlayfs: exploit not yet implemented in IAMROOT.\n" + " Status: 🔵 DETECT-ONLY (see CVES.md).\n" + " Reference: vsh's exploit-cve-2021-3493. The exploit mounts\n" + " overlayfs inside a userns, places a /bin/sh-like binary in\n" + " the upper layer with cap_setuid+ep set, then re-executes it\n" + " outside the namespace to drop a root shell.\n"); + return IAMROOT_PRECOND_FAIL; +} + +/* ----- Embedded detection rules ----- */ + +static const char overlayfs_auditd[] = + "# overlayfs userns LPE (CVE-2021-3493) — auditd detection rules\n" + "# Flag userns-clone followed by overlayfs mount + setcap-like xattr.\n" + "-a always,exit -F arch=b64 -S mount -F a2=overlay -k iamroot-overlayfs\n" + "-a always,exit -F arch=b32 -S mount -F a2=overlay -k iamroot-overlayfs\n" + "# Watch for security.capability xattr writes (the post-mount step)\n" + "-a always,exit -F arch=b64 -S setxattr,fsetxattr,lsetxattr -k iamroot-overlayfs-cap\n"; + +const struct iamroot_module overlayfs_module = { + .name = "overlayfs", + .cve = "CVE-2021-3493", + .summary = "Ubuntu userns-overlayfs file-capability injection → host root", + .family = "overlayfs", + .kernel_range = "Ubuntu-specific; kernels w/ userns-overlayfs-mount before per-release fix (USN-4915-1)", + .detect = overlayfs_detect, + .exploit = overlayfs_exploit, + .mitigate = NULL, + .cleanup = NULL, + .detect_auditd = overlayfs_auditd, + .detect_sigma = NULL, + .detect_yara = NULL, + .detect_falco = NULL, +}; + +void iamroot_register_overlayfs(void) +{ + iamroot_register(&overlayfs_module); +} diff --git a/modules/overlayfs_cve_2021_3493/iamroot_modules.h b/modules/overlayfs_cve_2021_3493/iamroot_modules.h new file mode 100644 index 0000000..eaba0a7 --- /dev/null +++ b/modules/overlayfs_cve_2021_3493/iamroot_modules.h @@ -0,0 +1,12 @@ +/* + * overlayfs_cve_2021_3493 — IAMROOT module registry hook + */ + +#ifndef OVERLAYFS_IAMROOT_MODULES_H +#define OVERLAYFS_IAMROOT_MODULES_H + +#include "../../core/module.h" + +extern const struct iamroot_module overlayfs_module; + +#endif