Mounts, superblocks, propagation, and namespaces

Explain mounts, propagation and filesystem views across mount namespaces.

15 min readChapter 38 of 168Intermediate → advanced
In this chapter

Before you start#

Read buffered and direct I/O. The examples use Linux v6.12 source and a disposable Linux guest. Namespace creation can be disabled by distribution policy; lack of permission is an expected possible result. The tag is a historical baseline.

Learning goals#

Distinguish filesystem instance, mount attachment and mount namespace; explain propagation and namespace copying; perform a guest-only namespace observation without touching host mounts.

Source walkthrough#

Mounts, superblocks, propagation, and namespaces
Mounts, superblocks, propagation, and namespacesnamespace A: mount tree view → mount attachment A: path in A (contains). namespace B: copied mount tree view → mount attachment B: path in B (contains). namespace A: mount tree view → superblock: underlying filesystem instance (refers to). namespace B: copied mount tree view → superblock: underlying filesystem instance (may refer to same instance)containscontainsrefers tomay refer to sameinstancenamespace Amount tree viewnamespace Bcopied mount tree viewmount attachment Apath in Amount attachment Bpath in Bsuperblockunderlying filesysteminstance
namespace Amount tree view
mount attachment Apath in A
namespace Bcopied mount tree view
mount attachment Bpath in B
namespace Amount tree view
superblockunderlying filesystem instance
namespace Bcopied mount tree view
superblockunderlying filesystem instance
Read the text version
  • namespace A: mount tree view → mount attachment A: path in A (contains)
  • namespace B: copied mount tree view → mount attachment B: path in B (contains)
  • namespace A: mount tree view → superblock: underlying filesystem instance (refers to)
  • namespace B: copied mount tree view → superblock: underlying filesystem instance (may refer to same instance)
Conceptual model. Conceptual Linux v6.12 source model, not a captured trace; configuration and errors affect the path.

A superblock describes a filesystem instance: it carries filesystem operations, state and a root dentry. A mount attaches a filesystem root or subtree at a point in a mount tree. The same superblock can be reached through multiple mount attachments with different mount-specific properties. A mount namespace contains a view of a mount tree; it is not a copy of every underlying file. A path consists of a mount plus a dentry because the same dentry can be visible through more than one attachment. Source definitions appear in include/linux/fs.h and include/linux/mount.h.

A new filesystem mount is built through a filesystem context and then attached. In fs/namespace.c, do_new_mount obtains a context, parses options, obtains a tree and calls do_new_mount_fc to install it. Filesystem type, privilege, options, namespace ownership and security checks affect success. On error it must release the context and any acquired resources; merely allocating a context does not attach a mount.

copy_mnt_ns shows how task creation can preserve or create a view (GPL-2.0-only; other code omitted):

c
	if (likely(!(flags & CLONE_NEWNS))) {
		get_mnt_ns(ns);
		return ns;
	}

Without CLONE_NEWNS, the caller receives a reference to the existing namespace. With the flag, the function allocates a new namespace and copies mount-tree topology:

c
	new = copy_tree(old, old->mnt.mnt_root, copy_flags);
	if (IS_ERR(new)) {
		namespace_unlock();

The remainder of that error branch frees namespace bookkeeping and returns the error. A copy failure therefore does not publish a partially constructed namespace. Copying mount objects doesn't clone the files themselves. Whether future mount events propagate depends on propagation relationships and namespace rules, not just on the existence of the copy.

The sequence below is a teaching model of the copy path, showing topology and not file contents.

copy_mnt_ns with CLONE_NEWNS
copy_mnt_ns with CLONE_NEWNS1. unshare or clone (CLONE_NEWNS) to copy_mnt_ns: request a copied mount namespace. 2. copy_mnt_ns to copy_tree: first pass: copy mount topology. 3. copy_tree to copy_mnt_ns (return): copied tree, or error. 4. copy_mnt_ns to new mnt_namespace: second pass: add copies, re-point task root and pwd. 5. new mnt_namespace to unshare or clone (CLONE_NEWNS) (return): namespace returned to the caller.unshare or clone (CLONE_NEWNS)copy_mnt_nscopy_treenew mnt_namespacerequest a copiedmount namespacefirst pass: copymount topologycopied tree, orerrorsecond pass: add copies, re-point task rootand pwdnamespace returned to the caller
  1. unshare or clone (CLONE_NEWNS)copy_mnt_ns

    request a copied mount namespace

  2. copy_mnt_nscopy_tree

    first pass: copy mount topology

  3. copy_treecopy_mnt_ns

    Return. copied tree, or error

  4. copy_mnt_nsnew mnt_namespace

    second pass: add copies, re-point task root and pwd

  5. new mnt_namespaceunshare or clone (CLONE_NEWNS)

    Return. namespace returned to the caller

Time runs downward. Dashed arrows show returns.

Read the text version
  1. unshare or clone (CLONE_NEWNS) → copy_mnt_ns: request a copied mount namespace
  2. copy_mnt_ns → copy_tree: first pass: copy mount topology
  3. copy_tree → copy_mnt_ns (return): copied tree, or error
  4. copy_mnt_ns → new mnt_namespace: second pass: add copies, re-point task root and pwd
  5. new mnt_namespace → unshare or clone (CLONE_NEWNS) (return): namespace returned to the caller
Conceptual model. Teaching model of copy_mnt_ns in Linux v6.12 (source map). It copies mount topology, not file contents, and omits locking details.

Propagation and a concrete example#

A shared mount belongs to a peer group: eligible mount and unmount events propagate to peers. A slave receives events from its master but does not propagate them back in the same way. A private mount does neither; an unbindable mount also cannot be bind-mounted. The details involve peer groups, subtrees, and privileges, not a simple global switch. A copied namespace that contains shared mounts can still participate in propagation; isolation requires intentionally considering propagation. See shared subtree documentation.

In copy_mnt_ns, v6.12, SPDX GPL-2.0-only (surrounding code omitted):

c
	/* First pass: copy the tree topology */
	copy_flags = CL_COPY_UNBINDABLE | CL_EXPIRE;
	if (user_ns != ns->user_ns)
		copy_flags |= CL_SHARED_TO_SLAVE;
	new = copy_tree(old, old->mnt.mnt_root, copy_flags);
	if (IS_ERR(new)) {

The first pass copies the mount topology with CL_COPY_UNBINDABLE and CL_EXPIRE, and adds CL_SHARED_TO_SLAVE only when the new namespace has a different user namespace. Propagation of the copied mounts therefore depends on these flags, not on the copy alone. The excerpt does not show what copy_tree does with each mount.

Imagine namespace A has /data attached to a filesystem, and B is created by copying the mount namespace. Initially both views refer to the same underlying filesystem contents. If a process writes /data/note, a reader in the other namespace may see that file subject to normal permissions and caching. That is not mount propagation: it is a shared filesystem object. If B makes its relevant mounts private and then attaches a new tmpfs at /data/extra, that mount event stays within the relevant private topology. A's /data/extra is not automatically the new tmpfs. The exact copy, mount and propagation operations can fail; namespace separation is not a complete security boundary.

Unmount detaches a mount from a namespace but open files and other references can keep objects alive. A busy unmount can fail; a lazy detach has different semantics and does not mean all users instantly stopped using the underlying filesystem. Never extrapolate a disposable guest's mount experiment to a host or production namespace.

A mount tree is a view, not a storage copy#

A bind mount attaches an existing subtree at another location. It creates an additional path through the mount tree; it does not copy the files into a new filesystem. Per-mount flags and superblock-wide state must be considered separately. Two attachments may share the same backing inode while differing in mount-level access restrictions. A remount or attribute change can fail because of permissions, filesystem support or locked attributes. A namespace does not make a process privileged over every filesystem it can see.

Mount propagation is best understood as a relationship between mounts, not as a property of a process. A shared peer group can forward mount events; a slave receives from a master without sending corresponding events back to that master. Creating a new namespace duplicates mount topology but can preserve relationships that allow propagation. A runtime that wants isolation therefore has to examine and set propagation deliberately within its permitted namespace. A private mount suppresses propagation at that mount, but making only one node private does not automatically explain the propagation state of all descendants. The recursive and nonrecursive operations have different scopes.

In a hypothetical private guest, create namespaces A and B from the same starting tree. A has /work, and B initially sees the same underlying /work. Create a normal file in B: A can observe the changed contents if both views reach that filesystem and permissions permit. Next attach a new filesystem at a private mountpoint in B: the new attachment changes B's mount tree, not necessarily A's. Remove the attachment in B: a process that already opened a file beneath it can retain references, subject to the unmount method and busy checks. This sequence demonstrates three independent concepts: shared file data, propagation of topology changes, and object lifetime after detachment.

The guest unshare exercise intentionally does not create a mount, so it proves only that a distinct namespace identifier can be obtained in that environment. Seeing different identifiers does not prove a propagation mode, filesystem duplication or confinement against an adversarial privileged task. Those claims require separate inspection or a separately designed disposable guest experiment.

How lookup and mount lifetimes meet#

When pathname lookup encounters a mountpoint, it must switch from the covered dentry in one mount to the root of the mounted tree. The covered directory still exists in the underlying filesystem, but it is obscured through that attachment while the mount is present. Another namespace or another mount path may expose a different view. The process's root and current working directory are themselves paths, so namespace creation and changes to a task's filesystem context have to preserve meaningful references. copy_mnt_ns receives new_fs for that wider task context; simply copying a list of path strings would not be enough.

In the same copy_mnt_ns (v6.12, SPDX GPL-2.0-only, surrounding code omitted), the second pass re-points the task's root and working directory:

c
		if (new_fs) {
			if (&p->mnt == new_fs->root.mnt) {
				new_fs->root.mnt = mntget(&q->mnt);
				rootmnt = &p->mnt;
			}
			if (&p->mnt == new_fs->pwd.mnt) {
				new_fs->pwd.mnt = mntget(&q->mnt);
				pwdmnt = &p->mnt;
			}
		}

Each copied mount that matches the task's root or working-directory mount receives a new reference, so the task's paths follow the copied tree. The excerpt omits the rest of the loop and the other per-mount work.

A superblock's root dentry and a mount's root can differ when a subtree is bind-mounted. Multiple mounts can refer to one superblock, and mount flags can differ by attachment. Conversely, two separate filesystem instances can happen to have similar paths while referring to unrelated storage. findmnt reports the current namespace's view; the same command in a different namespace can display a different tree. /proc/<pid>/mountinfo includes identifiers and propagation-related fields, but interpretation requires knowing the target process and namespace.

An attempted unmount of a busy filesystem can return an error. Open files, working directories, nested mounts and other references are examples that can complicate teardown. Lazy unmount detaches an attachment from the namespace and delays final cleanup until references are released; it is not a durability operation. A successful unmount also must not be conflated with a proof that every application checked its earlier writeback errors. Cleanup instructions for real experiments should specify which namespace owns each mount and should never guess a host device path.

User namespaces add an authority boundary to the lab: mapping the current user to root inside a new user namespace gives certain capabilities relative to that namespace, not global host root. The kernel may restrict which mounts or operations are permitted there, and distributions may disable unprivileged user namespaces. The presented observation avoids mounting entirely. If it fails, a student can still perform the source-reading task and report the policy-dependent failure as the result, rather than trying to override the boundary.

Mount objects, references, and the newer mount API#

The internal struct mount wraps a struct vfsmount and carries namespace/topology and propagation bookkeeping; struct mountpoint represents an attachment point, while mnt_namespace owns a mount-tree view. A struct path holds a dentry plus vfsmount reference. A superblock represents a filesystem instance and has its own active references, including s_active; it is not identical to one mount attachment. A bind mount attaches an existing subtree at another location, and a recursive bind includes eligible submounts according to its flags and rules. The new mount does not duplicate file contents. See fs/namespace.c, include/linux/mount.h, and fs/super.c.

Mount propagation is a graph of relationships. Shared mounts belong to peer groups; slave mounts receive events from a master without propagating them back in the same way; private mounts neither send nor receive peer propagation. A recursive operation and a single-mount operation have different scope. Read-only flags are another dimension: an attachment may be read-only while another view of the same backing filesystem is writable, subject to superblock and mount restrictions. A read-only mount is not a snapshot of file content and does not imply that another permitted path cannot change it.

The newer filesystem context interface separates steps: fsopen creates an fs_context, fsconfig supplies parameters and requests creation/reconfiguration, fsmount creates a detached mount, and move_mount can attach a mount into a namespace. mount_setattr can alter supported mount attributes under its permission and scope rules. Failure at each step requires closing the context or detached mount fd and releasing only the resources acquired; obtaining an fs_context is not yet a visible attachment. fs/fsopen.c and fs/namespace.c implement these boundaries. Idmapped mounts use a user-namespace mapping on supported filesystems and under relevant ownership/permission constraints; they alter the presented identity interpretation for that mount, not the on-disk UID values by rewriting every inode.

The flow below lists the newer mount API steps by their syscall entry points in v6.12.

Newer mount API steps
  1. fsopen

    creates an fs_context for a filesystem type

  2. fsconfig

    sets parameters and can request creation or reconfiguration

  3. fsmount

    returns a detached mount fd in fs/namespace.c

  4. move_mount

    attaches the detached mount in a namespace; checks may refuse it

  5. mount_setattr

    changes supported mount attributes under scope rules

Source map. Source map of the new mount API entry points in Linux v6.12 (fs/fsopen.c, fs/namespace.c). A failed step does not attach a mount, and each step releases only what it acquired.

In do_umount, after taking namespace and mount locks and checking locked state, v6.12 handles lazy detach as follows (GPL-2.0-only; surrounding conditions omitted):

c
	if (flags & MNT_DETACH) {
		if (mnt->mnt.mnt_flags & MNT_ONRB ||
		    !list_empty(&mnt->mnt_list))
			umount_tree(mnt, UMOUNT_PROPAGATE);

This detaches the applicable mount tree from the namespace view; the later mntput/reference cleanup is separate. An open file can retain a path and continue to hold references after a lazy detach, so the superblock and underlying objects need not be destroyed immediately. A normal unmount can return EBUSY when references or mounts prevent it. Lazy unmount is neither a forced close of all fds nor an fsync guarantee.

For a hypothetical private mount namespace B copied from A, both initially refer to the same underlying filesystem. B makes the relevant propagation private and attaches a separate mount under /work; A does not receive that mount event under those assumptions, though both can still see changes to shared underlying files through their other views. A process in B opens /work/file, then B lazily detaches the mount. The open fd can retain its referenced file/path while new path lookup no longer traverses that detached attachment. When the last relevant references go away, mount and superblock cleanup can proceed. This is a conceptual timeline; the existing unshare lab does not mount or detach anything.

Detached mounts and failure ownership#

A detached mount fd from fsmount is a reference to a mount not yet attached at a chosen path. move_mount can fail because of permissions, namespace relationships, invalid source or destination, or other checks. Failure does not justify treating the mount as installed; closing the fd releases the caller's reference under the mount API. A successful move changes the namespace topology but does not by itself synchronize filesystem data or copy its contents. mount_setattr has its own validation and can apply recursively only when the requested flags and operation allow it.

A mount and its superblock have separate reference lifetimes. A struct file can hold a struct path, which holds mount and dentry references. After lazy detach, the pathname may no longer be reachable through the old namespace location while that open file remains usable subject to filesystem and device state. The final mntput and superblock deactivation occur only under their reference and teardown rules. An fd retaining a mount is not a promise that an unplugged backing device will keep working. Do not use lazy unmount as a substitute for stopping I/O and checking synchronization errors.

Propagation is equally local to the relationship being changed. Making one mount private does not automatically make every descendant private unless the applicable recursive operation is requested and succeeds. A slave relationship can receive events from a master without propagating its own events back. The unshare observation in this chapter shows distinct namespace identifiers but does not inspect or alter propagation. A real mount exercise would require a disposable guest, explicit private propagation and a recovery procedure; none is run on the host here.

A detached mount is still an object with a lifetime: a failed attach must release the detached mount reference, while a successful attach transfers or retains references according to the operation's contract. s_active tracks active superblock references separately from a process's descriptor number. These facts explain why namespace visibility, mount release and filesystem shutdown can happen at different times.

An idmapped mount is also not a recursive ownership-changing command. It associates an identity mapping with a supported mount so operations through that view interpret ownership under the relevant rules. Another mount of the same filesystem can present a different view, and permission checks still apply. Filesystem support, user-namespace ownership and the mount API's validation determine whether the request succeeds; an unsupported or denied request must not be reported as an applied mapping.

Practice#

Source exercise: rg -n -A55 'struct mnt_namespace \*copy_mnt_ns' "$KERNEL_SRC/fs/namespace.c"; mark the reference-sharing branch, allocation, copy and failure unwind. Confirm the checkout tag is v6.12.

Inside a disposable guest, run the following as an unprivileged user. It creates a user and mount namespace only if the guest policy permits it; it does not mount anything:

sh
printf 'outer: '; readlink /proc/self/ns/mnt
unshare --user --map-root-user --mount sh -c '
  printf "inner: "; readlink /proc/self/ns/mnt
  printf "root view: "; findmnt -n -o TARGET /
'
printf 'outer again: '; readlink /proc/self/ns/mnt

The inner namespace identifier should differ from the outer one; the outer identifier should remain the same after the child exits. / should be reported as the root mount if findmnt is present. If unshare returns Operation not permitted, record that policy or configuration prevented the test; do not run it with host privileges as a workaround. Mount IDs and propagation settings vary. This observation does not establish that the underlying filesystem was copied or that all mounts are private. The child exits without persistent setup. Deliver a diagram separating mount-tree copying from file-content sharing, and include the observed identifiers or the permission failure.

Knowledge check#

  1. What is the difference between a superblock and a mount?

  2. What happens in copy_mnt_ns without CLONE_NEWNS?

  3. Why can a file write be visible in both copied namespaces without being a propagation event?

  4. Why might an unmount not immediately destroy all related objects?

  5. What can remain alive after MNT_DETACH removes a mount from a namespace view?

Answers#

  1. A superblock represents a filesystem instance; a mount is an attachment of a filesystem root or subtree in a mount tree.

  2. The function increments the namespace reference and returns the existing namespace instead of copying it.

  3. Both mount views may refer to the same underlying filesystem. Propagation concerns mount events, not ordinary file writes.

  4. Open files and other references can remain after a mount is detached; the object's lifetime is reference governed.

  5. An open file can retain path, mount and dentry references, keeping relevant objects alive until the references are released. Detachment is not a forced close or an fsync.

Checkpoint#

Save the diagram and namespace output or permitted-failure explanation. Pass when you distinguish shared content, namespace copying and shared mount propagation, and explain the copy_tree failure branch. Retry after reviewing the source and shared-subtree documentation. This is not certification.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.