summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric W. Biederman <ebiederm@xmission.com>2017-05-15 15:42:07 -0400
committerEric W. Biederman <ebiederm@xmission.com>2017-05-23 09:40:32 -0400
commit570487d3faf2a1d8a220e6ee10f472163123d7da (patch)
tree185995239e9eddcc5c0a3e1e2d88aede25455c0b
parentc70d9d809fdeecedb96972457ee45c49a232d97f (diff)
mnt: In umount propagation reparent in a separate pass
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. Cc: stable@vger.kernel.org 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>
-rw-r--r--fs/mount.h1
-rw-r--r--fs/namespace.c1
-rw-r--r--fs/pnode.c35
3 files changed, 32 insertions, 5 deletions
diff --git a/fs/mount.h b/fs/mount.h
index bf1fda6eed8f..ede5a1d5cf99 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -58,6 +58,7 @@ struct mount {
58 struct mnt_namespace *mnt_ns; /* containing namespace */ 58 struct mnt_namespace *mnt_ns; /* containing namespace */
59 struct mountpoint *mnt_mp; /* where is it mounted */ 59 struct mountpoint *mnt_mp; /* where is it mounted */
60 struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */ 60 struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
61 struct list_head mnt_reparent; /* reparent list entry */
61#ifdef CONFIG_FSNOTIFY 62#ifdef CONFIG_FSNOTIFY
62 struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks; 63 struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
63 __u32 mnt_fsnotify_mask; 64 __u32 mnt_fsnotify_mask;
diff --git a/fs/namespace.c b/fs/namespace.c
index 8bd3e4d448b9..51e49866e1fe 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -236,6 +236,7 @@ static struct mount *alloc_vfsmnt(const char *name)
236 INIT_LIST_HEAD(&mnt->mnt_slave_list); 236 INIT_LIST_HEAD(&mnt->mnt_slave_list);
237 INIT_LIST_HEAD(&mnt->mnt_slave); 237 INIT_LIST_HEAD(&mnt->mnt_slave);
238 INIT_HLIST_NODE(&mnt->mnt_mp_list); 238 INIT_HLIST_NODE(&mnt->mnt_mp_list);
239 INIT_LIST_HEAD(&mnt->mnt_reparent);
239 init_fs_pin(&mnt->mnt_umount, drop_mountpoint); 240 init_fs_pin(&mnt->mnt_umount, drop_mountpoint);
240 } 241 }
241 return mnt; 242 return mnt;
diff --git a/fs/pnode.c b/fs/pnode.c
index 5bc7896d122a..52aca0a118ff 100644
--- a/fs/pnode.c
+++ b/fs/pnode.c
@@ -439,7 +439,7 @@ static void mark_umount_candidates(struct mount *mnt)
439 * NOTE: unmounting 'mnt' naturally propagates to all other mounts its 439 * NOTE: unmounting 'mnt' naturally propagates to all other mounts its
440 * parent propagates to. 440 * parent propagates to.
441 */ 441 */
442static void __propagate_umount(struct mount *mnt) 442static void __propagate_umount(struct mount *mnt, struct list_head *to_reparent)
443{ 443{
444 struct mount *parent = mnt->mnt_parent; 444 struct mount *parent = mnt->mnt_parent;
445 struct mount *m; 445 struct mount *m;
@@ -464,17 +464,38 @@ static void __propagate_umount(struct mount *mnt)
464 */ 464 */
465 topper = find_topper(child); 465 topper = find_topper(child);
466 if (topper) 466 if (topper)
467 mnt_change_mountpoint(child->mnt_parent, child->mnt_mp, 467 list_add_tail(&topper->mnt_reparent, to_reparent);
468 topper);
469 468
470 if (list_empty(&child->mnt_mounts)) { 469 if (topper || list_empty(&child->mnt_mounts)) {
471 list_del_init(&child->mnt_child); 470 list_del_init(&child->mnt_child);
471 list_del_init(&child->mnt_reparent);
472 child->mnt.mnt_flags |= MNT_UMOUNT; 472 child->mnt.mnt_flags |= MNT_UMOUNT;
473 list_move_tail(&child->mnt_list, &mnt->mnt_list); 473 list_move_tail(&child->mnt_list, &mnt->mnt_list);
474 } 474 }
475 } 475 }
476} 476}
477 477
478static void reparent_mounts(struct list_head *to_reparent)
479{
480 while (!list_empty(to_reparent)) {
481 struct mount *mnt, *parent;
482 struct mountpoint *mp;
483
484 mnt = list_first_entry(to_reparent, struct mount, mnt_reparent);
485 list_del_init(&mnt->mnt_reparent);
486
487 /* Where should this mount be reparented to? */
488 mp = mnt->mnt_mp;
489 parent = mnt->mnt_parent;
490 while (parent->mnt.mnt_flags & MNT_UMOUNT) {
491 mp = parent->mnt_mp;
492 parent = parent->mnt_parent;
493 }
494
495 mnt_change_mountpoint(parent, mp, mnt);
496 }
497}
498
478/* 499/*
479 * collect all mounts that receive propagation from the mount in @list, 500 * collect all mounts that receive propagation from the mount in @list,
480 * and return these additional mounts in the same list. 501 * and return these additional mounts in the same list.
@@ -485,11 +506,15 @@ static void __propagate_umount(struct mount *mnt)
485int propagate_umount(struct list_head *list) 506int propagate_umount(struct list_head *list)
486{ 507{
487 struct mount *mnt; 508 struct mount *mnt;
509 LIST_HEAD(to_reparent);
488 510
489 list_for_each_entry_reverse(mnt, list, mnt_list) 511 list_for_each_entry_reverse(mnt, list, mnt_list)
490 mark_umount_candidates(mnt); 512 mark_umount_candidates(mnt);
491 513
492 list_for_each_entry(mnt, list, mnt_list) 514 list_for_each_entry(mnt, list, mnt_list)
493 __propagate_umount(mnt); 515 __propagate_umount(mnt, &to_reparent);
516
517 reparent_mounts(&to_reparent);
518
494 return 0; 519 return 0;
495} 520}