---
id: CVE-2026-98980
title: 'namei: don''t reveal overmounted entries in refwalk'
summary: |-
  In the Linux kernel, the following vulnerability has been resolved:

  namei: don't reveal overmounted entries in refwalk

  In rcuwalk the dentry is validated before it is accepted. step_into()
  step_into() rechecks d_seq and __follow_mount_…
severity: high
cvss: 8.8
cvssVector: 'CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H'
cvssSource: cna
vendor: Linux
product: Linux
affected:
  - >-
    Linux >= 8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe <
    e2b10181d913b4efa3a8dc67a532f859aa5f95e0
  - >-
    Linux >= 8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe <
    5f97df8ca7fadf5b53954f50073445ba193902c0
  - >-
    Linux >= 8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe <
    b43ff2deef48260bfbdc0dcb520f7929bd3e4007
  - >-
    Linux >= 8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe <
    8189ab688f5a2c745dc90b63374da02f213d6994
  - Linux 3.18
published: '2026-10-23'
updated: '2026-10-24'
sourceUpdated: '2026-10-24T14:09:48.534Z'
source: CVEORG
sourceUrl: 'https://www.cve.org/CVERecord?id=CVE-2026-98980'
references:
  - url: 'https://git.kernel.org/stable/c/e2b10181d913b4efa3a8dc67a532f859aa5f95e0'
  - url: 'https://git.kernel.org/stable/c/5f97df8ca7fadf5b53954f50073445ba193902c0'
  - url: 'https://git.kernel.org/stable/c/b43ff2deef48260bfbdc0dcb520f7929bd3e4007'
  - url: 'https://git.kernel.org/stable/c/8189ab688f5a2c745dc90b63374da02f213d6994'
tags:
  - cve.org
epss: 0.00209
epssPercentile: 0.10505
ingestedAt: '2026-10-23T13:24:55.549Z'
---

## Overview

In the Linux kernel, the following vulnerability has been resolved:

namei: don't reveal overmounted entries in refwalk

In rcuwalk the dentry is validated before it is accepted. step_into()
step_into() rechecks d_seq and __follow_mount_rcu() rechecks mount_lock.
An entry that gets unlinked in between causes the lookup to retry and
miss.

A refwalk doesn't do this. lookup_fast() takes a reference on the hashed
dentry and simply accepts it. So an unlink that happens after the
reference was taken isn't seen by refwalk. That's fine for a simple
file. We just happened to open it before it was unlinked, no problem.

For a mountpoint and specifically a locked mountpoint it very much
isn't. The unlink detaches all mounts and then removes the name. Any
refwalk that hasn't traversed the mounts yet simply reveals the
underlying entry. It's a very narrow window but it can be hit:

  reads of a covered file in 60 s, 3 walkers, ~15000 unlinks
    no widening            27
    that step + 200 us   3741

See the appended patch for a more reliable reproducer.

So check the entry after step_into(). unlink(), rmdir() and rename()
mark the dentry with dont_mount() before they detach the mounts and
remove the dentry.

So a dentry that is marked with DCACHE_CANT_MOUNT and is unhashed by the
time its mounts were looked at is a name that was unlinked under the
refwalk. DCACHE_CANT_MOUNT is read after the mounts were looked up and
it is set before they are detached by detach_mounts(). A refwalk that
missed the mounts will see DCACHE_CANT_MOUNT.

Plain d_unlinked() is fine. A rename takes the dentry off its hash chain
but ___d_drop() leaves d_hash.pprev set. So only __d_drop() and a rename
over the dentry unhash it. The only move that flips IS_ROOT splices in a
disconnected alias. That doesn't have DCACHE_CANT_MOUNT set.

Add the to step_into_slowpath(). ".." and LOOKUP_DOWN may legitimately
land on an unhashed directory. A refwalk that crossed onto a mount has
path.mnt different from nd->path.mnt. A dentry that a filesystem dropped
on its own (d_invalidate(), d_drop()) doesn't have the flag set and is
treated as before.

  // SPDX-License-Identifier: GPL-2.0
  /*
   * unlink_covered: unlink a file that is a mountpoint in a detached copy of
   * its mount, against walkers that open it through that copy.
   *
   * A is a tmpfs with the file f1 (content SECRET_F) and the plain file
   * MARK_A. A' is an open_tree(OPEN_TREE_CLONE) copy of A with MARK_A bound
   * on A'/f1, held through an O_PATH fd on its root once the tree fd is
   * closed. Walkers open f1 through that fd while the driver unlinks A/f1,
   * where nothing is mounted on it. A walker may read MARK_A or get ENOENT.
   * A read of SECRET_F is a hit: the name was found after its mount was
   * gone. The lockless walks use openat2(RESOLVE_CACHED).
   *
   * usage: unlink_covered [-t seconds] [-w walkers]
   */
  #ifndef _GNU_SOURCE
  #define _GNU_SOURCE
  #endif
  #include <errno.h>
  #include <fcntl.h>
  #include <pthread.h>
  #include <stdatomic.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <sys/mount.h>
  #include <sys/stat.h>
  #include <sys/syscall.h>
  #include <linux/openat2.h>

  #ifndef __NR_open_tree
  #define __NR_open_tree 428
  #endif
  #ifndef __NR_move_mount
  #define __NR_move_mount 429
  #endif
  #ifndef __NR_openat2
  #define __NR_openat2 437
  #endif
  #ifndef OPEN_TREE_CLONE
  #define OPEN_TREE_CLONE 1
  #endif
  #ifndef OPEN_TREE_CLOEXEC
  #define OPEN_TREE_CLOEXEC O_CLOEXEC
  #endif
  #ifndef MOVE_MOUNT_F_EMPTY_PATH
  #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
  #endif

  #define WORK "/tmp/uc"
  #define ADIR WORK "/A"

  static int duration = 60, nwalkers = 3;
  static atomic_int stop, writer_waiting;
  static pthread_rwlock_t cur_lock = PTHREAD_RWLOCK_INITIALIZER;
  static int cur_fd = -1;		/* the root of A', -1 while there is none */
  static atomic_long n_unlink, n_walk, n_mark, n_enoent, n_othe
---truncated---

## Affected

- `Linux >= 8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe < e2b10181d913b4efa3a8dc67a532f859aa5f95e0`
- `Linux >= 8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe < 5f97df8ca7fadf5b53954f50073445ba193902c0`
- `Linux >= 8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe < b43ff2deef48260bfbdc0dcb520f7929bd3e4007`
- `Linux >= 8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe < 8189ab688f5a2c745dc90b63374da02f213d6994`
- `Linux 3.18`

## Remediation

Refer to the linked advisories for vendor-supplied fixes and affected version ranges.
