Skip to content

Commit 7cbc395

Browse files
ebiedermgregkh
authored andcommitted
mnt: In umount propagation reparent in a separate pass
commit 570487d3faf2a1d8a220e6ee10f472163123d7da upstream. It was observed that in some pathlogical cases that the current code does not unmount everything it should. After investigation it was determined that the issue is that mnt_change_mntpoint can can change which mounts are available to be unmounted during mount propagation which is wrong. The trivial reproducer is: $ cat ./pathological.sh mount -t tmpfs test-base /mnt cd /mnt mkdir 1 2 1/1 mount --bind 1 1 mount --make-shared 1 mount --bind 1 2 mount --bind 1/1 1/1 mount --bind 1/1 1/1 echo grep test-base /proc/self/mountinfo umount 1/1 echo grep test-base /proc/self/mountinfo $ unshare -Urm ./pathological.sh The expected output looks like: 46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 The output without the fix looks like: 46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 That last mount in the output was in the propgation tree to be unmounted but was missed because the mnt_change_mountpoint changed it's parent before the walk through the mount propagation tree observed it. Fixes: 1064f874abc0 ("mnt: Tuck mounts under others instead of creating shadow/side mounts.") Acked-by: Andrei Vagin <avagin@virtuozzo.com> Reviewed-by: Ram Pai <linuxram@us.ibm.com> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 050b074 commit 7cbc395

3 files changed

Lines changed: 32 additions & 5 deletions

File tree

fs/mount.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ struct mount {
5757
struct mnt_namespace *mnt_ns; /* containing namespace */
5858
struct mountpoint *mnt_mp; /* where is it mounted */
5959
struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
60+
struct list_head mnt_reparent; /* reparent list entry */
6061
#ifdef CONFIG_FSNOTIFY
6162
struct hlist_head mnt_fsnotify_marks;
6263
__u32 mnt_fsnotify_mask;

fs/namespace.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ static struct mount *alloc_vfsmnt(const char *name)
237237
INIT_LIST_HEAD(&mnt->mnt_slave_list);
238238
INIT_LIST_HEAD(&mnt->mnt_slave);
239239
INIT_HLIST_NODE(&mnt->mnt_mp_list);
240+
INIT_LIST_HEAD(&mnt->mnt_reparent);
240241
#ifdef CONFIG_FSNOTIFY
241242
INIT_HLIST_HEAD(&mnt->mnt_fsnotify_marks);
242243
#endif

fs/pnode.c

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ static void mark_umount_candidates(struct mount *mnt)
441441
* NOTE: unmounting 'mnt' naturally propagates to all other mounts its
442442
* parent propagates to.
443443
*/
444-
static void __propagate_umount(struct mount *mnt)
444+
static void __propagate_umount(struct mount *mnt, struct list_head *to_reparent)
445445
{
446446
struct mount *parent = mnt->mnt_parent;
447447
struct mount *m;
@@ -466,17 +466,38 @@ static void __propagate_umount(struct mount *mnt)
466466
*/
467467
topper = find_topper(child);
468468
if (topper)
469-
mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
470-
topper);
469+
list_add_tail(&topper->mnt_reparent, to_reparent);
471470

472-
if (list_empty(&child->mnt_mounts)) {
471+
if (topper || list_empty(&child->mnt_mounts)) {
473472
list_del_init(&child->mnt_child);
473+
list_del_init(&child->mnt_reparent);
474474
child->mnt.mnt_flags |= MNT_UMOUNT;
475475
list_move_tail(&child->mnt_list, &mnt->mnt_list);
476476
}
477477
}
478478
}
479479

480+
static void reparent_mounts(struct list_head *to_reparent)
481+
{
482+
while (!list_empty(to_reparent)) {
483+
struct mount *mnt, *parent;
484+
struct mountpoint *mp;
485+
486+
mnt = list_first_entry(to_reparent, struct mount, mnt_reparent);
487+
list_del_init(&mnt->mnt_reparent);
488+
489+
/* Where should this mount be reparented to? */
490+
mp = mnt->mnt_mp;
491+
parent = mnt->mnt_parent;
492+
while (parent->mnt.mnt_flags & MNT_UMOUNT) {
493+
mp = parent->mnt_mp;
494+
parent = parent->mnt_parent;
495+
}
496+
497+
mnt_change_mountpoint(parent, mp, mnt);
498+
}
499+
}
500+
480501
/*
481502
* collect all mounts that receive propagation from the mount in @list,
482503
* and return these additional mounts in the same list.
@@ -487,11 +508,15 @@ static void __propagate_umount(struct mount *mnt)
487508
int propagate_umount(struct list_head *list)
488509
{
489510
struct mount *mnt;
511+
LIST_HEAD(to_reparent);
490512

491513
list_for_each_entry_reverse(mnt, list, mnt_list)
492514
mark_umount_candidates(mnt);
493515

494516
list_for_each_entry(mnt, list, mnt_list)
495-
__propagate_umount(mnt);
517+
__propagate_umount(mnt, &to_reparent);
518+
519+
reparent_mounts(&to_reparent);
520+
496521
return 0;
497522
}

0 commit comments

Comments
 (0)