Linux kernel: Mount namespace flaw allows removal of locked file cover
Summary
A flaw in the Linux kernel mount handling (fs/namespace.c) lets a local user who is root inside its own user namespace remove a lock-protected mount, such as a bind mount masking a file. As a result, the original content of the masked file can become readable. The attack requires a local account with low privileges and the ability to create user and mount namespaces, and it is not remotely exploitable.
Affected
- Linux kernel from commit c62a4766937edec2962d52e583276b459b739f2d (introduced in 7.1) up to, but not including, the fixed stable commits bd71d33923b9d6216bcee44331dd37142b0a8441 and 69fbe94f80bc423613fa18dae6656757423732a2
- Exact affected stable release numbers: Unknown.
Fixed in
- Fixed in stable commits 69fbe94f80bc423613fa18dae6656757423732a2 and bd71d33923b9d6216bcee44331dd37142b0a8441
- Fixed in Linux 7.2.13 and later 7.2.x; unaffected from 7.3 according to the CNA data
- Fixed release numbers on other stable branches: Unknown.
Workaround
- None documented.
- Restricting unprivileged user namespaces may reduce exposure, but the setting depends on the distribution and has not been verified for this CVE.
Exploitation
- OffSeq reports the CVE as not known to be exploited and not in CISA KEV.
- The CVE text contains a reproducer from the reporter, so a public proof of concept is effectively available.
- Not remotely exploitable; a local foothold with low privileges is required.
Our recommendation
- Inventory Linux hosts with several local users, CI runners, container or build hosts, and other systems where untrusted users have a shell.
- Check the kernel advisories of your distribution and install a kernel with the fix (Linux 7.2.13 or later, or the corresponding stable build).
- Restrict unprivileged user namespaces where they are not needed.
- Reboot after the kernel update so the fix becomes active.
Public exploits
PoC-in-GitHub, Metasploit and Exploit-DB list nothing for this CVE.
Research sources
- Linux stable commit 69fbe94f80bc423613fa18dae6656757423732a2 git.kernel.org
- Linux stable commit bd71d33923b9d6216bcee44331dd37142b0a8441 git.kernel.org
- Ubuntu CVE-2026-99170 ubuntu.com
- OSV BELL-CVE-2026-99170 osv.dev
- OffSeq radar CVE-2026-99170 radar.offseq.com
- Strix CVE-2026-99170 www.strix.ai
Researched and written up in English and German by an AI (Claude by Anthropic) based on NVD, CISA, EPSS and the sources listed. The vendor information is authoritative. Researched on 31 Oct 2026, 14:23.
Original description (NVD)
In the Linux kernel, the following vulnerability has been resolved: namespace: a mount that copy_tree() leaves out can't take over a lock Since commit c62a4766937e ("move_mount: transfer MNT_LOCKED") a mount can be moved beneath a locked mount. The lock moves from the old cover to the new mount which takes over the job of hiding what's under it and the old cover can be unmounted. That works as long as the new mount stays. But the caller picks the new mount and copy_tree() leaves a mount of a mount namespace file out of every copy made without CL_COPY_MNT_NS_FILE, together with everything mounted on it, and never looks at MNT_LOCKED. unshare(CLONE_NEWNS) and mount propagation make such copies. So a task that is root in its own user namespace gets rid of a locked cover on a file without a race and without touching the filesystem below it. It makes a child with a mount namespace of its own, binds the child's /proc/<pid>/ns/mnt beneath the cover with MOVE_MOUNT_BENEATH, which hands the lock to that bind, and calls unshare(CLONE_NEWNS). The new mount namespace has nothing on the file. That's the usual way to mask a single file, a bind of /dev/null or of an empty file. With a bind of an ordinary file the file stays covered. copy_tree() already refuses to leave out a locked unbindable mount with -EPERM since commit df7342b24018 ("mount: Don't allow copying MNT_UNBINDABLE|MNT_LOCKED mounts"). Do the same for a locked mount of a mount namespace file. That also covers a tree copied with CL_COPY_MNT_NS_FILE and attached into a mount namespace of another user namespace where lock_mnt_tree() locks it. Note that unshare(CLONE_NEWNS) and clone(CLONE_NEWNS) fail with EPERM from such a mount namespace from then on. The mount can't be left out and it can't be unmounted. And refuse to move a mount of a mount namespace file beneath a locked mount in the first place. Otherwise the task ends up with a mount it can't unmount and an unshare() that fails for good. Chris also found a second mount that goes away by itself, a clone of an automounted mount that shrink_submounts() took along with its parent. Commit f78ddcc87224 ("namespace: never expire a locked mount") took care of that one. Chris's reproducer is appended. Mode nsfile reads SECRET after the unshare() on 7.3-rc7, with this patch the move_mount() fails with EINVAL and the file stays covered. nsfile-control uses an ordinary file and reads MASK both ways. // SPDX-License-Identifier: GPL-2.0 /* * Get rid of a locked cover with MOVE_MOUNT_BENEATH. * * The "admin" (root) makes a tmpfs W: * W/D file, holds SECRET, covered by a bind of a file with MASK * W/Dd/inner file, holds SECRET, Dd is covered by a bind of a directory * whose "inner" holds MASK * W/own directory of uid 1000 with the file "mine" and the * directory "minedir", whose "inner" holds MINE * W/dbg debugfs, mode 755. dbg/tracing is an automount point * The "attacker" is uid 1000. It enters a new user namespace with a new mount * namespace, so W and both covers are locked there. * * usage: prog MODE * plain put a bind of own/mine beneath the cover of D, unmount the cover * nsfile put a bind of the mount namespace file of a child beneath the * cover of D, then unshare(CLONE_NEWNS) * nsfile-control the same with a bind of own/mine * shrink put a bind of the automounted tracefs beneath the cover of Dd, * unmount the cover, put a tmpfs beneath W, call umount(W), which fails * shrink-control the same with a bind of own/minedir * The environment variable AUTOMOUNT_DIR names another automount point for "shrink". */ #define _GNU_SOURCE #include <errno.h> #include <fcntl.h> #include <grp.h> #include <sched.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> ---truncated---
Sources
The tracker combines official, freely usable data sources – updated continuously:
- NVD (NIST) – CVEs, rating (CVSS), affected products
- CISA KEV – vulnerabilities confirmed by CISA as actively exploited
- CISA Vulnrichment – CISA assessment (SSVC) in the official CVE record
- FIRST EPSS – probability of exploitation
- Microsoft MSRC – Patch Tuesday: vulnerabilities, severity, updates
- PoC-in-GitHub – public proof-of-concept repositories
- Metasploit (Rapid7) – modules of the Metasploit Framework
- Exploit-DB (OffSec) – archive of public exploits (links only)
- Patch days: official vendor pages (linked per vendor) (Vendor patch days)
This product uses data from the NVD API but is not endorsed or certified by the NVD. EPSS data © FIRST.org.
Affected? We can help.
We check whether your systems are affected, install updates and close the gap – continuously with the System Health Checker if you wish.
