mount_namespaces(7)

๐ŸŒธ NAME ๐ŸŒธ

mount_namespaces โ€” bestie, this is your complete overview of Linux mount namespaces. It's a section 7 page, so it lives in the Miscellaneous Information Manual pile, and it's filed under mount_namespaces(7). ๐Ÿ’…


โœจ DESCRIPTION โœจ

Okay so, first things first: if you want the big-picture tour of namespaces in general, go read namespaces(7). This page is specifically about the mount flavour.

Mount namespaces give you isolation of the list of mounts that the processes in each namespace instance can see. ๐Ÿซถ So like, the processes sitting in each of the mount namespace instances each get their own distinct single-directory hierarchies. No sharing, no peeking, no cap.

The views you get from the /proc/pid/mounts, /proc/pid/mountinfo, and /proc/pid/mountstats files (all three are described over in proc(5), go look) match the mount namespace that the process with the PID pid is living in. And fr fr: all the processes chilling in the same mount namespace see the exact same view in those files. ๐Ÿ‘‘

A brand new mount namespace gets created with either clone(2) or unshare(2), using the CLONE_NEWNS flag. And the moment a new mount namespace pops into existence, its mount list gets initialized like this:

  • If the namespace was created with clone(2), the mount list of the child's namespace is a copy of the mount list in the parent process's mount namespace. ๐Ÿงฌ
  • If the namespace was created with unshare(2), the mount list of the new namespace is a copy of the mount list in the caller's previous mount namespace. ๐ŸŒ€

After that, any further modifications to the mount list (mount(2) and umount(2)) in either mount namespace will not (by default, anyway) affect the mount list seen in the other namespace. But โ€” and this is the plot twist โ€” see the discussion of shared subtrees right below. ๐Ÿ‘€


๐ŸŒˆ SHARED SUBTREES ๐ŸŒˆ

So after mount namespaces got implemented and everything, lived experience showed that the isolation they handed out was, in some cases, too much. ๐Ÿ’€ Like, imagine you want a freshly loaded optical disk to be available in all the mount namespaces โ€” you'd have to do a mount operation in every single namespace. Exhausting.

So for this use case (and for others), the shared subtree feature was introduced in Linux 2.6.15. This feature lets mount(2) and umount(2) events propagate automatically and in a controlled way between namespaces โ€” or, to be way more precise about it, between the mounts that are members of a peer group that are propagating events to one another. ๐Ÿฆ‹

Each mount gets marked (via mount(2)) as having one of these propagation types:

  • MS_SHARED โ€” this mount shares events with members of a peer group. mount(2) and umount(2) events immediately under this mount will propagate to the other mounts that are members of the peer group. And "propagate" here means the exact same mount(2) or umount(2) will automatically occur under all of the other mounts in the peer group. Conversely, mount(2) and umount(2) events that happen under peer mounts will propagate to this mount. It's a two-way group chat. ๐Ÿ’ฌ
  • MS_PRIVATE โ€” this mount is private; it does not have a peer group. mount(2) and umount(2) events do not propagate into or out of this mount. Do not disturb. ๐Ÿ™ˆ
  • MS_SLAVE โ€” mount(2) and umount(2) events propagate into this mount from a (master) shared peer group. But mount(2) and umount(2) events under this mount do not propagate to any peer. It listens, it doesn't post. ๐Ÿ‘‚

Note that a mount can be the slave of another peer group while at the same time sharing mount(2) and umount(2) events with a peer group of which it is a member. (More precisely: one peer group can be the slave of another peer group.) ๐ŸŽ€

  • MS_UNBINDABLE โ€” this is like a private mount, and on top of that, this mount can't be bind mounted. Attempts to bind mount this mount (mount(2) with the MS_BIND flag) will fail. ๐Ÿšซ

When a recursive bind mount (mount(2) with the MS_BIND and MS_REC flags) is performed on a directory subtree, any bind mounts within the subtree are automatically pruned (i.e., not replicated) when replicating that subtree to produce the target subtree. โœ‚๏ธ

If you want the whole discussion of which propagation type a new mount gets assigned, skip down to NOTES. โญ

The propagation type is a per-mount-point setting. So some mounts may be marked as shared (with each shared mount being a member of a distinct peer group), while others are private (or slaved, or unbindable). Mix and match, honestly.

Now, important: a mount's propagation type determines whether mount(2) and umount(2) of mounts immediately under the mount are propagated. So the propagation type does not affect propagation of events for grandchildren and further-removed descendant mounts. And what happens if the mount itself gets unmounted is determined by the propagation type that's in effect for the parent of the mount. iykyk. ๐ŸŒŸ

Members are added to a peer group when a mount is marked as shared and either:

  1. the mount is replicated during the creation of a new mount namespace; or
  2. a new bind mount is created from the mount.

In both of these cases, the new mount joins the peer group of which the existing mount is a member. ๐Ÿซ‚

A new peer group also gets created when a child mount is created under an existing mount that is marked as shared. In this case, the new child mount is also marked as shared, and the resulting peer group consists of all the mounts that are replicated under the peers of parent mounts. โœจ

A mount ceases to be a member of a peer group when either the mount is explicitly unmounted, or when the mount is implicitly unmounted because a mount namespace is removed (because it has no more member processes). Bye bestie. ๐Ÿ˜ญ

The propagation type of the mounts in a mount namespace can be discovered via the "optional fields" exposed in /proc/pid/mountinfo. (See proc(5) for the details of this file.) These tags can show up in the optional fields for a record in that file:

  • shared:X โ€” this mount is shared in peer group X. Every peer group has a unique ID that the kernel generates automatically, and all mounts in the same peer group will show the same ID. (These IDs are assigned starting from the value 1, and may be recycled when a peer group ceases to have any members.) ๐Ÿ”
  • master:X โ€” this mount is a slave to shared peer group X.
  • propagate_from:X (since Linux 2.6.26) โ€” this mount is a slave and receives propagation from shared peer group X. This tag will always appear in conjunction with a master:X tag. Here, X is the closest dominant peer group under the process's root directory. If X is the immediate master of the mount, or if there is no dominant peer group under the same root, then only the master:X field is present and not the propagate_from:X field. For further details, see below. ๐Ÿ‘‡
  • unbindable โ€” this is an unbindable mount.

If none of the above tags is present, then this is a private mount. ๐Ÿ•ต๏ธ


๐ŸŽ€ MS_SHARED and MS_PRIVATE example ๐ŸŽ€

Suppose that on a terminal in the initial mount namespace, we mark one mount as shared and another as private, and then view the mounts in /proc/self/mountinfo:

sh1# mount --make-shared /mntS
sh1# mount --make-private /mntP
sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
77 61 8:17 / /mntS rw,relatime shared:1
83 61 8:15 / /mntP rw,relatime

From that /proc/self/mountinfo output, we can see that /mntS is a shared mount in peer group 1, and that /mntP has no optional tags, which tells us it's a private mount. The first two fields in each record in this file are the unique ID for this mount, and the mount ID of the parent mount. We can dig even further into this file and see that the parent mount of /mntS and /mntP is the root directory, /, which is mounted as private:

sh1# cat /proc/self/mountinfo | awk '$1 == 61' | sed 's/ - .*//'
61 0 8:2 / / rw,relatime

On a second terminal, we create a new mount namespace, where we run a second shell and inspect the mounts:

$ PS1='sh2# ' sudo unshare -m --propagation unchanged sh
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
222 145 8:17 / /mntS rw,relatime shared:1
225 145 8:15 / /mntP rw,relatime

The new mount namespace received a copy of the initial mount namespace's mounts. These new mounts keep the same propagation types, but they have unique mount IDs. (The --propagation unchanged option stops unshare(1) from marking all mounts as private when it creates a new mount namespace โ€” which, btw, it does by default.) ๐Ÿ’…

In the second terminal, we then create submounts under each of /mntS and /mntP and inspect the set-up:

sh2# mkdir /mntS/a
sh2# mount /dev/sdb6 /mntS/a
sh2# mkdir /mntP/b
sh2# mount /dev/sdb7 /mntP/b
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
222 145 8:17 / /mntS rw,relatime shared:1
225 145 8:15 / /mntP rw,relatime
178 222 8:22 / /mntS/a rw,relatime shared:2
230 225 8:23 / /mntP/b rw,relatime

From the above, you can see that /mntS/a was created as shared (it inherited that setting from its parent mount) and /mntP/b was created as a private mount. Inherited the vibes, basically. ๐Ÿงฌ

Returning to the first terminal and inspecting the set-up, we see that the new mount created under the shared mount /mntS propagated to its peer mount (in the initial mount namespace), but the new mount created under the private mount /mntP did not propagate:

sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
77 61 8:17 / /mntS rw,relatime shared:1
83 61 8:15 / /mntP rw,relatime
179 77 8:22 / /mntS/a rw,relatime shared:2

๐Ÿ’œ MS_SLAVE example ๐Ÿ’œ

Making a mount a slave lets it receive propagated mount(2) and umount(2) events from a master shared peer group, while preventing it from propagating events to that master. This is useful if we want to (say) receive a mount event when an optical disk is mounted in the master shared peer group (in another mount namespace), but want to prevent mount(2) and umount(2) events under the slave mount from causing side effects in other namespaces. It's giving "I'll read your texts but I'm not replying." ๐Ÿ“ต

We can demonstrate the effect of slaving by first marking two mounts as shared in the initial mount namespace:

sh1# mount --make-shared /mntX
sh1# mount --make-shared /mntY
sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
132 83 8:23 / /mntX rw,relatime shared:1
133 83 8:22 / /mntY rw,relatime shared:2

On a second terminal, we create a new mount namespace and inspect the mounts:

sh2# unshare -m --propagation unchanged sh
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
168 167 8:23 / /mntX rw,relatime shared:1
169 167 8:22 / /mntY rw,relatime shared:2

In the new mount namespace, we then mark one of the mounts as a slave:

sh2# mount --make-slave /mntY
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
168 167 8:23 / /mntX rw,relatime shared:1
169 167 8:22 / /mntY rw,relatime master:2

So from that output up there, we can see that /mntY is now a slave mount โ€” it's receiving propagation events from the shared peer group with the ID 2. Adorable. ๐Ÿ’…

Still kicking it in the new namespace, we create submounts under both /mntX and /mntY:

sh2# mkdir /mntX/a
sh2# mount /dev/sda3 /mntX/a
sh2# mkdir /mntY/b
sh2# mount /dev/sda5 /mntY/b

Then we vibe check the mounts in the new mount namespace, and hmm โ€” /mntX/a got created as a new shared mount (no cap, it inherited the "shared" setting from its parent mount) while /mntY/b got created as a private mount:

sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
168 167 8:23 / /mntX rw,relatime shared:1
169 167 8:22 / /mntY rw,relatime master:2
173 168 8:3 / /mntX/a rw,relatime shared:3
175 169 8:5 / /mntY/b rw,relatime

Now we go back to the first terminal (the initial mount namespace), and look at that โ€” the mount /mntX/a propagated to the peer (the shared /mntX), but the mount /mntY/b did not propagate:

sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
132 83 8:23 / /mntX rw,relatime shared:1
133 83 8:22 / /mntY rw,relatime shared:2
174 132 8:3 / /mntX/a rw,relatime shared:3

And now we create a new mount under /mntY in the first shell:

sh1# mkdir /mntY/c
sh1# mount /dev/sda1 /mntY/c
sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
132 83 8:23 / /mntX rw,relatime shared:1
133 83 8:22 / /mntY rw,relatime shared:2
174 132 8:3 / /mntX/a rw,relatime shared:3
178 133 8:1 / /mntY/c rw,relatime shared:4

When we check the mounts in the second mount namespace this time, the new mount has been propagated to the slave mount, and that new mount is itself a slave mount (slaving to peer group 4):

sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
168 167 8:23 / /mntX rw,relatime shared:1
169 167 8:22 / /mntY rw,relatime master:2
173 168 8:3 / /mntX/a rw,relatime shared:3
175 169 8:5 / /mntY/b rw,relatime
179 169 8:1 / /mntY/c rw,relatime master:4

โœจ MS_UNBINDABLE example โœจ

One of the main character reasons unbindable mounts exist at all is to dodge the "mount explosion" problem that happens when you keep bind mounting a higher-level subtree at a lower-level mount, over and over. ๐Ÿ’€ The problem is literally illustrated by this shell session.

Say we've got a system with these mounts:

# mount | awk '{print $1, $2, $3}'
/dev/sda1 on /
/dev/sdb6 on /mntX
/dev/sdb7 on /mntY

And suppose (hypothetically, bestie) we wanna recursively bind mount the root directory under several users' home directories. We do it for the first user, and inspect the mounts:

# mount --rbind / /home/cecilia/
# mount | awk '{print $1, $2, $3}'
/dev/sda1 on /
/dev/sdb6 on /mntX
/dev/sdb7 on /mntY
/dev/sda1 on /home/cecilia
/dev/sdb6 on /home/cecilia/mntX
/dev/sdb7 on /home/cecilia/mntY

Now we repeat the operation for the second user, and boom โ€” the explosion problem starts showing its whole face:

# mount --rbind / /home/henry
# mount | awk '{print $1, $2, $3}'
/dev/sda1 on /
/dev/sdb6 on /mntX
/dev/sdb7 on /mntY
/dev/sda1 on /home/cecilia
/dev/sdb6 on /home/cecilia/mntX
/dev/sdb7 on /home/cecilia/mntY
/dev/sda1 on /home/henry
/dev/sdb6 on /home/henry/mntX
/dev/sdb7 on /home/henry/mntY
/dev/sda1 on /home/henry/home/cecilia
/dev/sdb6 on /home/henry/home/cecilia/mntX
/dev/sdb7 on /home/henry/home/cecilia/mntY

Under /home/henry, we didn't just recursively get the /mntX and /mntY mounts โ€” we also got the recursive mounts of those directories under /home/cecilia that were created in the previous step. ๐Ÿ˜ญ And the moment you repeat the step for a third user, it becomes painfully obvious that this explosion is exponential in nature:

# mount --rbind / /home/otto
# mount | awk '{print $1, $2, $3}'
/dev/sda1 on /
/dev/sdb6 on /mntX
/dev/sdb7 on /mntY
/dev/sda1 on /home/cecilia
/dev/sdb6 on /home/cecilia/mntX
/dev/sdb7 on /home/cecilia/mntY
/dev/sda1 on /home/henry
/dev/sdb6 on /home/henry/mntX
/dev/sdb7 on /home/henry/mntY
/dev/sda1 on /home/henry/home/cecilia
/dev/sdb6 on /home/henry/home/cecilia/mntX
/dev/sdb7 on /home/henry/home/cecilia/mntY
/dev/sda1 on /home/otto
/dev/sdb6 on /home/otto/mntX
/dev/sdb7 on /home/otto/mntY
/dev/sda1 on /home/otto/home/cecilia
/dev/sdb6 on /home/otto/home/cecilia/mntX
/dev/sdb7 on /home/otto/home/cecilia/mntY
/dev/sda1 on /home/otto/home/henry
/dev/sdb6 on /home/otto/home/henry/mntX
/dev/sdb7 on /home/otto/home/henry/mntY
/dev/sda1 on /home/otto/home/henry/home/cecilia
/dev/sdb6 on /home/otto/home/henry/home/cecilia/mntX
/dev/sdb7 on /home/otto/home/henry/home/cecilia/mntY

The glow-up fix for the mount explosion problem in that whole scenario? Make each of the new mounts unbindable. The effect is that recursive mounts of the root directory won't replicate the unbindable mounts. We make such a mount for the first user:

# mount --rbind --make-unbindable / /home/cecilia

Before we go any further, here's us proving that unbindable mounts are, indeed, unbindable:

# mkdir /mntZ
# mount --bind /home/cecilia /mntZ
mount: wrong fs type, bad option, bad superblock on /home/cecilia,
       missing codepage or helper program, or other error

       In some cases useful info is found in syslog - try
       dmesg | tail or so.

Now we create unbindable recursive bind mounts for the other two users:

# mount --rbind --make-unbindable / /home/henry
# mount --rbind --make-unbindable / /home/otto

And when we inspect the mount list, there's been no explosion of mounts, because those unbindable mounts weren't replicated under each user's directory. Periodt:

# mount | awk '{print $1, $2, $3}'
/dev/sda1 on /
/dev/sdb6 on /mntX
/dev/sdb7 on /mntY
/dev/sda1 on /home/cecilia
/dev/sdb6 on /home/cecilia/mntX
/dev/sdb7 on /home/cecilia/mntY
/dev/sda1 on /home/henry
/dev/sdb6 on /home/henry/mntX
/dev/sdb7 on /home/henry/mntY
/dev/sda1 on /home/otto
/dev/sdb6 on /home/otto/mntX
/dev/sdb7 on /home/otto/mntY

๐Ÿ” Propagation type transitions ๐Ÿ”

This next table shows what happens when you apply a new propagation type (i.e. mount --make-xxxx) to a mount that already has a propagation type. Rows = the propagation type the mount already has, columns = the new propagation settings. And for reasons of space, "private" is abbreviated as "priv" and "unbindable" as "unbind":

                     make-shared   make-slave      make-priv  make-unbind
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
shared       โ”‚shared        slave/priv [1]  priv       unbind
slave        โ”‚slave+shared  slave [2]       priv       unbind
slave+shared โ”‚slave+shared  slave           priv       unbind
private      โ”‚shared        priv [2]        priv       unbind
unbindable   โ”‚shared        unbind [2]      priv       unbind

A couple of details you need to clock about that table:

  • [1] If a shared mount is the only mount in its peer group, then making it a slave automatically makes it private. โœจ
  • [2] Slaving a nonshared mount has no effect on the mount.

๐ŸŽ€ Bind (MS_BIND) semantics ๐ŸŽ€

Suppose this command gets run:

mount --bind A/a B/b

Here, A is the source mount, B is the destination mount, a is a subdirectory path under the mount point A, and b is a subdirectory path under the mount point B. The propagation type of the resulting mount, B/b, depends on the propagation types of the mounts A and B โ€” all summarized in this table, bestie:

                          source(A)
                  shared  private    slave         unbind
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
dest(B)  shared   โ”‚shared  shared     slave+shared  invalid
         nonsharedโ”‚shared  private    slave         invalid

Heads up: a recursive bind of a subtree follows the exact same semantics as a bind operation on each individual mount in the subtree. (And unbindable mounts are automatically pruned at the target mount point. ๐Ÿช„)

For further details, see Documentation/filesystems/sharedsubtree.rst in the kernel source tree.


๐Ÿšš Move (MS_MOVE) semantics ๐Ÿšš

Suppose this command gets run:

mount --move A B/b

Here, A is the source mount, B is the destination mount, and b is a subdirectory path under the mount point B. The propagation type of the resulting mount, B/b, depends on the propagation types of the mounts A and B, and it's all in this table:

                          source(A)
                  shared  private    slave         unbind
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
dest(B)  shared   โ”‚shared  shared     slave+shared  invalid
         nonsharedโ”‚shared  private    slave         unbindable
Note: moving a mount that resides under a shared mount is invalid. Not it. ๐Ÿ™ˆ

For further details, see Documentation/filesystems/sharedsubtree.rst in the kernel source tree.


๐Ÿ“ฆ Mount semantics ๐Ÿ“ฆ

Suppose we use this command to create a mount:

mount device B/b

Here, B is the destination mount, and b is a subdirectory path under the mount point B. The propagation type of the resulting mount, B/b, follows the same rules as for a bind mount โ€” except that the propagation type of the source mount is always considered to be private.


๐Ÿงน Unmount semantics ๐Ÿงน

Suppose we use this command to tear a mount down:

umount A

Here, A is a mount on B/b, where B is the parent mount and b is a subdirectory path under the mount point B. If B is shared, then all most-recently-mounted mounts at b on mounts that receive propagation from mount B and that don't have submounts under them are unmounted too. The whole little friend group goes. ๐Ÿ’…


๐Ÿ” The /proc/pid/mountinfo propagate_from tag ๐Ÿ”

The propagate_from:X tag shows up in the optional fields of a /proc/pid/mountinfo record whenever a process can't see a slave's immediate master โ€” like, the pathname of the master isn't reachable from the filesystem root directory โ€” and so it can't work out the chain of propagation between the mounts it can see. iykyk. ๐Ÿฆ‹

Okay bestie, deep breath, because this next part is the iconic walkthrough โ€” we're literally building a whole master-slave chain by hand, step by step, and watching the kernel spill the tea in /proc/self/mountinfo. Slay. ๐Ÿ’…

In the following example, we first build a two-link master-slave chain linking up the mounts /mnt, /tmp/etc, and /mnt/tmp/etc. Then we hit it with the chroot(1) command, which makes the /tmp/etc mount point unreachable from the root directory โ€” and now we've got ourselves a whole situation where the master of /mnt/tmp/etc isn't reachable from the process's (new) root directory. Messy? Yes. Instructive? Also yes. โœจ

First up: we bind mount the root directory onto /mnt, and then bind mount /proc at /mnt/proc, so that after the later chroot(1) the proc(5) filesystem is still visible at the correct location inside the chroot-ed environment. Thinking ahead, queen. ๐Ÿ‘‘

# mkdir -p /mnt/proc
# mount --bind / /mnt
# mount --bind /proc /mnt/proc

Next, we make sure the /mnt mount is a shared mount in a brand new peer group (with absolutely no peers, no entourage, main character only):

# mount --make-private /mnt  # Isolate from any previous peer group
# mount --make-shared /mnt
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
239 61 8:2 / /mnt ... shared:102
248 239 0:4 / /mnt/proc ... shared:5

Then we bind mount /mnt/etc onto /tmp/etc:

# mkdir -p /tmp/etc
# mount --bind /mnt/etc /tmp/etc
# cat /proc/self/mountinfo | egrep '/mnt|/tmp/' | sed 's/ - .*//'
239 61 8:2 / /mnt ... shared:102
248 239 0:4 / /mnt/proc ... shared:5
267 40 8:2 /etc /tmp/etc ... shared:102

So at first these two mounts are chilling in the same peer group, but then we make /tmp/etc a slave of /mnt/etc, and then we make /tmp/etc shared as well โ€” because it needs to be able to propagate events down to the next slave in the chain. Passing the vibe along, one link at a time. ๐Ÿฆ‹

# mount --make-slave /tmp/etc
# mount --make-shared /tmp/etc
# cat /proc/self/mountinfo | egrep '/mnt|/tmp/' | sed 's/ - .*//'
239 61 8:2 / /mnt ... shared:102
248 239 0:4 / /mnt/proc ... shared:5
267 40 8:2 /etc /tmp/etc ... shared:105 master:102

Then we bind mount /tmp/etc onto /mnt/tmp/etc. Same deal, same energy: at first the two mounts are in the same peer group, but then we make /mnt/tmp/etc a slave of /tmp/etc:

# mkdir -p /mnt/tmp/etc
# mount --bind /tmp/etc /mnt/tmp/etc
# mount --make-slave /mnt/tmp/etc
# cat /proc/self/mountinfo | egrep '/mnt|/tmp/' | sed 's/ - .*//'
239 61 8:2 / /mnt ... shared:102
248 239 0:4 / /mnt/proc ... shared:5
267 40 8:2 /etc /tmp/etc ... shared:105 master:102
273 239 8:2 /etc /mnt/tmp/etc ... master:105

So from all of that we can see that /mnt is the master of the slave /tmp/etc, which is itself the master of the slave /mnt/tmp/etc. A whole inheritance chain. Royalty, honestly. ๐Ÿ‘‘

Then we chroot(1) to the /mnt directory โ€” which makes the mount with ID 267 unreachable from the (new) root directory:

# chroot /mnt

And when we peek at the state of the mounts from inside the chroot-ed environment, here's what we get:

# cat /proc/self/mountinfo | sed 's/ - .*//'
239 61 8:2 / / ... shared:102
248 239 0:4 / /proc ... shared:5
273 239 8:2 /etc /tmp/etc ... master:105 propagate_from:102

Okay so look at that last line, bestie โ€” the mount with ID 273 is a slave whose master is the peer group 105. But the mount point for that master is unreachable, right? So the kernel hits us with a propagate_from tag, which is basically the kernel saying "hey, this is the closest thing to a master I could actually point you at." Specifically, it indicates that the closest dominant peer group โ€” i.e., the nearest reachable mount in the slave chain โ€” is the peer group with the ID 102, which corresponds to the /mnt mount point from before the chroot(1) was performed. The kernel really said "closest reachable ancestor, no cap." ๐Ÿ˜ญ


๐Ÿ“œ STANDARDS ๐Ÿ“œ

  • Linux.

๐Ÿ•ฐ๏ธ HISTORY ๐Ÿ•ฐ๏ธ

  • Linux 2.4.19.

๐Ÿ“ NOTES

okay so boom ๐Ÿ’ฅ โ€” the propagation type a brand new mount gets is not a free-for-all, it literally depends on whatever its parent mount is doing. If the mount has a parent (aka it's a non-root mount) and that parent's propagation type is MS_SHARED, then bestie, the new mount is also MS_SHARED. Anything else? Then the new mount comes out as MS_PRIVATE. that's just the rules ๐Ÿ’…

ngl, even though the default for a new mount is MS_PRIVATE in a whole lotta cases, MS_SHARED is lowkey the more useful one. Which is exactly why systemd(1) just goes ahead and remounts all the mounts as MS_SHARED on system startup. so on most modern systems, the vibe is: the default propagation type is, in practice, MS_SHARED. โœจ

But wait ๐Ÿซถ โ€” when you use unshare(1) to spin up a mount namespace, the whole point is usually to get full isolation of the mounts in that shiny new namespace. So unshare(1) (since util-linux 2.27) does the reverse of what systemd(1) did, and makes all mounts private in the new namespace. Which is literally the same as running this inside the new mount namespace:

mount --make-rprivate /

Want to stop it from doing that? easy. hit unshare(1) with the --propagation unchanged option. ๐ŸŽ€

And if you're an application creating a new mount namespace directly with clone(2) or unshare(2), you might wanna prevent mount events from propagating to other mount namespaces (same as what unshare(1) does). do that by flipping the propagation type of mounts in the new namespace to either MS_SLAVE or MS_PRIVATE, with a call like this one:

mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL);

For the full tea on propagation types when you're moving mounts (MS_MOVE) and creating bind mounts (MS_BIND), go read Documentation/filesystems/sharedsubtree.rst. iykyk ๐ŸŒธ


๐Ÿšง Restrictions on mount namespaces

bestie, these are the plot points you need to know about mount namespaces:

  • [1] Every mount namespace has an owner user namespace. Like we said earlier, when a new mount namespace gets created, its mount list starts life as a copy of another mount namespace's mount list. If the new namespace and the namespace it copied from are owned by different user namespaces, then the new mount namespace counts as less privileged. ๐Ÿ‘‘
  • [2] When you create a less privileged mount namespace, shared mounts get downgraded to slave mounts. this is on purpose โ€” it makes sure that mappings done in less privileged mount namespaces never propagate upward into more privileged mount namespaces. no leaking upstairs, periodt ๐Ÿšซ
  • [3] Mounts that arrive as a single unit from a more privileged mount namespace are locked together, and they may not be separated in a less privileged mount namespace. (the unshare(2) CLONE_NEWNS operation brings all the mounts from the original mount namespace across as one single unit, and recursive mounts that propagate between mount namespaces propagate as a single unit too.)

In this context, "may not be separated" means the mounts are locked so they can't be individually unmounted. Consider this absolute example:

$ sudo sh
# mount --bind /dev/null /etc/shadow
# cat /etc/shadow       # Produces no output

So those steps, done in a more privileged mount namespace, created a bind mount that hides the contents of the shadow password file, /etc/shadow. For security reasons, it should not be possible to umount(2) that mount in a less privileged mount namespace, because doing that would expose the contents of /etc/shadow. ๐Ÿ’€

Now imagine we create a new mount namespace owned by a new user namespace. The new mount namespace inherits copies of every mount from the previous mount namespace. But those mounts are locked, because the new mount namespace is less privileged. So an attempt to umount(2) the mount fails, like this:

# unshare --user --map-root-user --mount \
               strace -o /tmp/log \
               umount /etc/shadow
umount: /etc/shadow: not mounted.
# grep '^umount' /tmp/log
umount2("/etc/shadow", 0)     = -1 EINVAL (Invalid argument)

The error message from mount(8) is a lil confusing ngl, but the strace(1) output spills the tea: the underlying umount2(2) system call failed with the error EINVAL, which is exactly the error the kernel returns to say "this mount is locked." ๐Ÿ”’

Note, though: you can stack (and unstack) a mount on top of one of those inherited locked mounts in a less privileged mount namespace:

# echo 'aaaaa' > /tmp/a    # File to mount onto /etc/shadow
# unshare --user --map-root-user --mount \
    sh -c 'mount --bind /tmp/a /etc/shadow; cat /etc/shadow'
aaaaa
# umount /etc/shadow

That final umount(8) command up there is performed in the initial mount namespace, and it makes the original /etc/shadow file visible in that namespace once more. iconic behavior honestly โœจ

  • [4] Following on from point [3], heads up: it is possible to umount(2) an entire subtree of mounts that propagated as a unit into a less privileged mount namespace. here's the example:

First, we create new user and mount namespaces using unshare(1). In the new mount namespace, the propagation type of all mounts is set to private. Then we create a shared bind mount at /mnt, plus a small hierarchy of mounts underneath it.

$ PS1='ns1# ' sudo unshare --user --map-root-user \
                       --mount --propagation private bash
ns1# echo $$        # We need the PID of this shell later
778501
ns1# mount --make-shared --bind /mnt /mnt
ns1# mkdir /mnt/x
ns1# mount --make-private -t tmpfs none /mnt/x
ns1# mkdir /mnt/x/y
ns1# mount --make-private -t tmpfs none /mnt/x/y
ns1# grep /mnt /proc/self/mountinfo | sed 's/ - .*//'
986 83 8:5 /mnt /mnt rw,relatime shared:344
989 986 0:56 / /mnt/x rw,relatime
990 989 0:57 / /mnt/x/y rw,relatime

Still in the same shell session, we then create a second shell in a new user namespace and a new (less privileged) mount namespace, and check the state of the propagated mounts rooted at /mnt.

ns1# PS1='ns2# ' unshare --user --map-root-user \
                       --mount --propagation unchanged bash
ns2# grep /mnt /proc/self/mountinfo | sed 's/ - .*//'
1239 1204 8:5 /mnt /mnt rw,relatime master:344
1240 1239 0:56 / /mnt/x rw,relatime
1241 1240 0:57 / /mnt/x/y rw,relatime

What's notable in that output: the propagation type of the mount /mnt has been reduced to slave, exactly like point [2] said. Which means submount events will propagate from the master /mnt in "ns1", but propagation will not happen in the opposite direction. one-way street bestie ๐Ÿ›ฃ๏ธ

From a separate terminal window, we then use nsenter(1) to enter the mount and user namespaces corresponding to "ns1". In that terminal window, we recursively bind mount /mnt/x at the location /mnt/ppp.

$ PS1='ns3# ' sudo nsenter -t 778501 --user --mount
ns3# mount --rbind --make-private /mnt/x /mnt/ppp
ns3# grep /mnt /proc/self/mountinfo | sed 's/ - .*//'
986 83 8:5 /mnt /mnt rw,relatime shared:344
989 986 0:56 / /mnt/x rw,relatime
990 989 0:57 / /mnt/x/y rw,relatime
1242 986 0:56 / /mnt/ppp rw,relatime
1243 1242 0:57 / /mnt/ppp/y rw,relatime shared:518

Because the propagation type of the parent mount, /mnt, was shared, the recursive bind mount propagated a small subtree of mounts under the slave mount /mnt into "ns2". verify it by running this in that shell session:

ns2# grep /mnt /proc/self/mountinfo | sed 's/ - .*//'
1239 1204 8:5 /mnt /mnt rw,relatime master:344
1240 1239 0:56 / /mnt/x rw,relatime
1241 1240 0:57 / /mnt/x/y rw,relatime
1244 1239 0:56 / /mnt/ppp rw,relatime
1245 1244 0:57 / /mnt/ppp/y rw,relatime master:518

And while it's not possible to umount(2) a part of the propagated subtree (/mnt/ppp/y) in "ns2", it is possible to umount(2) the entire subtree, like so:

ns2# umount /mnt/ppp/y
umount: /mnt/ppp/y: not mounted.
ns2# umount -l /mnt/ppp | sed 's/ - .*//'      # Succeeds...
ns2# grep /mnt /proc/self/mountinfo
1239 1204 8:5 /mnt /mnt rw,relatime master:344
1240 1239 0:56 / /mnt/x rw,relatime
1241 1240 0:57 / /mnt/x/y rw,relatime
  • [5] The mount(2) flags MS_RDONLY, MS_NOSUID, MS_NOEXEC, and the "atime" flags (MS_NOATIME, MS_NODIRATIME, MS_RELATIME) all become locked when propagated from a more privileged to a less privileged mount namespace. you can't change them in the less privileged namespace. locked in fr fr ๐Ÿ”

Here's that point in action โ€” in a more privileged mount namespace, we create a bind mount marked read-only. For security reasons it should not be possible to make that mount writable in a less privileged mount namespace, and the kernel straight up prevents it:

$ sudo mkdir /mnt/dir
$ sudo mount --bind -o ro /some/path /mnt/dir
$ sudo unshare --user --map-root-user --mount \
               mount -o remount,rw /mnt/dir
mount: /mnt/dir: permission denied.
  • [6] A file or directory that's a mount point in one namespace but not a mount point in another namespace may be renamed, unlinked, or removed (rmdir(2)) in the mount namespace where it's not a mount point (subject to the usual permission checks, obviously). Consequently, the mount point gets removed in the mount namespace where it was a mount point. ๐Ÿ˜ญ

Previously (before Linux 3.18), trying to unlink, rename, or remove a file or directory that was a mount point in another mount namespace would throw the error EBUSY. That behavior had technical problems with enforcement (for NFS, for example) and it allowed denial-of-service attacks against more privileged users (i.e., preventing individual files from being updated by bind mounting on top of them). not cute. not the vibe. ๐Ÿšฉ


๐ŸŽ€ EXAMPLES

See pivot_root(2). that's it, that's the example bestie ๐Ÿ’–

โœจ SEE ALSO โœจ

okay bestie, you made it to the end, congrats ๐ŸŽ€ here's your little squad of related man pages โ€” go read these next, they're the group chat that this page lives in ๐Ÿ’…

  • unshare(1) โ€” the command-line bestie that runs stuff in fresh new namespaces without you writing a single line of C โœจ
  • clone(2) โ€” the absolute icon that creates a new process and optionally hands it shiny new namespaces to live in ๐Ÿฆ‹
  • mount(2) โ€” the syscall that does the actual mounting, this is the one doing all the heavy lifting under the hood ๐Ÿ”ฅ
  • mount_setattr(2) โ€” for when you need to change mount attributes, very specific, very slay ๐Ÿ‘‘
  • pivot_root(2) โ€” swaps out the root filesystem, main character energy for container setups ๐ŸŒˆ
  • setns(2) โ€” joins an existing namespace, so you can just slide into a namespace someone already made ๐Ÿ˜Œ
  • umount(2) โ€” takes mounts away, the breakup arc of the filesystem world ๐Ÿ’€
  • unshare(2) โ€” the syscall version of disengaging from a namespace, lowkey the whole point of this page fr fr
  • proc(5) โ€” documents /proc, where all the namespace info shows up as files, iykyk ๐Ÿ“
  • namespaces(7) โ€” the big overview of all the namespace types, your crash course before you go deep here ๐Ÿ’œ
  • user_namespaces(7) โ€” user namespaces specifically, and you already know they're intertwined with mount namespaces ๐Ÿ’–
  • findmnt(8) โ€” the command that shows you the mount tree and namespace stuff without making you squint at /proc by hand โœจ
  • mount(8) โ€” the mount command itself, you've literally used it a hundred times ๐Ÿซถ
  • pam_namespace(8) โ€” sets up per-user private mount namespaces at login, iconic behaviour tbh ๐Ÿงš
  • pivot_root(8) โ€” the command-line buddy of pivot_root(2) ๐ŸŒธ
  • umount(8) โ€” the command-line buddy of umount(2), they come as a set ๐Ÿ’…

also, if you want the deep lore from the kernel devs themselves, go read:

  • Documentation/filesystems/sharedsubtree.rst in the kernel source tree ๐Ÿ“š
that's the shared-subtrees documentation, AKA where all the propagation magic is explained by the people who actually wrote it โญ

and that's a wrap, bestie ๐Ÿฅน๐Ÿ’– you just read Linux man-pages 6.20, dated 2026-10-10 โ€” mount namespaces, fully understood, no cap. go forth and unshare responsibly, ily ๐ŸŽ€โœจ๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from
  • arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • ๐Ÿ“œ the page's license: Linux-man-pages-copyleft
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms