{"id":"CVE-2026-98980","kind":"vulnerability","aliases":["CVE-2026-98980"],"related":[],"title":"Linux kernel: refwalk path lookup can reveal files hidden under unlinked mounts","description":"In the Linux kernel, the following vulnerability has been resolved:\n\nnamei: don't reveal overmounted entries in refwalk\n\nIn rcuwalk the dentry is validated before it is accepted. step_into()\nstep_into() rechecks d_seq and __follow_mount_rcu() rechecks mount_lock.\nAn entry that gets unlinked in between causes the lookup to retry and\nmiss.\n\nA refwalk doesn't do this. lookup_fast() takes a reference on the hashed\ndentry and simply accepts it. So an unlink that happens after the\nreference was taken isn't seen by refwalk. That's fine for a simple\nfile. We just happened to open it before it was unlinked, no problem.\n\nFor a mountpoint and specifically a locked mountpoint it very much\nisn't. The unlink detaches all mounts and then removes the name. Any\nrefwalk that hasn't traversed the mounts yet simply reveals the\nunderlying entry. It's a very narrow window but it can be hit:\n\n  reads of a covered file in 60 s, 3 walkers, ~15000 unlinks\n    no widening            27\n    that step + 200 us   3741\n\nSee the appended patch for a more reliable reproducer.\n\nSo check the entry after step_into(). unlink(), rmdir() and rename()\nmark the dentry with dont_mount() before they detach the mounts and\nremove the dentry.\n\nSo a dentry that is marked with DCACHE_CANT_MOUNT and is unhashed by the\ntime its mounts were looked at is a name that was unlinked under the\nrefwalk. DCACHE_CANT_MOUNT is read after the mounts were looked up and\nit is set before they are detached by detach_mounts(). A refwalk that\nmissed the mounts will see DCACHE_CANT_MOUNT.\n\nPlain d_unlinked() is fine. A rename takes the dentry off its hash chain\nbut ___d_drop() leaves d_hash.pprev set. So only __d_drop() and a rename\nover the dentry unhash it. The only move that flips IS_ROOT splices in a\ndisconnected alias. That doesn't have DCACHE_CANT_MOUNT set.\n\nAdd the to step_into_slowpath(). \"..\" and LOOKUP_DOWN may legitimately\nland on an unhashed directory. A refwalk that crossed onto a mount has\npath.mnt different from nd->path.mnt. A dentry that a filesystem dropped\non its own (d_invalidate(), d_drop()) doesn't have the flag set and is\ntreated as before.\n\n  // SPDX-License-Identifier: GPL-2.0\n  /*\n   * unlink_covered: unlink a file that is a mountpoint in a detached copy of\n   * its mount, against walkers that open it through that copy.\n   *\n   * A is a tmpfs with the file f1 (content SECRET_F) and the plain file\n   * MARK_A. A' is an open_tree(OPEN_TREE_CLONE) copy of A with MARK_A bound\n   * on A'/f1, held through an O_PATH fd on its root once the tree fd is\n   * closed. Walkers open f1 through that fd while the driver unlinks A/f1,\n   * where nothing is mounted on it. A walker may read MARK_A or get ENOENT.\n   * A read of SECRET_F is a hit: the name was found after its mount was\n   * gone. The lockless walks use openat2(RESOLVE_CACHED).\n   *\n   * usage: unlink_covered [-t seconds] [-w walkers]\n   */\n  #ifndef _GNU_SOURCE\n  #define _GNU_SOURCE\n  #endif\n  #include <errno.h>\n  #include <fcntl.h>\n  #include <pthread.h>\n  #include <stdatomic.h>\n  #include <stdio.h>\n  #include <stdlib.h>\n  #include <string.h>\n  #include <unistd.h>\n  #include <sys/mount.h>\n  #include <sys/stat.h>\n  #include <sys/syscall.h>\n  #include <linux/openat2.h>\n\n  #ifndef __NR_open_tree\n  #define __NR_open_tree 428\n  #endif\n  #ifndef __NR_move_mount\n  #define __NR_move_mount 429\n  #endif\n  #ifndef __NR_openat2\n  #define __NR_openat2 437\n  #endif\n  #ifndef OPEN_TREE_CLONE\n  #define OPEN_TREE_CLONE 1\n  #endif\n  #ifndef OPEN_TREE_CLOEXEC\n  #define OPEN_TREE_CLOEXEC O_CLOEXEC\n  #endif\n  #ifndef MOVE_MOUNT_F_EMPTY_PATH\n  #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004\n  #endif\n\n  #define WORK \"/tmp/uc\"\n  #define ADIR WORK \"/A\"\n\n  static int duration = 60, nwalkers = 3;\n  static atomic_int stop, writer_waiting;\n  static pthread_rwlock_t cur_lock = PTHREAD_RWLOCK_INITIALIZER;\n  static int cur_fd = -1;\t\t/* the root of A', -1 while there is none */\n  static atomic_long n_unlink, n_walk, n_mark, n_enoent, n_othe\n---truncated---","summary":"A race in the Linux kernel's path lookup (namei) lets a refwalk that is already holding a dentry reference reach an entry whose mount was detached by a concurrent unlink, so it can read the underlying file instead of the mounted one. The sources describe a local reproducer that reads a covered file through an O_PATH-held cloned mount while another thread unlinks the mountpoint.","severity":"low","cvss":null,"epss":0.00209,"exploited":false,"exploited_at":null,"products":[],"ecosystems":[],"tags":["linux-kernel","multi-user-hosts","runtime","self-hosted"],"not_affected_if":["no-untrusted-local-users","single-user-host"],"how_to_check":"- `uname -r` and compare against the fixed kernel version in the vendor advisory (unknown in the sources)\n- Check whether unprivileged users can create mount namespaces or call open_tree/move_mount (unknown in the sources)","fix":"- Install the kernel update from your distribution that includes the upstream namei commit adding a DCACHE_CANT_MOUNT check after step_into() (version unknown in the sources)\n- Reboot into the patched kernel","status":"open","published_at":"2026-10-23T13:17:19+00:00","first_seen_at":"2026-10-23T21:57:33+00:00","triaged_at":"2026-10-25T04:56:18+00:00","updated_at":"2026-10-25T12:38:50+00:00","category":"runtime","confidence":0.6,"triage":{"at":"2026-10-25T04:56:18Z","model":"claude-haiku-5-5","output":{"fix":"- Install the kernel update from your distribution that includes the upstream namei commit adding a DCACHE_CANT_MOUNT check after step_into() (version unknown in the sources)\n- Reboot into the patched kernel","reach":"local","title":"Linux kernel: refwalk path lookup can reveal files hidden under unlinked mounts","impact":"low","summary":"A race in the Linux kernel's path lookup (namei) lets a refwalk that is already holding a dentry reference reach an entry whose mount was detached by a concurrent unlink, so it can read the underlying file instead of the mounted one. The sources describe a local reproducer that reads a covered file through an O_PATH-held cloned mount while another thread unlinks the mountpoint.","category":"runtime","nicknames":[],"who_cares":["linux-kernel","self-hosted","multi-user-hosts"],"confidence":0.6,"needs_auth":"yes","worst_case":"local user reads files hidden beneath a mount","analyst_note":"Confirm the affected kernel version ranges and fixed releases from NVD or the distribution advisories; sources give no version data.","how_to_check":"- `uname -r` and compare against the fixed kernel version in the vendor advisory (unknown in the sources)\n- Check whether unprivileged users can create mount namespaces or call open_tree/move_mount (unknown in the sources)","needs_analyst":true,"not_affected_if":["single-user-host","no-untrusted-local-users"],"who_is_affected":"The flaw is in the kernel's path walk for mountpoints that are unlinked while a lookup is in progress, so it matters most on multi-user hosts where untrusted local code can create mounts and race file unlinks. Single-user machines and systems without untrusted local users are lower risk. Sources do not state whether default configurations are exposed.","user_interaction":"no","needs_special_setup":"unknown","severity_if_unknown":"low"},"cost_usd":0.000832,"input_tokens":5003,"output_tokens":663,"severity_before":"unknown"},"analyst_status":null,"analyst_note":null,"search":"'-1':733C,738C '-2.0':483C '-2026':14A,17A '-98980':15A,18A '/a':707C '/f1':542C '/tmp/uc':703C '0x00000004':699C '1':674C '15000':265C '200':272C '27':269C '3':263C,713C '3741':274C '428':646C '429':656C '437':664C '60':261C,711C 'a/f1':568C 'accept':125C,171C 'add':425C 'adir':705C 'alia':416C 'alreadi':45B 'anoth':93B 'append':277C 'atom':715C,744C 'bound':539C 'cach':607C 'cant':318C,341C,371C,422C 'caus':147C 'chain':386C 'check':285C 'cloexec':679C,683C,685C 'clone':90B,532C,669C,673C 'close':557C 'concurr':59B 'content':516C 'copi':496C,507C,533C 'cover':82B,258C,485C,610C 'cross':444C 'cur':724C,731C 'cve':13A,16A 'd':131C,374C,388C,395C,463C,465C 'd_hash.pprev':391C 'dcach':317C,340C,370C,421C 'defin':618C,642C,652C,661C,670C,680C,693C,701C,704C 'dentri':48B,119C,168C,297C,309C,312C,382C,402C,455C 'describ':75B 'detach':56B,222C,303C,358C,360C,495C 'differ':450C 'directori':440C 'disconnect':415C 'doesn':156C,418C,467C 'dont':299C 'driver':566C 'drop':389C,396C,459C,466C 'durat':710C 'empti':691C,697C 'endif':621C,647C,657C,665C,675C,686C,700C 'enoent':583C,753C 'entri':52B,113C,141C,243C,287C 'f':518C,588C,690C,696C 'f1':515C,560C 'fast':161C 'fd':548C,555C,563C,732C 'file':8A,67B,83B,194C,259C,488C,514C,522C 'filesystem':458C 'fine':190C,377C 'flag':471C 'flip':409C 'follow':103C,134C 'found':595C 'get':143C,582C 'gnu':616C,619C 'gone':600C 'gpl':482C 'happen':177C,197C 'hash':167C,385C 'hasn':233C 'held':89B,543C 'hidden':9A 'hit':254C,591C 'hold':46B 'host':25B,29B 'identifi':481C 'ifndef':615C,638C,648C,658C,666C,676C,687C 'includ':622C,623C,624C,625C,626C,627C,628C,629C,630C,632C,634C,636C 'initi':728C 'instead':68B 'int':709C,716C,730C 'invalid':464C 'isn':183C,218C 'kernel':2A,21B,35B,101C 'land':436C 'leav':390C 'legitim':435C 'let':40B 'licens':480C 'linux':1A,20B,34B,100C 'linux-kernel':19B 'linux/openat2.h':637C 'local':77B 'lock':139C,213C,725C 'lockless':602C 'long':745C 'look':329C,349C 'lookup':5A,38B,149C,160C,432C 'mark':295C,315C,523C,537C,579C,751C 'may':434C,577C 'miss':153C,365C 'mount':12A,54B,71B,91B,135C,138C,224C,237C,300C,305C,319C,327C,342C,347C,361C,367C,372C,423C,447C,499C,572C,598C,651C,655C,689C,695C 'mountpoint':97B,209C,214C,492C 'move':407C,650C,654C,688C,694C 'much':217C 'multi':23B 'multi-user-host':22B 'n':746C,748C,750C,752C,754C 'name':229C,333C,593C 'namei':39B,108C 'narrow':248C 'nd':452C 'none':742C 'noth':570C 'nr':639C,643C,649C,653C,659C,662C 'nwalker':712C 'o':86B,546C,684C 'one':72B 'onto':445C 'open':199C,503C,528C,530C,559C,640C,644C,667C,671C,677C,681C 'openat2':605C,660C,663C 'oth':755C 'overmount':112C 'patch':278C 'path':4A,37B,88B,547C,692C,698C 'path-held':87B 'path.mnt':449C,453C 'plain':373C,521C 'problem':206C 'pthread':721C,726C 'race':31B 'rcu':136C 'rcuwalk':117C 'reach':50B 'read':64B,80B,255C,344C,578C,585C 'recheck':130C,137C 'refer':49B,164C,180C 'refwalk':3A,42B,115C,155C,187C,231C,339C,363C,442C 'reliabl':282C 'remov':227C,307C 'renam':294C,379C,399C 'reproduc':78B,283C 'resolv':107C,606C 'retri':151C 'reveal':7A,111C,240C 'rmdir':292C 'root':411C,551C,735C 'runtim':26B 'rwlock':722C,727C 'second':612C 'secret':517C,587C 'see':275C,369C 'seen':185C 'self':28B 'self-host':27B 'seq':132C 'set':354C,392C,424C,472C 'simpl':193C 'simpli':170C,239C 'slowpath':430C 'sourc':74B,617C,620C 'spdx':479C 'spdx-license-identifi':478C 'specif':211C 'splice':412C 'static':708C,714C,720C,729C,743C 'step':126C,128C,271C,289C,428C 'stop':717C 'sys/mount.h':631C 'sys/stat.h':633C 'sys/syscall.h':635C 'take':162C,380C 'taken':182C 'thread':94B 'time':325C 'tmpfs':511C 'travers':235C 'treat':475C 'tree':529C,531C,554C,641C,645C,668C,672C,678C,682C 'truncat':756C 'under':66B,242C 'unhash':322C,403C,439C 'unlink':11A,60B,95B,144C,175C,204C,221C,266C,291C,336C,375C,484C,486C,567C,609C,747C 'us':273C 'usag':608C 'use':604C 'user':24B 'valid':121C 'vulner':104C 'w':613C 'wait':719C 'walk':603C,749C 'walker':264C,501C,558C,576C,614C 'whose':53B 'widen':268C 'window':249C 'work':702C,706C 'writer':718C 'yet':238C","nicknames":[],"mitigations":[],"who_affected":"The flaw is in the kernel's path walk for mountpoints that are unlinked while a lookup is in progress, so it matters most on multi-user hosts where untrusted local code can create mounts and race file unlinks. Single-user machines and systems without untrusted local users are lower risk. Sources do not state whether default configurations are exposed.","tier":"C","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-23T13:17:19+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"nvd","external_id":"CVE-2026-98980","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-98980","severity":null,"published_at":"2026-10-23T13:17:19+00:00","modified_at":"2026-10-23T13:17:19+00:00","first_seen_at":"2026-10-23T21:57:33+00:00","fetched_at":"2026-10-23T22:29:03+00:00","note":null},{"source":"epss","external_id":"CVE-2026-98980","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-98980","severity":null,"published_at":null,"modified_at":null,"first_seen_at":"2026-10-25T12:38:50+00:00","fetched_at":"2026-10-25T12:38:50+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}