CIFSwitch: A non-universal Linux local root vulnerability Security researchers discovered a Linux local privilege escalation vulnerability, dubbed CIFSwitch, in the kernel's CIFS filesystem and the userspace cifs-utils helper. The flaw allows an attacker to exploit a missing validation of key description origins, enabling a namespace confusion attack that grants root access on the machine. The vulnerability affects specific Linux distributions and has been patched in the upstream kernel. TLDR: A distro-specific Linux LPE found by harnessing LLMs into better multihop knowledge composition. Read on for affected distros, mitigations, and vulnerability details. Background background In Getting LLMs Drunk to Find Remote Linux Kernel OOB Writes and More https://heyitsas.im/posts/drinking-llms/ future-research-directions , I’d mentioned how improving LLMs’ ability to compose existing knowledge is a promising avenue for unlocking “creative” – or at least non-trivial – vulnerability findings. Incidentally, among the latest slew of Linux LPEs, CopyFail stood out for – among other things – exquisitely composing several logic bugs, serving as a reminder of the massive potential value of the approach. Unfortunately, training a capable looped transformer to improve compositionality was a non-starter, so I started looking for harness-level improvements instead. GraphWalk: Enabling Reasoning in Large Language Models through Tool-Based Graph Navigation https://arxiv.org/html/2604.01610v1 offered a promising alternative: the authors developed a tool for models to traverse and reason through graphs, improving their multihop reasoning capabilities. The benefits were measured primarily for non-reasoning models; but, on large-enough graphs, non-reasoning models equipped with the tool outperformed reasoning models without it. So, the general approach seemed promising even for otherwise-scaffolded reasoning models – slicing the context with RLMs, .md files with “memories,” etc., are all useful for tackling graph-based problems, but we could still strengthen the harness with a first-class graph traversal tool. The paper described a tool for existing graphs, but for vulnerability hunting we don’t actually have “interesting” graphs pre-built CodeQL-style CPGs are too low-level/clunky for the level of abstraction I wanted So I harnessed the agents to a build the graphs at a higher level of abstraction and b actually query them, like in the paper above. The graphs were intended to capture the following deliberately somewhat fuzzy to play to LLMs’ strengths : - Privileged consumers: what kernel paths, daemons, helpers, etc. consume an object as authoritative? - Creators: what actions create or modify in some way the object? Are they attacker-controlled? Under what conditions? - Object: what is the exact kernel object – e.g. key, policy verdict, fd, queue entry, signature, etc.? - Check split: what security-relevant properties of the object are checked at creation time vs. later? - Drift: the most important how can an object’s origin, credentials, namespace, idmap, mount view, LSM domain, etc. stop matching what’s assumed by its consumer? At this point, I had the scaffolding to point at the desired target. Ideally, it’d be something straddling kernel and userspace for compositionality to really shine, and primarily/entirely a logic bug chain… Based on some https://nvd.nist.gov/vuln/detail/CVE-2026-31432 prior https://nvd.nist.gov/vuln/detail/CVE-2026-31433 experience https://www.samba.org/samba/security/CVE-2026-1933.html , the SMB protocol family seemed like a fertile ground. The vulnerability the-vulnerability The harnessed agents found an issue at the intersection of kernel’s CIFS and the userspace cifs-utils -provided helper. In short, they first discovered that the kernel did not validate the description origin of the cifs.spnego key object. Backtracking, they then found that they could therefore issue a request key syscall with a fake key description, which launches a rootful helper. Finally, after noticing that the fake key descriptions have actual security relevance – they contain pid , which combined with upcall target=app controls which namespace the helper actually runs in – they converted the namespace confusion into root on the machine. Since the patch https://github.com/torvalds/linux/commit/3da1fdf4efbc490041eb4f836bf596201203f8f2 has been out for over a week and is queued for stable, we agreed with linux-distros@ on an embargo through May 27, 2026. The advisory is now public so that the affected system owners can patch or apply other mitigations /posts/cifswitch/ are-you-affected--mitigation . The CVE assignment is still pending. CIFS basics cifs-basics CIFS/SMB is a Windows-style network filesystem protocol. On Linux, the CIFS kernel client handles the actual filesystem parts: mounting the share, talking SMB to the server, doing reads/writes, etc. But, understandably, for Kerberos-auth’d mounts, kernel CIFS doesn’t roll its own auth stack and instead relies on a userspace helper provided by cifs-utils . The interaction happens through Linux keyrings. The kernel requests a cifs.spnego -type key, and the normal keyutils/request-key config runs cifs.upcall as root to fetch or build the Kerberos/SPNEGO material. That brings us to – ahem – the key part. Bird’s eye view birds-eye-view The expected interaction between the kernel and userspace parts is the following: - Kernel CIFS decides it needs Kerberos/SPNEGO material for a mount. - Kernel CIFS builds a semicolon-separated cifs.spnego description string from real kernel state: server, uid, creduid, pid, namespace target, etc. For example: ver=0x2;host=fs.acme.com;ip4=192.168.1.10;sec=krb5;uid=0x3e8;creduid=0x3e8;user=test@ACME.COM;pid=0x4f2a;upcall target=app - Kernel CIFS calls request key for a cifs.spnego key while using its private spnego cred . In userspace , /sbin/request-key checks the rules for cifs.spnego e.g., the default create cifs.spnego /usr/sbin/cifs.upcall %k in /etc/request-key.d/cifs.spnego.conf and calls cifs.upcall as root. cifs.upcall then parses the description and uses it to decide which uid, credential cache, process, and namespaces to use.- If the upcall succeeds, kernel CIFS gets back the SPNEGO blob and continues the mount/session setup. You may have already noticed the critical question: does either userspace or the kernel validate that the key description fields actually came from kernel CIFS? That’s where things break: - An attacker in userspace can call request key "cifs.spnego", totally fake description, ... directly. - In the kernel, the pre- patch https://github.com/torvalds/linux/commit/3da1fdf4efbc490041eb4f836bf596201203f8f2 cifs.spnego key type does not reject the untrusted userspace-created descriptions, treating them as if they came from kernel CIFS. - Since the requested key type is cifs.spnego , /sbin/request-key calls cifs.upcall as root per the default cifs.spnego rule, just like in the happy-path scenario above. - The userspace helper then parses attacker-controlled pid , uid , creduid , and upcall target fields, assuming them to be kernel-produced. - With the attacker-fed upcall target=app , the helper switches into the namespaces of the supplied pid . - Before the final setuid / setgid /privilege drop, the helper does account lookup. Account lookup involves NSS https://en.wikipedia.org/wiki/Name Service Switch , which permits loading of NSS modules based on the NSS config. - So, the attacker’s mount namespace can contain a fake nsswitch.conf and a libnss .so.2 NSS module, getting the root helper to trigger the loading of attacker-controlled NSS code. Diving in diving-in Now, let’s walk through what actually enables the PoC https://github.com/manizada/CIFSwitch to convert the above to root: Letting userspace speak as kernel CIFS letting-userspace-speak-as-kernel-cifs A normal Kerberos CIFS mount eventually reaches cifs get spnego key https://github.com/torvalds/linux/blob/4d8690dace005a38e6dbde9ecce2da3ad85c7c41/fs/smb/client/cifs spnego.c L83-L162 . The kernel builds a cifs.spnego description string from kernel state, then asks the keyring subsystem for a cifs.spnego -type key under CIFS’s private spnego cred : dp += sprintf dp, ";uid=0x%x", ... ; dp += sprintf dp, ";creduid=0x%x", ... ; dp += sprintf dp, ";pid=0x%x", current- pid ; ... if sesInfo- upcall target == UPTARGET MOUNT dp += sprintf dp, ";upcall target=mount" ; else dp += sprintf dp, ";upcall target=app" ; ... scoped with creds spnego cred spnego key = request key &cifs spnego key type, description, "" ; These fields actually matter: uid / creduid decide whose credentials the helper should look up, while pid / upcall target determine what the helper should treat as the application’s namespace. But the key type definition did not enforce that the key was kernel-CIFS-originating. Before the fix, cifs spnego key type https://github.com/torvalds/linux/blob/4d8690dace005a38e6dbde9ecce2da3ad85c7c41/fs/smb/client/cifs spnego.c L47-L52 was just: struct key type cifs spnego key type = { .name = "cifs.spnego", .instantiate = cifs spnego key instantiate, .destroy = cifs spnego key destroy, .describe = user describe, }; Note the missing .vet description , the hook that would govern a given key type ’s description’s legitimacy . So, an unprivileged process could ask for the same cifs.spnego -type key with request key "cifs.spnego", totally fake description, ... , and the default request-key rule create cifs.spnego /usr/sbin/cifs.upcall %k would still launch cifs.upcall as root. Importantly, the kernel did not need to return a key for cifs.upcall to launch. The upcall launches first, enabling the attack, even if the kernel -ENOKEY s after. Forged pid – root NSS forged-pid--root-nss On the userspace side, cifs.upcall parses the key description and treats the decoded fields as kernel-provided facts regardless of where they came from . The namespace-aware code parses upcall target=mount / upcall target=app and then switches namespaces https://github.com/piastry/cifs-utils/blob/89b679228cc1be9739d54203d28289b03352c174/cifs.upcall.c L1511-L1525 when the upcall target is app : / Change to the process's namespace. This means that things will work acceptably in containers, because we'll be looking at the correct filesystem and have the correct network configuration. / if arg- upcall target == UPTARGET APP || arg- upcall target == UPTARGET UNSPECIFIED { syslog LOG INFO, "upcall target=app, switching namespaces to application thread" ; rc = switch to process ns arg- pid ; if rc == -1 goto out; if trim capabilities env probe goto out; } The comment explains why this is a supported mode of operation; it also reveals the risk of managing to thread through an attacker-controlled arg- pid . Only after that namespace switch does it get the target gid out of the passwd NSS database with getpwuid uid https://github.com/piastry/cifs-utils/blob/89b679228cc1be9739d54203d28289b03352c174/cifs.upcall.c L1518-L1540 . The final identity transition happens later, when the helper reaches : https://github.com/piastry/cifs-utils/blob/89b679228cc1be9739d54203d28289b03352c174/cifs.upcall.c L1568-L1579 setuid uid and drop all capabilities / The kernel doesn't pass down the gid, so we resort here to scraping one out of the passwd nss db. / pw = getpwuid uid ; ... rc = setgroups 0, NULL ; ... rc = setgid pw- pw gid ; ... env cachename = get cachename from process env ... ; rc = setuid uid ; ... rc = drop all capabilities ; And to reiterate, getpwuid 0 goes through NSS. If the process has already switched into an attacker-controlled mount namespace, NSS can mean the following is executed as root: php /etc/nsswitch.conf - passwd: pwn files libnss pwn.so.2 - loaded by the root helper At this point, libnss pwn.so.2 can drop a sudoers.d config with the attacker’s username, as in the PoC. The fix the-fix The bare minimum kernel-side fix https://github.com/torvalds/linux/blob/3da1fdf4efbc490041eb4f836bf596201203f8f2/fs/smb/client/cifs spnego.c L44-L64 is to treat the descriptions as legitimate only when CIFS is using spnego cred : js static int cifs spnego key vet description const char description { if current cred = spnego cred return -EPERM; return 0; } struct key type cifs spnego key type = { .name = "cifs.spnego", .vet description = cifs spnego key vet description, ... }; There’s still userspace hardening to be done to not assume that the key description is necessarily kernel-generated, but the above stops the exploitation by itself. Are you affected? + Mitigation are-you-affected--mitigation The exploitability conditions are all of the below: - Vulnerable kernel version. The kernel-side bug has been around since 2007 https://github.com/torvalds/linux/blame/7ad785927d9eb348adb381d168ed73d0dd3c7670/fs/smb/client/cifs spnego.c - An affected cifs-utils version and the default cifs.spnego rule it comes with . Nominally, this is 6.14 and higher, but backports of other CVE fixes have introduced issues into older cifs-utils as well see thebelow Distro impact tables /posts/cifswitch/ distro-impact-tables - Unprivileged users must be able to create user and mount namespaces - SELinux/AppArmor/etc. policies that do not get in the way the defaults vary by distro/version, see the below Distro impact tables /posts/cifswitch/ distro-impact-tables Aside from applying the backported kernel patch https://github.com/torvalds/linux/commit/3da1fdf4efbc490041eb4f836bf596201203f8f2 , you can mitigate via any of the following: - Blocking the cifs module from loading if not required , assuming it’s not built-in - Removing cifs-utils if not required - Deleting/overriding the default cifs.spnego request-key rule if Kerberos auth is not required , e.g. adjusting for your keyctl path : cat /etc/request-key.d/cifs.spnego.conf <<'EOF' create cifs.spnego /usr/sbin/keyctl negate %k 30 %S EOF - Disabling unprivileged user namespaces You can use the released PoC https://github.com/manizada/CIFSwitch to validate the mitigations. Distro impact tables distro-impact-tables A very non-exhaustive list of systems tested. Stock-exploitable stock-exploitable Here, cifs-utils is installed by default and the default distro config LSMs/etc. does not stop the exploitation: | Target | Details | |---|---| | Linux Mint 21.3/22.3 Cinnamon | Exploitable with AppArmor active | | CentOS Stream 9 GNOME | Exploitable with SELinux enforcing | | Rocky Linux 9 Workstation | Exploitable with SELinux enforcing | | Kali Linux 2021.4/2022.4/2023.4/2024.4/2025.4/2026.1 headless | Exploitable with AppArmor active | | AlmaLinux 9.7 Workstation/Azure cloud image | Exploitable with SELinux enforcing | | SLES 15 SP7/SAP 15 SP7 | Exploitable with AppArmor active | | SLES SAP 16 | Exploitable with SELinux permissive | Stock-policy exploitable if cifs-utils is installed stock-policy-exploitable-if-cifs-utils-is-installed Exploitable under default distro config, but cifs-utils needs to be installed manually: | Target | Details | |---|---| | Ubuntu 18.04/20.04/22.04 Desktop/Server | Exploitable with AppArmor active | | Pop OS 22.04 Intel/24.04 Generic | Exploitable with AppArmor active | | Ubuntu 24.04 Desktop minimal/full and Server | Direct unshare is blocked by AppArmor userns policy; exploitable through aa-exec -p trinity ‘ | | Debian 11/12/13 netinst standard and GNOME/KDE/standard/XFCE | Exploitable with AppArmor active | | CentOS Stream 9 Cinnamon/KDE/MATE/XFCE | Exploitable with SELinux enforcing | | Rocky Linux 9 KDE/Workstation-Lite | Exploitable with SELinux enforcing | | openSUSE Leap 15.6 GNOME/KDE | Exploitable with AppArmor active | | Rocky Linux 8 GenericCloud | Exploitable with SELinux enforcing | | Oracle Linux 8/9 KVM | Exploitable with SELinux enforcing | | Amazon Linux 2023 KVM | Exploitable with SELinux permissive | Blocked by stock policy blocked-by-stock-policy The default distro config LSMs/etc. blocks exploitation even if cifs-utils is present: | Target | cifs-utils installed by default? | Details | |---|---|---| | Ubuntu 26.04 Desktop minimal/full and Server | no | Blocked by AppArmor userns policy by default; exploitable after AppArmor userns sysctls are relaxed | | Fedora 40/41/42/43/44 Workstation/Server | yes | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | | CentOS Stream 10 GNOME | yes | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | | CentOS Stream 10 KDE | no | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | | Rocky Linux 10 Workstation | yes | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | | Rocky Linux 10 KDE/Workstation-Lite | no | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | | AlmaLinux 10.1 Workstation/Azure cloud image recipe | yes | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | | Oracle Linux 10 KVM | no | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | | openSUSE Tumbleweed GNOME/KDE | yes | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | | openSUSE Leap 16.0 OEM GNOME/KDE | yes | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | | openSUSE Leap 16.0 Minimal-VM | no | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | | SLES 16 | yes | Blocked by SELinux enforcing by default; exploitable after setenforce 0 | Unaffected unaffected The two tested cases where cifs-utils is too old to be exploitable: | Target | cifs-utils installed by default? | Details | |---|---|---| | Amazon Linux 2 KVM | no | Unaffected by this PoC: cifs-utils 6.2 lacks the namespace-switch path | | Kali Linux 2019.4/2020.4 | yes | Unaffected by this PoC after userns relaxation: cifs-utils 6.9 lacks the namespace-switch path | Conclusion conclusion Ultimately, I was curious if the models could build non-trivial, multihop chains given the right tools. By producing and walking a semantic graph of security-relevant objects and properties – not so in the weeds that they got stifled by the low-level definitions, but not so abstract that they flailed aimlessly – the models arrived at: php forged userspace cifs.spnego request - root cifs.upcall is launched by the normal request-key rule - cifs.upcall trusts fake, not-actually-kernel-originating fields - fake pid + upcall target=app moves the root helper into an attacker-controlled namespace - NSS lookup happens before the final privilege drop - namespace-local NSS module is loaded inside the root helper, writing to sudoers.d While the primitives themselves are not groundbreaking, the chain is pretty neat, and is much more exciting than the earlier “drunk” ksmbd memory safety /posts/drinking-llms/ findings The graph-based approach was likely not strictly necessary, as simple Markdown memory may have sufficed, but it did seem to enable the agents to systematically burn down the potential exploit lanes with ease.