The Linux Virtual File System: One Interface, Many Filesystems
Understand Linux VFS path lookup, dentries, inodes, open-file descriptions, mounts, page cache, permissions and safe resolution across filesystems.
The Linux Virtual Filesystem Switch gives system calls a common object and operation model across local disk filesystems, network filesystems, pseudo-filesystems, FUSE, overlay mounts, and devices. Uniform calls do not imply uniform durability, locking, permissions, rename behavior, caching, error modes, or performance.
VFS is a kernel-internal framework that evolves; it is not a stable external-module ABI summarized by four permanent structs and one read function pointer.
A pathname is resolved in a process context
Resolution starts from the process root or current working directory, or a directory file descriptor for *at() calls. It walks components through a mount namespace, crossing mount points and optionally following symlinks, while applying execute/search permissions, LSM checks, and resolution rules.
The same string can reach different objects in another mount namespace, after chroot, under bind/overlay mounts, or with a changed working directory. Record namespace/root/cwd and directory fd when analyzing a path race.
Dentries cache names—including misses
A dentry represents a name relationship in a directory and can point to an inode. Negative dentries cache a name lookup that found no inode, avoiding repeated expensive misses. Rename, unlink, mount, revalidation, memory pressure, and network filesystem rules change dentry state.
The dcache accelerates lookup but is not an authorization cache administrators should edit. Reclaimable slab is not necessarily instantly reclaimable without cost.
Inodes represent filesystem objects, not names
An inode holds VFS metadata and connects to filesystem-specific state/operations. Multiple hard-link dentries can reference one inode; unlinking one name does not remove the object while another link or open reference remains. Filesystems synthesize inode numbers differently, and remote inode identity can have caveats.
The inode number alone is not globally unique. Combine filesystem/mount identity, generation where applicable, and stat metadata; paths remain race-prone.
File descriptors point to open file descriptions
A process fd table entry references a kernel struct file—the open file description—with status flags, current offset, credentials/context, mount+dentry path, and operations. dup() and fork() can share that same open description and offset; it is not necessarily one file object per process.
Independent open() calls create distinct open descriptions even for the same inode. Descriptor flags such as FD_CLOEXEC belong to the descriptor, while status flags such as O_APPEND belong to the open description.
Names can disappear while open files remain
After open(), rename or unlink can remove/change the pathname while the open description still references the object. /proc/<pid>/fd may show (deleted) yet reads/writes continue. This enables safe temporary files and atomic replacement, but can also hide disk space held by an unlinked open file.
Use lsof +L1 or scoped proc inspection to find such references; do not delete random descriptors or truncate live files as a space fix.
Mounts connect trees inside a namespace
A mount associates a filesystem tree with a mountpoint in one mount namespace. Bind mounts expose another subtree; overlayfs composes layers; automounts trigger lookup-time mounts; propagation controls event sharing across namespaces.
Use findmnt and /proc/<pid>/mountinfo, not /proc/mounts alone, when mount ID, parent, propagation, bind roots, and escaped paths matter. Never paste a mount command with /dev/sda1; verify UUID/device and filesystem first.
Superblocks represent mounted filesystem instances
The VFS superblock connects a filesystem instance to its operations, block or pseudo backing, state, and active mounts. Multiple mounts can refer to the same superblock with different mount flags/locations, while separate superblocks can expose similar underlying data depending on filesystem design.
Filesystem-wide sync/freeze/error state and mount-specific policy are not interchangeable. Inspect both filesystem and mount options.
Operation tables dispatch filesystem behavior
VFS invokes operation tables for inode, file, address-space, superblock, dentry, and other objects. Modern buffered I/O commonly uses iterator-based callbacks such as read_iter/write_iter, not the old simplified file->f_op->read() diagram. iomap, netfs, direct I/O and filesystem helpers further share implementation.
The common dispatch point does not make an NFS read, procfs generated file, ext4 extent, and FUSE request equivalent. Each backend can return distinct errors, blocking, consistency, and caching semantics.
Page cache integrates file I/O and memory
An address-space mapping associates cached pages/folios with file offsets. Buffered reads/writes and shared mappings can converge there; writeback calls filesystem methods to persist dirty data. Direct I/O seeks to bypass/minimize page cache but has alignment and coherence rules.
VFS provides common machinery, while filesystem and storage guarantees decide durability. write() success is not fsync() durability, and O_DIRECT is not O_SYNC.
Permission checks occur at multiple times
Path traversal checks directories; open checks requested access and flags; LSMs can mediate; later operations may apply file/inode-specific checks. An open descriptor can retain access after pathname permissions change, subject to operation and policy semantics.
TOCTOU arises when software checks a path then opens/renames it separately while an attacker changes components. Avoid pre-checks such as access() followed by open() as authorization. Perform the intended operation and handle its error.
openat2() constrains hostile path resolution
Linux openat2() adds a structured open_how and resolution flags such as preventing symlink traversal, mount crossing, magic links, or escape beneath a directory. These help safely resolve paths supplied by less-trusted code, but require careful feature probing and policy design.
Use a trusted directory fd, least flags, and current manual. RESOLVE_BENEATH/IN_ROOT are not substitutes for filesystem permissions, namespaces, and an LSM.
Rename and durability are filesystem contracts
Rename within one filesystem has atomic namespace semantics for many common cases, but cross-filesystem rename fails with EXDEV; applications must copy+sync+replace explicitly. Atomic visibility is not crash durability. Robust file replacement may require syncing file data/metadata and the containing directory.
Network, clustered, overlay, and FUSE filesystems can have different cache coherency, locking, and failure behavior. Test the exact mount, not just the same syscall on tmpfs.
Pseudo-filesystems are real VFS objects with side effects
procfs, sysfs, cgroupfs, tracefs, debugfs, and configfs implement VFS operations without ordinary persistent files. A read can generate live data or block; a write can tune kernel/hardware or create kernel objects.
Never recursively back up, checksum, or grep all virtual filesystems. Query documented ABI files and exclude pseudo-mounts from file scanners.
FUSE crosses into a userspace server
FUSE lets a userspace process implement filesystem operations through a kernel protocol. This enables SSH-backed, archive, encrypted, and application filesystems but introduces daemon lifecycle, request queues, permissions (allow_other policy), caching, timeout, and deadlock concerns.
If the server stalls, client syscalls can stall. Mount options and library behavior need threat-model review; a FUSE mount is not trusted because it uses ordinary paths.
Overlay filesystems complicate identity
Overlayfs presents merged upper/lower layers, with copy-up, whiteouts, opaque directories, and sometimes changing inode/metadata behavior. A container-visible file may originate from an image lower layer until first write copies it to upper storage.
Forensics must capture mount/layer configuration and inspect layers without mutating them. Hashing only the merged path can miss provenance and whiteout semantics.
Build a VFS acceptance matrix
Test path traversal, symlinks, hard links, rename/unlink while open, concurrent shared offsets, short I/O, ENOSPC, quota, read-only remount, network loss, fsync+directory durability, crash recovery, and namespace/bind/overlay views. Record kernel, filesystem/tool versions, mount options/IDs, block/network stack, and observer namespace.
The abstraction is understood when common application code handles backend-specific errors/guarantees, hostile path resolution is constrained, descriptors—not strings—anchor identity where possible, and recovery tests prove the promised durability on the actual filesystem.
Related:
- Understanding the Linux Page Cache and How Writeback Actually Works
- Linux Namespaces: The Kernel Primitive Behind Every Container
Sources: