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)andumount(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 samemount(2)orumount(2)will automatically occur under all of the other mounts in the peer group. Conversely,mount(2)andumount(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)andumount(2)events do not propagate into or out of this mount. Do not disturb. ๐MS_SLAVEโmount(2)andumount(2)events propagate into this mount from a (master) shared peer group. Butmount(2)andumount(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 theMS_BINDflag) 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:
- the mount is replicated during the creation of a new mount namespace; or
- 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 groupX. 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 value1, and may be recycled when a peer group ceases to have any members.) ๐master:Xโ this mount is a slave to shared peer groupX.propagate_from:X(since Linux 2.6.26) โ this mount is a slave and receives propagation from shared peer groupX. This tag will always appear in conjunction with amaster:Xtag. Here,Xis the closest dominant peer group under the process's root directory. IfXis the immediate master of the mount, or if there is no dominant peer group under the same root, then only themaster:Xfield is present and not thepropagate_from:Xfield. 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_NEWNSoperation 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)flagsMS_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 frproc(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/procby hand โจmount(8)โ themountcommand 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 ofpivot_root(2)๐ธumount(8)โ the command-line buddy ofumount(2), they come as a set ๐
also, if you want the deep lore from the kernel devs themselves, go read:
Documentation/filesystems/sharedsubtree.rstin 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
- Copyright 2016-2021, Michael Kerrisk <mtk.manpages@gmail.com>
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms