From ada56b0db3c7e2931a599afa6d10e3454a2c6821 Mon Sep 17 00:00:00 2001 From: KaraZajac Date: Mon, 8 Jun 2026 11:07:27 -0400 Subject: [PATCH] modules: add cifswitch (CVE-2026-46243, Asim Manizada's CIFSwitch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CIFSwitch is the newest kernel-7-era LPE not already in the corpus: a ~19-year-old logic flaw in fs/smb/client/cifs_spnego.c where the cifs.spnego request-key type accepts key descriptions created by userspace (add_key(2)/request_key(2)) without verifying the request came from the in-kernel CIFS client. The description's authority-bearing fields (pid/uid/creduid/upcall_target) are trusted by the root cifs.upcall helper; with user+mount namespace tricks an unprivileged user coerces cifs.upcall into loading an attacker NSS module as root. Fixed upstream by 3da1fdf4efbc (merged 7.1-rc5); CWE-20; not in CISA KEV. Takes the corpus to 42 modules / 37 CVEs. ๐ŸŸก honest port โ€” full chain not VM-verified. detect() gates on the kernel version (Debian backports 5.10.257/6.1.174/6.12.90/7.0.10) AND on the cifs userspace path (cifs.upcall / cifs.spnego request-key rule), so a vulnerable kernel without cifs-utils is PRECOND_FAIL not a false positive (override via SKELETONKEY_CIFS_ASSUME_PRESENT=1/0). exploit() fires only the non-destructive add_key(2) cifs.spnego probe (no upcall, loads nothing, revoked immediately) and returns EXPLOIT_FAIL without a euid-0 witness โ€” the namespace+NSS root-pop is not bundled until VM-verified. --mitigate blocklists the cifs module; --cleanup reverts. Wired everywhere: registry, Makefile, safety rank (86), 6 detect() test rows (env-driven precondition override), CVE_METADATA.json + cve_metadata.c + KEV_CROSSREF.md (sorted insert, CWE-20/T1068/not-KEV), README + CVES.md + website counts (42/37) and a yellow module pill, RELEASE_NOTES v0.9.10, verify-vm target (sweep pending). Credit: Asim Manizada. Version 0.9.10. --- CVES.md | 5 +- Makefile | 7 +- README.md | 28 +- core/cve_metadata.c | 8 + core/registry.h | 1 + core/registry_all.c | 1 + docs/CVE_METADATA.json | 9 + docs/KEV_CROSSREF.md | 3 +- docs/RELEASE_NOTES.md | 35 ++ docs/index.html | 21 +- modules/cifswitch_cve_2026_46243/MODULE.md | 60 +++ modules/cifswitch_cve_2026_46243/NOTICE.md | 69 +++ .../skeletonkey_modules.c | 419 ++++++++++++++++++ .../skeletonkey_modules.h | 12 + skeletonkey.c | 3 +- tests/test_detect.c | 39 ++ tools/verify-vm/targets.yaml | 10 + 17 files changed, 703 insertions(+), 27 deletions(-) create mode 100644 modules/cifswitch_cve_2026_46243/MODULE.md create mode 100644 modules/cifswitch_cve_2026_46243/NOTICE.md create mode 100644 modules/cifswitch_cve_2026_46243/skeletonkey_modules.c create mode 100644 modules/cifswitch_cve_2026_46243/skeletonkey_modules.h diff --git a/CVES.md b/CVES.md index bdca178..5dcc78c 100644 --- a/CVES.md +++ b/CVES.md @@ -23,14 +23,14 @@ Status legend: - ๐Ÿ”ด **DEPRECATED** โ€” fully patched everywhere relevant; kept for historical reference only -**Counts:** 41 modules total covering 36 CVEs; **28 of 36 CVEs +**Counts:** 42 modules total covering 37 CVEs; **28 of 37 CVEs verified end-to-end in real VMs** via `tools/verify-vm/`. ๐Ÿ”ต 0 ยท โšช 0 planned-with-stub ยท ๐Ÿ”ด 0. (One โšช row below โ€” CVE-2026-31402 โ€” is a *candidate* with no module, not counted as a module.) > **Note on unverified rows:** `vmwgfx` / `dirty_cow` / > `mutagen_astronomy` / `pintheft` / `vsock_uaf` / `fragnesia` / -> `ptrace_pidfd` / `sudo_host` are blocked by their target environment (VMware-only, +> `ptrace_pidfd` / `sudo_host` / `cifswitch` are blocked by their target environment (VMware-only, > kernel < 4.4, mainline panic, kmod not autoloaded, t64-transition > libs) or are brand-new this cycle, not by missing code. See > [`tools/verify-vm/targets.yaml`](tools/verify-vm/targets.yaml). @@ -95,6 +95,7 @@ root on a host can upstream their kernel's offsets via PR. | CVE-2026-41651 | Pack2TheRoot โ€” PackageKit `InstallFiles` TOCTOU | LPE (userspace D-Bus daemon โ†’ `.deb` postinst as root) | PackageKit 1.3.5 (commit `76cfb675`, 2026-04-22) | `pack2theroot` | ๐ŸŸก | **Ported from the public Vozec PoC, not yet VM-verified.** Two back-to-back `InstallFiles` D-Bus calls โ€” first `SIMULATE` (polkit bypass + queues a GLib idle), then immediately `NONE` + malicious `.deb` (overwrites the cached flags before the idle fires). GLib priority ordering makes the overwrite deterministic, not a race. Disclosure by **Deutsche Telekom security**. Affects PackageKit 1.0.2 โ†’ 1.3.4 โ€” default-enabled on Ubuntu Desktop, Debian, Fedora, Rocky/RHEL via Cockpit. `detect()` reads `VersionMajor/Minor/Micro` over D-Bus โ†’ high-confidence verdict (vs. precondition-only for dirtydecrypt/fragnesia). Debian-family only (PoC's built-in `.deb` builder). Needs `libglib2.0-dev` at build time; Makefile autodetects via `pkg-config gio-2.0` and falls through to a stub when absent. | | CVE-2026-46333 | ptrace `__ptrace_may_access` dumpable-race โ†’ `pidfd_getfd` credential-fd theft | LPE (steal a root-opened fd / authenticated channel from a process dropping privileges) | mainline 2026-05-14 (Debian backports 5.10.251 / 6.1.172 / 6.12.88 / 7.0.7) | `ptrace_pidfd` | ๐ŸŸก | **Qualys TRU disclosure (2026-05-20), exploit not yet VM-verified.** The `__ptrace_may_access` logic flaw leaves a process *dropping* privileges briefly reachable past its `dumpable` boundary; `pidfd_getfd(2)` rides that window. detect() is version-pinned with a predates-gate at `pidfd_getfd`'s 5.6 introduction. exploit() spawns a setuid victim (chage / pkexec / ssh-keysign), `pidfd_open()`s it and sweeps `pidfd_getfd()` across its descriptor table during the credential-drop window, reporting any uid-0-owned fd captured from a non-root context โ€” honest `EXPLOIT_FAIL` without a euid-0 witness; the target-specific full root-pop is not bundled until VM-verified. Arch-agnostic (descriptor theft, no shellcode). `--mitigate` sets `kernel.yama.ptrace_scope=2`; `--cleanup` reverts it. Credit: Qualys TRU. | | CVE-2025-32462 | sudo `-h`/`--host` policy bypass (Stratascale) | LPE (userspace; abuse a host-restricted sudoers rule for local root) | sudo 1.9.17p1 (2025-06-30) | `sudo_host` | ๐ŸŸข | **Stratascale CRU disclosure (Rich Mirch); sibling of `sudo_chwoot`.** sudo's `-h`/`--host` option โ€” meant only to pair with `-l` โ€” was honored when running a command, so a sudoers rule scoped to a host other than the current machine (and not ALL) is usable via `sudo -h `. Affects sudo 1.8.8 โ†’ 1.9.17p0; fixed 1.9.17p1. CWE-863, CVSS 8.8; not in KEV. detect() version-gates; exploit() finds an abusable host-restricted rule in readable sudoers (or `SKELETONKEY_SUDO_HOST`), witnesses with `sudo -n -h id -u`, and pops a root shell only on a uid-0 witness โ€” never fabricates root. Structural (no offsets/race); arch-agnostic. Most relevant to fleet-wide / LDAP / SSSD sudoers. Credit: Rich Mirch / Stratascale. | +| CVE-2026-46243 | CIFSwitch โ€” `cifs.spnego` key type trusts userspace-forged authority fields | LPE (coerce root `cifs.upcall` into loading an attacker NSS module) | fixed 5.10.257 / 6.1.174 / 6.12.90 / 7.0.10 (Debian backports of `3da1fdf4efbc`, mainline 7.1-rc5) | `cifswitch` | ๐ŸŸก | **Asim Manizada disclosure (2026-05-28), public PoC; exploit full-chain not yet VM-verified.** ~19-year-old logic flaw in `fs/smb/client/cifs_spnego.c`: the `cifs.spnego` key description carries authority-bearing fields (`pid`/`uid`/`creduid`/`upcall_target`) that root `cifs.upcall` trusts as kernel-originating, but userspace can create such keys via `add_key(2)`/`request_key(2)`. With user+mount namespace tricks, an unprivileged user makes `cifs.upcall` load a malicious NSS `.so` as root. CWE-20; not in KEV. Preconditions: `cifs` module + `cifs-utils` (`cifs.upcall`) + `cifs.spnego` request-key rule (override the probe via `SKELETONKEY_CIFS_ASSUME_PRESENT=1/0`). detect() version-gates and PRECOND_FAILs when the cifs userspace path is absent. exploit() fires only the non-destructive primitive โ€” `add_key(2)` of a forged-but-benign `cifs.spnego` key (no upcall, loads nothing), revoked immediately โ€” and returns honest `EXPLOIT_FAIL` without a euid-0 witness; the namespace+NSS root-pop is not bundled until VM-verified. Structural; arch-agnostic. `--mitigate` blocklists the `cifs` module; `--cleanup` reverts. Credit: Asim Manizada. | ## Operations supported per module diff --git a/Makefile b/Makefile index a4c7154..60af826 100644 --- a/Makefile +++ b/Makefile @@ -232,6 +232,11 @@ SUH_DIR := modules/sudo_host_cve_2025_32462 SUH_SRCS := $(SUH_DIR)/skeletonkey_modules.c SUH_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(SUH_SRCS)) +# CVE-2026-46243 CIFSwitch โ€” cifs.spnego userspace-forged key trust (Asim Manizada) +CIW_DIR := modules/cifswitch_cve_2026_46243 +CIW_SRCS := $(CIW_DIR)/skeletonkey_modules.c +CIW_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(CIW_SRCS)) + # Top-level dispatcher TOP_OBJ := $(BUILD)/skeletonkey.o @@ -245,7 +250,7 @@ MODULE_OBJS := $(CFF_OBJS) $(DP_OBJS) $(EB_OBJS) $(PK_OBJS) $(NFT_OBJS) \ $(DDC_OBJS) $(FGN_OBJS) $(P2TR_OBJS) \ $(SCHW_OBJS) $(UDB_OBJS) $(PTH_OBJS) \ $(MUT_OBJS) $(SRN_OBJS) $(TIO_OBJS) $(VSK_OBJS) $(PIP_OBJS) \ - $(PPF_OBJS) $(SUH_OBJS) + $(PPF_OBJS) $(SUH_OBJS) $(CIW_OBJS) ALL_OBJS := $(TOP_OBJ) $(CORE_OBJS) $(REGISTRY_ALL_OBJ) $(MODULE_OBJS) diff --git a/README.md b/README.md index d60091e..b4563b3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Modules](https://img.shields.io/badge/CVEs-28%20VM--verified%20%2F%2036-brightgreen.svg)](docs/VERIFICATIONS.jsonl) [![Platform: Linux](https://img.shields.io/badge/platform-linux-lightgrey.svg)](#) -> **One curated binary. 41 Linux LPE modules covering 36 CVEs from 2016 โ†’ 2026. +> **One curated binary. 42 Linux LPE modules covering 37 CVEs from 2016 โ†’ 2026. > Every year 2016 โ†’ 2026 covered. 28 confirmed end-to-end against real Linux > VMs via `tools/verify-vm/`. Detection rules in the box. One command picks > the safest one and runs it.** @@ -45,8 +45,8 @@ for every CVE in the bundle โ€” same project for red and blue teams. ## Corpus at a glance -**41 modules covering 36 distinct CVEs** across the 2016 โ†’ 2026 LPE -timeline. **28 of the 36 CVEs have been empirically verified** in real +**42 modules covering 37 distinct CVEs** across the 2016 โ†’ 2026 LPE +timeline. **28 of the 37 CVEs have been empirically verified** in real Linux VMs via `tools/verify-vm/`; the 8 still-pending entries are blocked by their target environment (legacy hypervisor, EOL kernel, or the t64-transition libc rollout) or are brand-new additions awaiting a @@ -68,7 +68,7 @@ af_packet ยท af_packet2 ยท af_unix_gc ยท cls_route4 ยท fuse_legacy ยท nf_tables ยท nft_set_uaf ยท nft_fwd_dup ยท nft_payload ยท netfilter_xtcompat ยท stackrot ยท sudo_samedit ยท sequoia ยท vmwgfx -### Empirical verification (28 of 36 CVEs) +### Empirical verification (28 of 37 CVEs) Records in [`docs/VERIFICATIONS.jsonl`](docs/VERIFICATIONS.jsonl) prove each verdict against a known-target VM. Coverage: @@ -137,7 +137,7 @@ uid=1000(kara) gid=1000(kara) groups=1000(kara) $ skeletonkey --auto --i-know [*] auto: host=demo distro=ubuntu/24.04 kernel=5.15.0-56-generic arch=x86_64 [*] auto: active probes enabled โ€” brief /tmp file touches and fork-isolated namespace probes -[*] auto: scanning 41 modules for vulnerabilities... +[*] auto: scanning 42 modules for vulnerabilities... [+] auto: dirty_pipe VULNERABLE (safety rank 90) [+] auto: cgroup_release_agent VULNERABLE (safety rank 98) [+] auto: pwnkit VULNERABLE (safety rank 100) @@ -206,10 +206,14 @@ also compile (modules with Linux-only headers stub out gracefully). ## Status -**v0.9.9 cut 2026-06-08.** 41 modules across 36 CVEs โ€” **every -year 2016 โ†’ 2026 now covered**. Newest: `ptrace_pidfd` (CVE-2026-46333, -Qualys's `__ptrace_may_access` / `pidfd_getfd` credential-steal) and -`sudo_host` (CVE-2025-32462, Stratascale's sudo `--host` policy bypass). +**v0.9.10 cut 2026-06-08.** 42 modules across 37 CVEs โ€” **every +year 2016 โ†’ 2026 now covered**. Newest: `cifswitch` (CVE-2026-46243, +Asim Manizada's "CIFSwitch" โ€” the `cifs.spnego` key type trusts +userspace-forged authority fields, coercing the root `cifs.upcall` helper +into loading an attacker NSS module as root), `ptrace_pidfd` +(CVE-2026-46333, Qualys's `__ptrace_may_access` / `pidfd_getfd` +credential-steal), and `sudo_host` (CVE-2025-32462, Stratascale's sudo +`--host` policy bypass). v0.9.0 added 5 gap-fillers (`mutagen_astronomy` / `sudo_runas_neg1` / `tioscpgrp` / `vsock_uaf` / `nft_pipapo`); v0.8.0 added 3 (`sudo_chwoot` / `udisks_libblockdev` / @@ -239,19 +243,19 @@ Reliability + accuracy work in v0.7.x: trace, OPSEC footprint, detection-rule coverage, verified-on records. Paste-into-ticket ready. - **CVE metadata pipeline** (`tools/refresh-cve-metadata.py`) โ€” fetches - CISA KEV catalog + NVD CWE; 13 of 36 modules cover KEV-listed CVEs. + CISA KEV catalog + NVD CWE; 13 of 37 modules cover KEV-listed CVEs. - **151 detection rules** across auditd / sigma / yara / falco; one command exports the corpus to your SIEM. - `--auto` upgrades: per-detect 15s timeout, fork-isolated detect + exploit, structured verdict table, scan summary, `--dry-run`. -Not yet verified (8 of 36 CVEs): `vmwgfx` (VMware-guest only), +Not yet verified (9 of 37 CVEs): `vmwgfx` (VMware-guest only), `dirty_cow` (needs โ‰ค 4.4 kernel), `mutagen_astronomy` (mainline 4.14.70 panics on Ubuntu 18.04 rootfs โ€” needs CentOS 6 / Debian 7), `pintheft` + `vsock_uaf` (kernel modules not autoloaded on common Vagrant boxes), `fragnesia` (mainline 7.0.5 .debs need t64-transition libs from Ubuntu 24.04+ / Debian 13+), `ptrace_pidfd` + `sudo_host` -(brand-new this cycle, sweep pending). Rationale in ++ `cifswitch` (brand-new this cycle, sweep pending). Rationale in [`tools/verify-vm/targets.yaml`](tools/verify-vm/targets.yaml). See [`ROADMAP.md`](ROADMAP.md) for the next planned modules and diff --git a/core/cve_metadata.c b/core/cve_metadata.c index 045c908..c60f037 100644 --- a/core/cve_metadata.c +++ b/core/cve_metadata.c @@ -284,6 +284,14 @@ const struct cve_metadata cve_metadata_table[] = { .in_kev = false, .kev_date_added = "", }, + { + .cve = "CVE-2026-46243", + .cwe = "CWE-20", + .attack_technique = "T1068", + .attack_subtechnique = NULL, + .in_kev = false, + .kev_date_added = "", + }, { .cve = "CVE-2026-46300", .cwe = "CWE-787", diff --git a/core/registry.h b/core/registry.h index 51858b9..d6590fe 100644 --- a/core/registry.h +++ b/core/registry.h @@ -57,6 +57,7 @@ void skeletonkey_register_vsock_uaf(void); void skeletonkey_register_nft_pipapo(void); void skeletonkey_register_ptrace_pidfd(void); void skeletonkey_register_sudo_host(void); +void skeletonkey_register_cifswitch(void); /* Call every skeletonkey_register_() above in canonical order. * Single source of truth so the main binary and the test binary stay diff --git a/core/registry_all.c b/core/registry_all.c index 66502e3..8de1f1a 100644 --- a/core/registry_all.c +++ b/core/registry_all.c @@ -53,4 +53,5 @@ void skeletonkey_register_all_modules(void) skeletonkey_register_nft_pipapo(); skeletonkey_register_ptrace_pidfd(); skeletonkey_register_sudo_host(); + skeletonkey_register_cifswitch(); } diff --git a/docs/CVE_METADATA.json b/docs/CVE_METADATA.json index 44b1aaf..ec16d84 100644 --- a/docs/CVE_METADATA.json +++ b/docs/CVE_METADATA.json @@ -305,6 +305,15 @@ "in_kev": false, "kev_date_added": "" }, + { + "cve": "CVE-2026-46243", + "module_dir": "cifswitch_cve_2026_46243", + "cwe": "CWE-20", + "attack_technique": "T1068", + "attack_subtechnique": null, + "in_kev": false, + "kev_date_added": "" + }, { "cve": "CVE-2026-46300", "module_dir": "fragnesia_cve_2026_46300", diff --git a/docs/KEV_CROSSREF.md b/docs/KEV_CROSSREF.md index 4dc688c..0ce7a07 100644 --- a/docs/KEV_CROSSREF.md +++ b/docs/KEV_CROSSREF.md @@ -4,7 +4,7 @@ Which SKELETONKEY modules cover CVEs that CISA has observed exploited in the wild per the Known Exploited Vulnerabilities catalog. Refreshed via `tools/refresh-cve-metadata.py`. -**13 of 36 modules cover KEV-listed CVEs.** +**13 of 37 modules cover KEV-listed CVEs.** ## In KEV (prioritize patching) @@ -53,5 +53,6 @@ and are technically reachable. "Not in KEV" is not the same as | CVE-2026-31635 | CWE-130 | `dirtydecrypt_cve_2026_31635` | | CVE-2026-41651 | CWE-367 | `pack2theroot_cve_2026_41651` | | CVE-2026-43494 | ? | `pintheft_cve_2026_43494` | +| CVE-2026-46243 | CWE-20 | `cifswitch_cve_2026_46243` | | CVE-2026-46300 | CWE-787 | `fragnesia_cve_2026_46300` | | CVE-2026-46333 | CWE-269 | `ptrace_pidfd_cve_2026_46333` | diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index be2dbc7..2f2e393 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -1,3 +1,38 @@ +## SKELETONKEY v0.9.10 โ€” new LPE module: cifswitch (CVE-2026-46243) + +Adds **`cifswitch` โ€” CVE-2026-46243 "CIFSwitch"** (Asim Manizada, +2026-05-28), taking the corpus to **42 modules / 37 CVEs**. The newest +kernel-7-era LPE not already covered: a ~19-year-old logic flaw in +`fs/smb/client/cifs_spnego.c` where the `cifs.spnego` request-key type +accepts key descriptions created by *userspace* (`add_key(2)` / +`request_key(2)`) without verifying the request came from the in-kernel +CIFS client. The description carries authority-bearing fields +(`pid`/`uid`/`creduid`/`upcall_target`) that the root `cifs.upcall` +helper trusts as kernel-originating; combined with user+mount namespace +tricks, an unprivileged user coerces `cifs.upcall` into loading an +attacker NSS module as root. Fixed upstream by `3da1fdf4efbc` (merged +7.1-rc5); NVD class CWE-20; not in CISA KEV. + +๐ŸŸก **Honest port โ€” full chain not VM-verified.** `detect()` gates on the +kernel version (Debian backports 5.10.257 / 6.1.174 / 6.12.90 / 7.0.10) +**and** on the presence of the vulnerable userspace path โ€” a vulnerable +kernel without `cifs-utils` reports `PRECOND_FAIL`, not a false +`VULNERABLE` (override the probe with `SKELETONKEY_CIFS_ASSUME_PRESENT=1` +/`0`). `exploit()` fires only the non-destructive primitive โ€” `add_key(2)` +of a forged-but-benign `cifs.spnego` key, which does **not** invoke +`cifs.upcall` and loads nothing, revoked immediately โ€” and treats a clean +accept as the empirical witness that userspace can forge the +authority-bearing key type. It then stops: the namespace-switch + +malicious-NSS-load root-pop is target/config-specific and is not bundled +until VM-verified, so it returns honest `EXPLOIT_FAIL` without a euid-0 +witness (never fabricates root). `--mitigate` blocklists the `cifs` +module (`/etc/modprobe.d/skeletonkey-disable-cifs.conf`); `--cleanup` +reverts. Structural, arch-agnostic (keyring + namespace logic, no +shellcode). Ships auditd + sigma + falco rules, MITRE ATT&CK T1068 + +CWE-20 metadata, six new `detect()` unit-test rows, and credits Asim +Manizada in `NOTICE.md`. Not yet VM-verified (sweep pending in +`tools/verify-vm/targets.yaml`), so the verified count stays 28 of 37. + ## SKELETONKEY v0.9.9 โ€” install.sh needs no root; CVE-2022-0492 KEV drift Two maintenance fixes, no new modules. diff --git a/docs/index.html b/docs/index.html index e4b973d..82dd95b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,9 +4,9 @@ SKELETONKEY โ€” Linux LPE corpus, VM-verified, SOC-ready detection - + - + @@ -56,13 +56,13 @@
- v0.9.9 โ€” released 2026-06-08 + v0.9.10 โ€” released 2026-06-08

SKELETONKEY

- One binary. 41 Linux LPE modules covering 36 CVEs โ€” + One binary. 42 Linux LPE modules covering 37 CVEs โ€” every year 2016 โ†’ 2026. 28 of 34 confirmed against real Linux kernels in VMs. SOC-ready detection rules in four SIEM formats. MITRE ATT&CK + CWE + CISA KEV annotated. @@ -227,7 +227,7 @@ uid=0(root) gid=0(root)

โ˜…

CISA KEV prioritized

- 13 of 36 CVEs in the corpus are in CISA's Known Exploited + 13 of 37 CVEs in the corpus are in CISA's Known Exploited Vulnerabilities catalog โ€” actively exploited in the wild. Refreshed on demand via tools/refresh-cve-metadata.py.

@@ -294,7 +294,7 @@ uid=0(root) gid=0(root) tools/verify-vm/ spins up known-vulnerable kernels (stock distro + mainline from kernel.ubuntu.com), runs --explain --active per module, and records the - verdict. 28 of 36 CVEs confirmed against + verdict. 28 of 37 CVEs confirmed against real Linux across Ubuntu 18.04 / 20.04 / 22.04 + Debian 11 / 12 + mainline 5.4.0-26 / 5.15.5 / 6.1.10 / 6.19.7. Records baked into the binary; --list shows โœ“ per module. @@ -309,7 +309,7 @@ uid=0(root) gid=0(root)
-

36 CVEs across 10 years. โ˜… = actively exploited (CISA KEV).

+

37 CVEs across 10 years. โ˜… = actively exploited (CISA KEV).

@@ -356,6 +356,7 @@ uid=0(root) gid=0(root) sequoia vmwgfx ptrace_pidfd + cifswitch

@@ -416,7 +417,7 @@ uid=0(root) gid=0(root)

๐ŸŽ“

Researchers / CTF

- 36 CVEs, 10-year span, each with the original PoC author + 37 CVEs, 10-year span, each with the original PoC author credited and the kernel-range citation auditable. --explain shows the reasoning chain; detection rules let you practice both sides. Source is the documentation. @@ -513,7 +514,7 @@ uid=0(root) gid=0(root)

shipped
    -
  • 28 of 36 CVEs empirically verified in real Linux VMs
  • +
  • 28 of 37 CVEs empirically verified in real Linux VMs
  • kernel.ubuntu.com/mainline/ kernel fetch path โ€” unblocks pin-not-in-apt targets
  • Per-module verified_on[] table baked into the binary
  • --explain mode โ€” one-page operator briefing per CVE
  • @@ -600,7 +601,7 @@ uid=0(root) gid=0(root) who found the bugs.

diff --git a/modules/cifswitch_cve_2026_46243/MODULE.md b/modules/cifswitch_cve_2026_46243/MODULE.md new file mode 100644 index 0000000..3303bed --- /dev/null +++ b/modules/cifswitch_cve_2026_46243/MODULE.md @@ -0,0 +1,60 @@ +# cifswitch โ€” CVE-2026-46243 ("CIFSwitch") + +The kernel's `cifs.spnego` request-key type trusts userspace-forged +authority fields, letting the root `cifs.upcall` helper be coerced into +loading an attacker NSS module as root. + +## The bug + +`fs/smb/client/cifs_spnego.c` registers the `cifs.spnego` key type so the +kernel CIFS client can ask the root-privileged `cifs.upcall` helper to +perform a SPNEGO/Kerberos exchange. The key *description* carries +authority-bearing fields โ€” `pid`, `uid`, `creduid`, `upcall_target` โ€” +that `cifs.upcall` reads as trusted, kernel-originating inputs. + +The flaw: the kernel never verified the request actually came from the +in-kernel CIFS client. Userspace can create keys of this type directly +through `add_key(2)` / `request_key(2)`, supplying all those fields. By +forging a description and manipulating user + mount namespaces, an +unprivileged user makes `cifs.upcall` trust attacker-controlled state and +load a malicious NSS shared library as root โ†’ root code execution. + +## Affected range + +| | | +|---|---| +| Flaw age | ~19 years (predates key-type origin checks) | +| Fixed upstream | commit `3da1fdf4efbc`, merged 7.1-rc5 | +| Debian backports | 5.10.257 ยท 6.1.174 ยท 6.12.90 ยท 7.0.10 | +| NVD class | CWE-20 (Improper Input Validation) | +| CISA KEV | no (as of disclosure) | + +Branches Debian does not ship (5.15 / 6.6 / 6.8 / 6.11 โ€ฆ) are reported on +the version-only verdict; confirm empirically. + +## Trigger / detection + +`detect()` returns `OK` for patched kernels, `PRECOND_FAIL` for a +vulnerable kernel where `cifs.upcall` / the `cifs.spnego` request-key rule +isn't installed (cifs-utils absent โ†’ unreachable), and `VULNERABLE` when +both the version and the userspace path line up. The precondition probe +can be overridden with `SKELETONKEY_CIFS_ASSUME_PRESENT=1` (force present) +or `0` (force absent). + +`exploit()` fires the non-destructive primitive: `add_key(2)` of a +forged-but-benign `cifs.spnego` key (no upcall, loads nothing), revoked +immediately. A clean accept is the witness that userspace can forge the +authority-bearing key type. The full root-pop (namespace switch + +malicious NSS load) is **not** bundled until VM-verified โ€” honest +`EXPLOIT_FAIL` without a euid-0 witness. + +## Fix / mitigation + +Upgrade the kernel. As a runtime stopgap, blocklist the `cifs` module โ€” +`--mitigate` writes `/etc/modprobe.d/skeletonkey-disable-cifs.conf` +(needs root) and `--cleanup` removes it. Already-loaded `cifs` persists +until unmount + `rmmod cifs` or reboot. + +## Credit + +Asim Manizada (2026-05-28). See `NOTICE.md`. diff --git a/modules/cifswitch_cve_2026_46243/NOTICE.md b/modules/cifswitch_cve_2026_46243/NOTICE.md new file mode 100644 index 0000000..c60e115 --- /dev/null +++ b/modules/cifswitch_cve_2026_46243/NOTICE.md @@ -0,0 +1,69 @@ +# NOTICE โ€” cifswitch (CVE-2026-46243, "CIFSwitch") + +## Vulnerability + +**CVE-2026-46243 "CIFSwitch"** โ€” the Linux kernel's `cifs.spnego` +request-key type (`fs/smb/client/cifs_spnego.c`) accepts key descriptions +created by **userspace** (via `add_key(2)` / `request_key(2)`) without +verifying that the request originated from the in-kernel CIFS client. The +key description carries authority-bearing fields โ€” `pid`, `uid`, +`creduid`, `upcall_target` โ€” that the root-privileged `cifs.upcall` +helper treats as trusted, kernel-originating inputs. An unprivileged +local user forges such a description and, combined with user + mount +namespace manipulation, coerces `cifs.upcall` into loading an +attacker-controlled NSS shared library as root โ†’ local privilege +escalation to root. + +It is a **~19-year-old** logic flaw โ€” the cifs spnego upcall predates the +key-type origin checks added to the keyrings subsystem later. NVD class: +**CWE-20** (Improper Input Validation). Not in CISA KEV (as of disclosure). + +**Preconditions:** the `cifs` kernel module available, `cifs-utils` +installed (so `cifs.upcall` is present), and the `cifs.spnego` +request-key rule active. Default-vulnerable distributions reported +include Linux Mint, CentOS Stream 9, Rocky Linux 9, AlmaLinux 9, Kali +Linux, SLES 15 SP7, and Red Hat Enterprise Linux 6โ€“10. + +## Research credit + +Discovered, named, and disclosed by **Asim Manizada** on **2026-05-28**, +with a working proof-of-concept published the same day. + +- Red Hat advisory (RHSB-2026-005): + +- BleepingComputer write-up: + +- Upstream fix: commit `3da1fdf4efbc490041eb4f836bf596201203f8f2` + ("smb: client: reject userspace cifs.spnego descriptions"), merged + 7.1-rc5. +- Debian-tracked stable backports: 5.10.257 (bullseye) / 6.1.174 + (bookworm) / 6.12.90 (trixie) / 7.0.10 (forky, sid). + +All research credit for finding and analysing this bug belongs to Asim +Manizada. SKELETONKEY is the bundling and bookkeeping layer only. + +## SKELETONKEY role + +๐ŸŸก **Primitive / ported-from-disclosure โ€” not yet VM-verified.** +`detect()` gates on the kernel version (the Debian backport thresholds +above) **and** the presence of the vulnerable userspace path +(`cifs.upcall` / the `cifs.spnego` request-key rule) โ€” a vulnerable +kernel without `cifs-utils` is reported `PRECOND_FAIL`, not `VULNERABLE`. +Override the probe with `SKELETONKEY_CIFS_ASSUME_PRESENT=1` (or `0`). + +`exploit()` fires only the reachable, **non-destructive** part of the +primitive: it attempts to register a forged-but-benign `cifs.spnego` key +as the unprivileged user via `add_key(2)` โ€” which instantiates the key +directly and does **not** invoke `cifs.upcall`, so it loads nothing and +spawns no privileged helper โ€” and revokes the key immediately. A clean +accept is the empirical witness that the missing-origin-validation flaw +is present. It then **stops**: the namespace-switch + malicious-NSS-load +chain that actually lands a root shell is target/config-specific and is +**not** bundled until it can be verified end-to-end against a real +vulnerable VM, in keeping with the project's no-fabrication rule. +`exploit()` returns `EXPLOIT_FAIL` unless it can witness euid 0. + +`--mitigate` writes `/etc/modprobe.d/skeletonkey-disable-cifs.conf` +(blocklists the `cifs` module โ€” the vendor-recommended runtime +mitigation); `--cleanup` removes it. Architecture-agnostic โ€” keyring and +namespace logic, no shellcode. diff --git a/modules/cifswitch_cve_2026_46243/skeletonkey_modules.c b/modules/cifswitch_cve_2026_46243/skeletonkey_modules.c new file mode 100644 index 0000000..e153b4d --- /dev/null +++ b/modules/cifswitch_cve_2026_46243/skeletonkey_modules.c @@ -0,0 +1,419 @@ +/* + * cifswitch_cve_2026_46243 โ€” SKELETONKEY module + * + * CVE-2026-46243 "CIFSwitch" โ€” the kernel's `cifs.spnego` request-key + * type accepts key descriptions created by *userspace* (via add_key(2) / + * request_key(2)) without verifying the request originated from the + * in-kernel CIFS client. Those descriptions carry authority-bearing + * fields (`pid`, `uid`, `creduid`, `upcall_target`) that the + * root-privileged `cifs.upcall` helper trusts as kernel-originating. + * An unprivileged user forges a description and โ€” combined with user + + * mount namespace manipulation โ€” coerces `cifs.upcall` into loading an + * attacker-controlled NSS shared library as root โ†’ local root. + * + * Disclosed by Asim Manizada, 2026-05-28 (public PoC same day). A + * ~19-year-old bug: the cifs spnego upcall predates the key-type origin + * checks added later. Fixed upstream by commit 3da1fdf4efbc (merged + * 7.1-rc5): "smb: client: reject userspace cifs.spnego descriptions". + * NVD: CWE-20 (Improper Input Validation). Not in CISA KEV. + * + * STATUS: ๐ŸŸก PRIMITIVE / ported-from-disclosure, NOT yet VM-verified. + * Structural logic flaw โ€” no offsets, no race, no shellcode. detect() + * gates on (a) the kernel version (Debian-tracked backports below) and + * (b) the presence of the vulnerable userspace path: the `cifs.upcall` + * helper / `cifs.spnego` request-key rule. A vulnerable kernel without + * cifs-utils is not reachable via this technique, so that case is + * PRECOND_FAIL, not VULNERABLE. exploit() fires the reachable, + * non-destructive part of the primitive โ€” it attempts to register a + * forged-but-benign `cifs.spnego` key as the unprivileged user (via + * add_key(2), which does NOT invoke cifs.upcall) and observes whether + * the kernel accepts a userspace-originated description โ€” then STOPS. + * The namespace-switch + malicious-NSS-load that turns that into a + * root shell is target/config-specific and is not bundled until it can + * be VM-verified end-to-end. Honest EXPLOIT_FAIL without a euid-0 + * witness; never fabricates root. + * + * Affected range (Debian-tracked stable backports of the fix): + * 5.10.x : K >= 5.10.257 (bullseye) + * 6.1.x : K >= 6.1.174 (bookworm) + * 6.12.x : K >= 6.12.90 (trixie) + * 7.0.x : K >= 7.0.10 (forky / sid); mainline fixed 7.1-rc5 + * Branches Debian doesn't track (5.15 / 6.6 / 6.8 / 6.11 ...) fall + * through to the version-only verdict โ€” confirm empirically. + * + * Preconditions: cifs kernel module available + cifs-utils installed + * (`cifs.upcall` present) + the `cifs.spnego` request-key rule active. + * Override the precondition probe with SKELETONKEY_CIFS_ASSUME_PRESENT + * = 1 (force present) / 0 (force absent) when you know the fleet's CIFS + * posture better than a local file probe can (also drives unit tests). + * + * arch_support: any. Keyring + namespace logic; no shellcode. + */ + +#include "skeletonkey_modules.h" +#include "../../core/registry.h" + +/* _GNU_SOURCE is passed via -D in the top-level Makefile; do not + * redefine here (warning: redefined). */ + +#include +#include +#include +#include +#include + +#ifdef __linux__ + +#include "../../core/kernel_range.h" +#include "../../core/host.h" +#include +#include +#include +#include +#include + +/* keyring syscalls live in libkeyutils, not glibc โ€” call them directly. + * The asm-generic numbers below match x86_64 / arm64 / most arches; fall + * back only when the toolchain headers don't already define them. */ +#ifndef SYS_add_key +#define SYS_add_key 248 +#endif +#ifndef SYS_keyctl +#define SYS_keyctl 250 +#endif + +/* keyctl operations + special keyring ids (uapi/linux/keyctl.h). */ +#ifndef KEYCTL_REVOKE +#define KEYCTL_REVOKE 3 +#endif +#ifndef KEY_SPEC_PROCESS_KEYRING +#define KEY_SPEC_PROCESS_KEYRING (-2) +#endif + +typedef int sk_key_serial_t; + +static sk_key_serial_t sk_add_key(const char *type, const char *desc, + const void *payload, size_t plen, + sk_key_serial_t keyring) +{ + return (sk_key_serial_t)syscall(SYS_add_key, type, desc, + payload, plen, keyring); +} +static long sk_keyctl_revoke(sk_key_serial_t key) +{ + return syscall(SYS_keyctl, (long)KEYCTL_REVOKE, (long)key, 0L, 0L, 0L); +} + +/* Debian-tracked stable backports of the 2026 fix (commit 3da1fdf4efbc, + * mainline 7.1-rc5). These are the authoritative thresholds + * (security-tracker.debian.org). Branches Debian doesn't ship fall + * through to the version-only verdict in detect(). */ +static const struct kernel_patched_from cifswitch_patched_branches[] = { + {5, 10, 257}, /* 5.10-LTS backport (Debian bullseye) */ + {6, 1, 174}, /* 6.1-LTS backport (Debian bookworm) */ + {6, 12, 90}, /* 6.12-LTS backport (Debian trixie) */ + {7, 0, 10}, /* 7.0 stable (Debian forky / sid) */ +}; + +static const struct kernel_range cifswitch_range = { + .patched_from = cifswitch_patched_branches, + .n_patched_from = sizeof(cifswitch_patched_branches) / + sizeof(cifswitch_patched_branches[0]), +}; + +/* Is the vulnerable userspace path present? The load-bearing signal is + * the cifs.upcall helper (the privileged component the bug abuses); the + * cifs.spnego request-key rule and a loaded/loadable cifs module + * corroborate. SKELETONKEY_CIFS_ASSUME_PRESENT overrides the probe: + * "1" = present, "0" = absent (operators who know their fleet's CIFS + * posture, and the unit tests, use this). */ +static bool cifs_userspace_present(void) +{ + const char *force = getenv("SKELETONKEY_CIFS_ASSUME_PRESENT"); + if (force && (force[0] == '1' || force[0] == '0')) + return force[0] == '1'; + + struct stat st; + static const char *upcall_paths[] = { + "/usr/sbin/cifs.upcall", "/sbin/cifs.upcall", + "/usr/bin/cifs.upcall", "/usr/local/sbin/cifs.upcall", NULL, + }; + for (size_t i = 0; upcall_paths[i]; i++) + if (stat(upcall_paths[i], &st) == 0) + return true; + + /* request-key rule for cifs.spnego (cifs-utils ships this). */ + static const char *reqkey_paths[] = { + "/etc/request-key.d/cifs.spnego.conf", + "/usr/share/request-key.d/cifs.spnego.conf", NULL, + }; + for (size_t i = 0; reqkey_paths[i]; i++) + if (stat(reqkey_paths[i], &st) == 0) + return true; + + return false; +} + +static skeletonkey_result_t cifswitch_detect(const struct skeletonkey_ctx *ctx) +{ + const struct kernel_version *v = ctx->host ? &ctx->host->kernel : NULL; + if (!v || v->major == 0) { + if (!ctx->json) + fprintf(stderr, "[!] cifswitch: host fingerprint missing kernel " + "version โ€” bailing\n"); + return SKELETONKEY_TEST_ERROR; + } + + /* A patched kernel is not vulnerable regardless of the userspace + * path โ€” decide that first so the verdict is deterministic. */ + if (kernel_range_is_patched(&cifswitch_range, v)) { + if (!ctx->json) + fprintf(stderr, "[+] cifswitch: kernel %s is patched " + "(version-only check)\n", v->release); + return SKELETONKEY_OK; + } + + /* Vulnerable kernel. Exploitation needs the cifs.upcall userspace + * path; without it the technique is unreachable here. */ + if (!cifs_userspace_present()) { + if (!ctx->json) { + fprintf(stderr, "[i] cifswitch: kernel %s is in the vulnerable " + "range but cifs.upcall / cifs.spnego request-key " + "rule not found โ€” cifs-utils not installed, bug " + "not reachable here\n", v->release); + fprintf(stderr, "[i] cifswitch: if you know this fleet uses CIFS, " + "re-run with SKELETONKEY_CIFS_ASSUME_PRESENT=1\n"); + } + return SKELETONKEY_PRECOND_FAIL; + } + + if (!ctx->json) { + fprintf(stderr, "[!] cifswitch: kernel %s VULNERABLE and cifs.upcall " + "present โ€” CVE-2026-46243 reachable\n", v->release); + fprintf(stderr, "[i] cifswitch: userspace can forge cifs.spnego key " + "descriptions (pid/uid/creduid/upcall_target) the root " + "cifs.upcall helper trusts\n"); + fprintf(stderr, "[i] cifswitch: branches Debian doesn't track " + "(5.15/6.6/6.8/6.11) are version-only here; confirm with " + "`--exploit cifswitch --i-know`\n"); + } + return SKELETONKEY_VULNERABLE; +} + +static skeletonkey_result_t cifswitch_exploit(const struct skeletonkey_ctx *ctx) +{ + if (!ctx->authorized) { + fprintf(stderr, "[-] cifswitch: --i-know required for --exploit\n"); + return SKELETONKEY_EXPLOIT_FAIL; + } + skeletonkey_result_t pre = cifswitch_detect(ctx); + if (pre != SKELETONKEY_VULNERABLE) { + fprintf(stderr, "[-] cifswitch: detect() says not vulnerable/reachable; " + "refusing\n"); + return pre; + } + bool is_root = ctx->host ? ctx->host->is_root : (geteuid() == 0); + if (is_root) { + fprintf(stderr, "[i] cifswitch: already running as root โ€” nothing to do\n"); + return SKELETONKEY_OK; + } + + /* Reachable, non-destructive primitive witness: can we, as an + * unprivileged user, register a cifs.spnego key carrying the + * authority-bearing fields? add_key(2) instantiates the key directly + * โ€” it does NOT invoke cifs.upcall (that is request_key's upcall + * path), so this loads nothing and triggers no privileged helper. On + * a VULNERABLE kernel the type accepts the userspace-originated + * description; the fix (3da1fdf4efbc) rejects it. We revoke any key + * we create immediately. A clean accept is the empirical signal that + * the missing-origin-validation flaw is present; any error is treated + * as inconclusive (could be patched, or add_key unsupported for the + * type) and reported honestly โ€” we never infer root from it. */ + const char *desc = + "ver=0x2;host=skeletonkey-probe;ip4=127.0.0.1;sec=krb5;" + "uid=0x0;creduid=0x0;user=skprobe;pid=0x0"; + errno = 0; + sk_key_serial_t k = sk_add_key("cifs.spnego", desc, "\x00", 1, + KEY_SPEC_PROCESS_KEYRING); + if (k > 0) { + sk_keyctl_revoke(k); /* don't leave the probe key lying around */ + fprintf(stderr, + "[!] cifswitch: primitive CONFIRMED โ€” kernel accepted a " + "userspace-forged cifs.spnego key (serial %d) carrying " + "uid/creduid/upcall_target. CVE-2026-46243 reachable.\n", k); + fprintf(stderr, + "[i] cifswitch: the full root-pop (user+mount namespace switch " + "coercing cifs.upcall to load an attacker NSS module as root) is " + "target/config-specific and NOT bundled until VM-verified. Not " + "fabricating a shell. See module NOTICE.md (Asim Manizada PoC).\n"); + return SKELETONKEY_EXPLOIT_FAIL; + } + + if (errno == ENOSYS) { + fprintf(stderr, "[-] cifswitch: add_key(2) ENOSYS โ€” keyrings " + "unavailable in this kernel build\n"); + return SKELETONKEY_PRECOND_FAIL; + } + fprintf(stderr, + "[-] cifswitch: kernel did not accept a userspace-forged cifs.spnego " + "key (add_key: %s). Inconclusive โ€” the kernel may carry the fix " + "(3da1fdf4efbc rejects userspace descriptions), or the key type may " + "not permit direct add_key here. detect() reported the version+helper " + "as vulnerable; verify against a known-vulnerable VM.\n", + strerror(errno)); + return SKELETONKEY_EXPLOIT_FAIL; +} + +/* Mitigation: the vendor-recommended runtime fix is to blocklist the + * cifs module so the vulnerable upcall path cannot be reached. We write + * a modprobe.d blocklist (needs root; persists across reboot and blocks + * future autoload). We do not force-unload a possibly-mounted cifs. The + * real fix is the kernel patch. --cleanup removes the blocklist file. */ +#define CIFSWITCH_BLOCKLIST "/etc/modprobe.d/skeletonkey-disable-cifs.conf" + +static skeletonkey_result_t cifswitch_mitigate(const struct skeletonkey_ctx *ctx) +{ + int fd = open(CIFSWITCH_BLOCKLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + fprintf(stderr, "[-] cifswitch: cannot write %s: %s " + "(need root: run as root, or " + "`echo 'blacklist cifs' | sudo tee %s`)\n", + CIFSWITCH_BLOCKLIST, strerror(errno), CIFSWITCH_BLOCKLIST); + return SKELETONKEY_PRECOND_FAIL; + } + static const char body[] = + "# Added by SKELETONKEY --mitigate cifswitch (CVE-2026-46243).\n" + "# Blocklists the cifs module so the vulnerable cifs.spnego upcall\n" + "# path cannot be reached. Remove via `--cleanup cifswitch`.\n" + "blacklist cifs\n" + "install cifs /bin/false\n"; + ssize_t w = write(fd, body, sizeof body - 1); + close(fd); + if (w != (ssize_t)(sizeof body - 1)) { + fprintf(stderr, "[-] cifswitch: short write to %s\n", CIFSWITCH_BLOCKLIST); + return SKELETONKEY_EXPLOIT_FAIL; + } + fprintf(stderr, "[+] cifswitch: wrote %s (blocklist cifs). Already-loaded " + "cifs stays until unmounted+`rmmod cifs` or reboot. This is " + "a stopgap; patch the kernel. Revert: `--cleanup cifswitch`.\n", + CIFSWITCH_BLOCKLIST); + (void)ctx; + return SKELETONKEY_OK; +} + +static skeletonkey_result_t cifswitch_cleanup(const struct skeletonkey_ctx *ctx) +{ + if (unlink(CIFSWITCH_BLOCKLIST) == 0) { + if (!ctx->json) + fprintf(stderr, "[*] cifswitch: removed %s\n", CIFSWITCH_BLOCKLIST); + } else if (errno != ENOENT) { + fprintf(stderr, "[-] cifswitch: could not remove %s: %s\n", + CIFSWITCH_BLOCKLIST, strerror(errno)); + } + return SKELETONKEY_OK; +} + +#else /* !__linux__ */ + +/* Non-Linux dev builds: keyrings, cifs.upcall and modprobe are all + * Linux-only. Stub so the module still registers and `make` completes on + * macOS/BSD dev boxes. */ +static skeletonkey_result_t cifswitch_detect(const struct skeletonkey_ctx *ctx) +{ + if (!ctx->json) + fprintf(stderr, "[i] cifswitch: Linux-only module " + "(cifs.spnego keyring trust) โ€” not applicable here\n"); + return SKELETONKEY_PRECOND_FAIL; +} +static skeletonkey_result_t cifswitch_exploit(const struct skeletonkey_ctx *ctx) +{ + (void)ctx; + fprintf(stderr, "[-] cifswitch: Linux-only module โ€” cannot run here\n"); + return SKELETONKEY_PRECOND_FAIL; +} +static skeletonkey_result_t cifswitch_mitigate(const struct skeletonkey_ctx *ctx) +{ + (void)ctx; + return SKELETONKEY_PRECOND_FAIL; +} +static skeletonkey_result_t cifswitch_cleanup(const struct skeletonkey_ctx *ctx) +{ + (void)ctx; + return SKELETONKEY_OK; +} + +#endif /* __linux__ */ + +/* Embedded detection rules โ€” keep the binary self-contained. The + * behavioural signal is a non-root process creating a `cifs.spnego` key + * (add_key/request_key) and/or an unexpected cifs.upcall execution + * paired with user-namespace setup. */ +static const char cifswitch_auditd[] = + "# CVE-2026-46243 (CIFSwitch) โ€” auditd detection rules\n" + "# A non-root add_key/request_key for cifs.spnego is the core abuse,\n" + "# usually paired with unshare(CLONE_NEWUSER|CLONE_NEWNS) and a\n" + "# cifs.upcall execution that loads an attacker NSS module.\n" + "-a always,exit -F arch=b64 -S add_key -F auid>=1000 -F auid!=4294967295 -k skeletonkey-cifswitch\n" + "-a always,exit -F arch=b64 -S request_key -F auid>=1000 -F auid!=4294967295 -k skeletonkey-cifswitch\n" + "-a always,exit -F arch=b64 -S unshare -F auid>=1000 -F auid!=4294967295 -k skeletonkey-cifswitch\n" + "-w /usr/sbin/cifs.upcall -p x -k skeletonkey-cifswitch\n"; + +static const char cifswitch_sigma[] = + "title: Possible CVE-2026-46243 CIFSwitch cifs.spnego keyring LPE\n" + "id: 9b2e7c10-skeletonkey-cifswitch\n" + "status: experimental\n" + "description: |\n" + " Detects a non-root process creating a cifs.spnego key via\n" + " add_key/request_key. CIFSwitch forges the authority-bearing fields\n" + " (uid/creduid/upcall_target) in a cifs.spnego key description that\n" + " the root cifs.upcall helper trusts, then uses namespace tricks to\n" + " load an attacker NSS module as root. False positives: legitimate\n" + " CIFS/Kerberos mounts normally trigger cifs.spnego from kernel\n" + " context (root), not from an unprivileged add_key.\n" + "logsource: {product: linux, service: auditd}\n" + "detection:\n" + " keyop: {type: 'SYSCALL', syscall: ['add_key', 'request_key']}\n" + " non_root: {auid|expression: '>= 1000'}\n" + " condition: keyop and non_root\n" + "level: high\n" + "tags: [attack.privilege_escalation, attack.t1068, cve.2026.46243]\n"; + +static const char cifswitch_falco[] = + "- rule: non-root cifs.spnego key creation (CVE-2026-46243 CIFSwitch)\n" + " desc: |\n" + " A non-root process creates a cifs.spnego key (add_key/request_key)\n" + " or spawns cifs.upcall outside a kernel-initiated CIFS mount. The\n" + " CIFSwitch LPE forges authority fields in the key description that\n" + " the root cifs.upcall helper trusts, loading an attacker NSS module\n" + " as root. False positives: container/CIFS tooling run as root.\n" + " condition: >\n" + " ((evt.type in (add_key, request_key)) or\n" + " (spawned_process and proc.name = cifs.upcall)) and not user.uid = 0\n" + " output: >\n" + " non-root cifs.spnego key op / cifs.upcall (possible CVE-2026-46243)\n" + " (user=%user.name proc=%proc.name pid=%proc.pid cmdline=\"%proc.cmdline\")\n" + " priority: HIGH\n" + " tags: [process, mitre_privilege_escalation, T1068, cve.2026.46243]\n"; + +const struct skeletonkey_module cifswitch_module = { + .name = "cifswitch", + .cve = "CVE-2026-46243", + .summary = "cifs.spnego key type trusts userspace-forged authority fields โ†’ cifs.upcall loads attacker NSS module as root (Asim Manizada)", + .family = "cifswitch", + .kernel_range = "fixed 5.10.257 / 6.1.174 / 6.12.90 / 7.0.10 (Debian backports of commit 3da1fdf4efbc, mainline 7.1-rc5); ~19-year-old bug below those", + .detect = cifswitch_detect, + .exploit = cifswitch_exploit, + .mitigate = cifswitch_mitigate, + .cleanup = cifswitch_cleanup, + .detect_auditd = cifswitch_auditd, + .detect_sigma = cifswitch_sigma, + .detect_yara = NULL, /* attacker NSS .so has no stable signature; behavioural bug */ + .detect_falco = cifswitch_falco, + .opsec_notes = "detect() consults the shared host fingerprint for the kernel version (Debian backports 5.10.257/6.1.174/6.12.90/7.0.10) and probes for the cifs.upcall helper / cifs.spnego request-key rule (override via SKELETONKEY_CIFS_ASSUME_PRESENT=1/0); a vulnerable kernel without cifs-utils is PRECOND_FAIL. exploit() fires only the non-destructive primitive: add_key(2) of a forged-but-benign cifs.spnego key (does NOT invoke cifs.upcall, loads nothing), revokes it immediately, and treats a clean accept as the empirical witness โ€” it never runs the namespace-switch + malicious-NSS-load chain that pops root, and returns EXPLOIT_FAIL without a euid-0 witness. Audit-visible via add_key/request_key for cifs.spnego by a non-root auid, typically alongside unshare(CLONE_NEWUSER|CLONE_NEWNS) and a cifs.upcall execution. --mitigate writes /etc/modprobe.d/skeletonkey-disable-cifs.conf (blacklist cifs); --cleanup removes it. Arch-agnostic (no shellcode).", + .arch_support = "any", +}; + +void skeletonkey_register_cifswitch(void) +{ + skeletonkey_register(&cifswitch_module); +} diff --git a/modules/cifswitch_cve_2026_46243/skeletonkey_modules.h b/modules/cifswitch_cve_2026_46243/skeletonkey_modules.h new file mode 100644 index 0000000..e2ab696 --- /dev/null +++ b/modules/cifswitch_cve_2026_46243/skeletonkey_modules.h @@ -0,0 +1,12 @@ +/* + * cifswitch_cve_2026_46243 โ€” SKELETONKEY module registry hook + */ + +#ifndef CIFSWITCH_SKELETONKEY_MODULES_H +#define CIFSWITCH_SKELETONKEY_MODULES_H + +#include "../../core/module.h" + +extern const struct skeletonkey_module cifswitch_module; + +#endif diff --git a/skeletonkey.c b/skeletonkey.c index d6c9b04..9882c97 100644 --- a/skeletonkey.c +++ b/skeletonkey.c @@ -35,7 +35,7 @@ #include #include -#define SKELETONKEY_VERSION "0.9.9" +#define SKELETONKEY_VERSION "0.9.10" static const char BANNER[] = "\n" @@ -1016,6 +1016,7 @@ static int module_safety_rank(const char *n) !strcmp(n, "fragnesia")) return 87; /* ported page-cache writes; version-pinned detect, exploit NOT VM-verified */ if (!strcmp(n, "ptrace_traceme")) return 85; /* userspace cred race */ if (!strcmp(n, "ptrace_pidfd")) return 84; /* pidfd_getfd fd-steal race; ported, exploit NOT VM-verified */ + if (!strcmp(n, "cifswitch")) return 86; /* structural cifs.spnego keyring trust; ported, full chain NOT bundled/VM-verified */ if (!strcmp(n, "sudo_samedit")) return 80; /* heap-tuned, may crash sudo */ if (!strcmp(n, "af_unix_gc")) return 25; /* kernel race, low win% */ if (!strcmp(n, "stackrot")) return 15; /* very low win% */ diff --git a/tests/test_detect.c b/tests/test_detect.c index f523b8e..0384aa4 100644 --- a/tests/test_detect.c +++ b/tests/test_detect.c @@ -70,6 +70,7 @@ extern const struct skeletonkey_module vsock_uaf_module; extern const struct skeletonkey_module nft_pipapo_module; extern const struct skeletonkey_module ptrace_pidfd_module; extern const struct skeletonkey_module sudo_host_module; +extern const struct skeletonkey_module cifswitch_module; static int g_pass = 0; static int g_fail = 0; @@ -803,6 +804,44 @@ static void run_all(void) &sudo_host_module, &h_sudo_host_1917, SKELETONKEY_VULNERABLE); + /* โ”€โ”€ cifswitch (CVE-2026-46243) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + * Version-gated on Debian backports 5.10.257 / 6.1.174 / 6.12.90 / + * 7.0.10. The VULNERABLE/PRECOND_FAIL split below the fix depends on + * whether the cifs.upcall userspace path is present; we drive that + * deterministically with SKELETONKEY_CIFS_ASSUME_PRESENT (1=present, + * 0=absent) so the rows don't depend on cifs-utils being installed on + * the runner. Patched-kernel rows return OK before the probe, so they + * need no override. */ + + /* patched branch (exact 6.12.90 backport) โ†’ OK regardless of cifs */ + struct skeletonkey_host h_ciw_61290 = + mk_host(h_kernel_6_12, 6, 12, 90, "6.12.90-test"); + run_one("cifswitch: 6.12.90 (exact backport) โ†’ OK via patch table", + &cifswitch_module, &h_ciw_61290, + SKELETONKEY_OK); + + /* 7.1.0 newer than every entry โ†’ mainline-inherited fix โ†’ OK */ + struct skeletonkey_host h_ciw_710 = + mk_host(h_kernel_6_12, 7, 1, 0, "7.1.0-test"); + run_one("cifswitch: 7.1.0 above all backports โ†’ OK (mainline inherit)", + &cifswitch_module, &h_ciw_710, + SKELETONKEY_OK); + + /* vulnerable kernel (one below 6.12.90) + cifs path present โ†’ VULNERABLE */ + struct skeletonkey_host h_ciw_61289 = + mk_host(h_kernel_6_12, 6, 12, 89, "6.12.89-test"); + setenv("SKELETONKEY_CIFS_ASSUME_PRESENT", "1", 1); + run_one("cifswitch: 6.12.89 + cifs.upcall present โ†’ VULNERABLE", + &cifswitch_module, &h_ciw_61289, + SKELETONKEY_VULNERABLE); + + /* same vulnerable kernel but cifs path absent โ†’ PRECOND_FAIL */ + setenv("SKELETONKEY_CIFS_ASSUME_PRESENT", "0", 1); + run_one("cifswitch: 6.12.89 but cifs-utils absent โ†’ PRECOND_FAIL", + &cifswitch_module, &h_ciw_61289, + SKELETONKEY_PRECOND_FAIL); + unsetenv("SKELETONKEY_CIFS_ASSUME_PRESENT"); + /* โ”€โ”€ coverage report โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ * Iterate the runtime registry (populated by skeletonkey_register_* * calls in main()) and warn for any module that was not touched diff --git a/tools/verify-vm/targets.yaml b/tools/verify-vm/targets.yaml index 376c964..1b8ca30 100644 --- a/tools/verify-vm/targets.yaml +++ b/tools/verify-vm/targets.yaml @@ -303,3 +303,13 @@ sudo_host: kernel_version: "4.15.0" expect_detect: VULNERABLE notes: "CVE-2025-32462; sudo -h/--host policy bypass (Stratascale, sibling of sudo_chwoot). Ubuntu 18.04 ships sudo 1.8.21p2, inside the vulnerable range [1.8.8, 1.9.17p0] (fixed 1.9.17p1), so detect() returns VULNERABLE on the version gate. Exercising exploit() empirically needs a sudoers rule scoped to a host other than the box hostname (and not ALL): provision e.g. 'vagrant fakehost = (ALL) NOPASSWD: ALL' in /etc/sudoers.d/, then 'SKELETONKEY_SUDO_HOST=fakehost skeletonkey --exploit sudo_host --i-know' pops root via 'sudo -h fakehost /bin/bash'. Brand-new addition this cycle; provisioner + sweep pending." + +# โ”€โ”€ cifswitch (CVE-2026-46243) addition โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +cifswitch: + box: ubuntu2204 + kernel_pkg: "" + mainline_version: "6.12.89" # one below the 6.12.90 backport; ~19yo bug present + kernel_version: "6.12.89" + expect_detect: VULNERABLE + notes: "CVE-2026-46243 'CIFSwitch'; cifs.spnego key type trusts userspace-forged authority fields (Asim Manizada, 2026-05-28). Fixed 5.10.257 / 6.1.174 / 6.12.90 / 7.0.10 (Debian backports of commit 3da1fdf4efbc, mainline 7.1-rc5); a ~19-year-old bug below those. Mainline 6.12.89 is one below the 6.12.90 backport so the version gate flags VULNERABLE โ€” but detect() ALSO requires the cifs userspace path: provision cifs-utils (so /usr/sbin/cifs.upcall + the cifs.spnego request-key rule exist) and `modprobe cifs`, else detect() returns PRECOND_FAIL (or force with SKELETONKEY_CIFS_ASSUME_PRESENT=1). exploit() fires the non-destructive add_key(2) cifs.spnego probe and reports the forged-key accept as the primitive witness; the namespace+NSS root-pop is not bundled until VM-verified. Brand-new addition this cycle; provisioner (cifs-utils install) + sweep pending."