modules: add cifswitch (CVE-2026-46243, Asim Manizada's CIFSwitch)
release / build (arm64) (push) Waiting to run
release / build (x86_64) (push) Waiting to run
release / build (x86_64-static / musl) (push) Waiting to run
release / build (arm64-static / musl) (push) Waiting to run
release / release (push) Blocked by required conditions

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.
This commit is contained in:
KaraZajac
2026-06-08 11:07:27 -04:00
parent 28a9289989
commit ada56b0db3
17 changed files with 703 additions and 27 deletions
+3 -2
View File
@@ -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 <host> <cmd>`. 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 <host> 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
+6 -1
View File
@@ -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)
+16 -12
View File
@@ -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
+8
View File
@@ -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",
+1
View File
@@ -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_<family>() above in canonical order.
* Single source of truth so the main binary and the test binary stay
+1
View File
@@ -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();
}
+9
View File
@@ -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",
+2 -1
View File
@@ -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` |
+35
View File
@@ -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.
+11 -10
View File
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SKELETONKEY — Linux LPE corpus, VM-verified, SOC-ready detection</title>
<meta name="description" content="One binary. 41 Linux privilege-escalation modules from 2016 to 2026. 28 of 36 CVEs empirically verified in real Linux VMs. 13 KEV-listed. 151 detection rules across auditd/sigma/yara/falco. MITRE ATT&CK and CWE annotated. --explain gives operator briefings.">
<meta name="description" content="One binary. 42 Linux privilege-escalation modules from 2016 to 2026. 28 of 37 CVEs empirically verified in real Linux VMs. 13 KEV-listed. 151 detection rules across auditd/sigma/yara/falco. MITRE ATT&CK and CWE annotated. --explain gives operator briefings.">
<meta property="og:title" content="SKELETONKEY — Linux LPE corpus, VM-verified">
<meta property="og:description" content="41 Linux LPE modules; 28 of 36 CVEs empirically verified in real VMs. 151 detection rules. ATT&CK + CWE + KEV annotated.">
<meta property="og:description" content="42 Linux LPE modules; 28 of 37 CVEs empirically verified in real VMs. 151 detection rules. ATT&CK + CWE + KEV annotated.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://karazajac.github.io/SKELETONKEY/">
<meta property="og:image" content="https://karazajac.github.io/SKELETONKEY/og.png">
@@ -56,13 +56,13 @@
<div class="container hero-inner">
<div class="hero-eyebrow">
<span class="dot dot-pulse"></span>
v0.9.9 — released 2026-06-08
v0.9.10 — released 2026-06-08
</div>
<h1 class="hero-title">
<span class="display-wordmark">SKELETONKEY</span>
</h1>
<p class="hero-tag">
One binary. <strong>41 Linux LPE modules</strong> covering 36 CVEs —
One binary. <strong>42 Linux LPE modules</strong> covering 37 CVEs —
<strong>every year 2016 → 2026</strong>. 28 of 34 confirmed against
real Linux kernels in VMs. SOC-ready detection rules in four SIEM
formats. MITRE ATT&amp;CK + CWE + CISA KEV annotated.
@@ -227,7 +227,7 @@ uid=0(root) gid=0(root)</pre>
<div class="bento-icon"></div>
<h3>CISA KEV prioritized</h3>
<p>
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 <code>tools/refresh-cve-metadata.py</code>.
</p>
@@ -294,7 +294,7 @@ uid=0(root) gid=0(root)</pre>
<code>tools/verify-vm/</code> spins up known-vulnerable
kernels (stock distro + mainline from kernel.ubuntu.com), runs
<code>--explain --active</code> per module, and records the
verdict. <strong>28 of 36 CVEs</strong> confirmed against
verdict. <strong>28 of 37 CVEs</strong> 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;
<code>--list</code> shows ✓ per module.
@@ -309,7 +309,7 @@ uid=0(root) gid=0(root)</pre>
<div class="container">
<div class="section-head">
<span class="section-tag">corpus</span>
<h2>36 CVEs across 10 years. ★ = actively exploited (CISA KEV).</h2>
<h2>37 CVEs across 10 years. ★ = actively exploited (CISA KEV).</h2>
</div>
<h3 class="corpus-h" data-color="green">
@@ -356,6 +356,7 @@ uid=0(root) gid=0(root)</pre>
<span class="pill yellow">sequoia</span>
<span class="pill yellow">vmwgfx</span>
<span class="pill yellow">ptrace_pidfd</span>
<span class="pill yellow">cifswitch</span>
</div>
<p class="corpus-foot">
@@ -416,7 +417,7 @@ uid=0(root) gid=0(root)</pre>
<div class="audience-icon">🎓</div>
<h3>Researchers / CTF</h3>
<p>
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.
<code>--explain</code> 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)</pre>
<div class="tl-col tl-shipped">
<div class="tl-tag">shipped</div>
<ul>
<li><strong>28 of 36 CVEs empirically verified</strong> in real Linux VMs</li>
<li><strong>28 of 37 CVEs empirically verified</strong> in real Linux VMs</li>
<li><strong>kernel.ubuntu.com/mainline/</strong> kernel fetch path — unblocks pin-not-in-apt targets</li>
<li>Per-module <code>verified_on[]</code> table baked into the binary</li>
<li><strong>--explain mode</strong> — one-page operator briefing per CVE</li>
@@ -600,7 +601,7 @@ uid=0(root) gid=0(root)</pre>
who found the bugs.
</p>
<p class="footer-meta">
v0.9.9 · MIT · <a href="https://github.com/KaraZajac/SKELETONKEY">github.com/KaraZajac/SKELETONKEY</a>
v0.9.10 · MIT · <a href="https://github.com/KaraZajac/SKELETONKEY">github.com/KaraZajac/SKELETONKEY</a>
</p>
</div>
</footer>
@@ -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`.
@@ -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 610.
## 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):
<https://access.redhat.com/security/vulnerabilities/RHSB-2026-005>
- BleepingComputer write-up:
<https://www.bleepingcomputer.com/news/security/new-cifswitch-linux-flaw-gives-root-on-multiple-distributions/>
- 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.
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#ifdef __linux__
#include "../../core/kernel_range.h"
#include "../../core/host.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
/* 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);
}
@@ -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
+2 -1
View File
@@ -35,7 +35,7 @@
#include <string.h>
#include <unistd.h>
#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% */
+39
View File
@@ -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
+10
View File
@@ -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."