Call for testing and review of mount detach fixes
cover Call for testing and review of mount detach fixes
Way back in October Andrey Vagin reported that umount(MNT_DETACH) could be used to defeat MNT_LOCKED.
That MNT_DETACH is allowed in user namespace comes from my early misunderstanding what MNT_DETACH does. My mistake.
To avoid breaking existing userspace the conflict between MNT_DETACH and MNT_LOCKED is fixed by leaving locked umounts attached in the mount hash table until the last reference goes away.
While investigating this issue I also found an issue with __detach_mounts. The code was unnecessarily and incorrectly triggering mount propagation. Resulting in too many mounts going away when a directory is deleted, and too many cpu cycles burned while doing that.
For those who like to see everything in a single tree the code is at:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-testing
Eric W. Biederman (9):
mnt: Improve the umount_tree flags
mnt: Don't propagate umounts in __detach_mounts
mnt: In umount_tree reuse mnt_list instead of mnt_hash
mnt: Add MNT_UMOUNT flag
mnt: Delay removal from the mount hash.
mnt: Factor out __detach_mnt from detach_mnt
mnt: Simplify umount_tree
mnt: Remove redundant NULL tests in namespace_unlock
mnt: Honor MNT_LOCKED when detaching mounts
fs/namespace.c | 150 +++++++++++++++++++++++++++++++------------------- fs/pnode.c | 8 +-- fs/pnode.h | 3 +- include/linux/mount.h | 1 + 4 files changed, 99 insertions(+), 63 deletions(-)
A message that is not in the archive (sent off-list or not yet received): [email protected]
PATCH 1/9 mnt: Improve the umount_tree flags
- Remove the unneeded declaration from pnode.h
- Mark umount_tree static as it has no callers outside of namespace.c
- Define an enumeration of umount_tree's flags.
- Pass umount_tree's flags in by name
This removes the magic numbers 0, 1 and 2 making the code a little clearer and makes it possible for there to be lazy unmounts that don't propagate. Which is what __detach_mounts actually wants for example.
--- fs/namespace.c | 31 ++++++++++++++++--------------- fs/pnode.h | 1 - 2 files changed, 16 insertions(+), 16 deletions(-)
fs/namespace.c +16 -15
@@ -1323,14 +1323,15 @@ static inline void namespace_lock(void)down_write (&namespace_sem); } +enum umount_tree_flags { +UMOUNT_SYNC =1 , +UMOUNT_PROPAGATE =2 , +};/* * mount_lock must be held * namespace_sem must be held for write -* how = 0 => just this tree, don't propagate -* how = 1 => propagate; we know that nobody else has reference to any victims -* how = 2 => lazy umount */ -void umount_tree (struct mount *mnt,int how) +static void umount_tree (struct mount *mnt,enum umount_tree_flags how) {HLIST_HEAD (tmp_list);struct mount *p;
@@ -1344,7 +1345,7 @@ void umount_tree(struct mount *mnt, int how)hlist_for_each_entry (p, &tmp_list, mnt_hash)list_del_init (&p->mnt_child); -if (how) +if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list);hlist_for_each_entry (p, &tmp_list, mnt_hash) {
@@ -1352,7 +1353,7 @@ void umount_tree(struct mount *mnt, int how)list_del_init (&p->mnt_list);__touch_mnt_namespace (p->mnt_ns); p->mnt_ns =NULL ; -if (how <2 ) +if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ;if (mnt_has_parent (p)) {hlist_del_init (&p->mnt_mp_list);
@@ -1457,14 +1458,14 @@ static int do_umount(struct mount *mnt, int flags)if (flags &MNT_DETACH ) {if (!list_empty (&mnt->mnt_list)) -umount_tree (mnt,2 ); +umount_tree (mnt,UMOUNT_PROPAGATE ); retval =0 ; }else {shrink_submounts (mnt); retval = -EBUSY ;if (!propagate_mount_busy (mnt,2 )) {if (!list_empty (&mnt->mnt_list)) -umount_tree (mnt,1 ); +umount_tree (mnt,UMOUNT_PROPAGATE |UMOUNT_SYNC ); retval =0 ; } }
@@ -1496,7 +1497,7 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,2 ); +umount_tree (mnt,UMOUNT_PROPAGATE ); }unlock_mount_hash ();put_mountpoint (mp);
@@ -1658,7 +1659,7 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry, out:if (res) {lock_mount_hash (); -umount_tree (res,0 ); +umount_tree (res,UMOUNT_SYNC );unlock_mount_hash (); }return q;
@@ -1682,7 +1683,7 @@ void drop_collected_mounts(struct vfsmount *mnt) {namespace_lock ();lock_mount_hash (); -umount_tree (real_mount (mnt),0 ); +umount_tree (real_mount (mnt),UMOUNT_SYNC );unlock_mount_hash ();namespace_unlock (); }
@@ -1865,7 +1866,7 @@ static int attach_recursive_mnt(struct mount *source_mnt, out_cleanup_ids:while (!hlist_empty (&tree_list)) { child = hlist_entry(tree_list.first, struct mount, mnt_hash); -umount_tree (child,0 ); +umount_tree (child,UMOUNT_SYNC ); }unlock_mount_hash ();cleanup_group_ids (source_mnt,NULL );
@@ -2045,7 +2046,7 @@ static int do_loopback(struct path *path, const char *old_name, err =graft_tree (mnt, parent, mp);if (err) {lock_mount_hash (); -umount_tree (mnt,0 ); +umount_tree (mnt,UMOUNT_SYNC );unlock_mount_hash (); } out2:
@@ -2416,7 +2417,7 @@ void mark_mounts_for_expiry(struct list_head *mounts)while (!list_empty (&graveyard)) { mnt = list_first_entry(&graveyard, struct mount, mnt_expire);touch_mnt_namespace (mnt->mnt_ns); -umount_tree (mnt,1 ); +umount_tree (mnt,UMOUNT_PROPAGATE |UMOUNT_SYNC ); }unlock_mount_hash ();namespace_unlock ();
@@ -2487,7 +2488,7 @@ static void shrink_submounts(struct mount *mnt) m = list_first_entry(&graveyard, struct mount, mnt_expire);touch_mnt_namespace (m->mnt_ns); -umount_tree (m,1 ); +umount_tree (m,UMOUNT_PROPAGATE |UMOUNT_SYNC ); } } }
fs/pnode.h +0 -1
@@ -47,7 +47,6 @@ int get_dominating_id(struct mount *mnt, const struct path *root);unsigned int mnt_get_count (struct mount *mnt);void mnt_set_mountpoint (struct mount *,struct mountpoint *,struct mount *); -void umount_tree (struct mount *,int );struct mount *copy_tree (struct mount *,struct dentry *,int );bool is_path_reachable (struct mount *,struct dentry *,const struct path *root);
Signature · 1 line
2.2.1
PATCH 2/9 mnt: Don't propagate umounts in __detach_mounts
In reply to a message not in the archiveInvoking mount propagation from __detach_mounts is inefficient and wrong.
It is inefficient because __detach_mounts already walks the list of mounts that where something needs to be done, and mount propagation walks some subset of those mounts again.
It is actively wrong because if the dentry that is passed to __detach_mounts is not part of the path to a mount that mount should not be affected.
change_mnt_propagation(p,MS_PRIVATE) modifies the mount propagation tree of a master mount so it's slaves are connected to another master if possible. Which means even removing a mount from the middle of a mount tree with __detach_mounts will not deprive any mount propagated mount events.
--- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
@@ -1497,7 +1497,7 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,UMOUNT_PROPAGATE ); +umount_tree (mnt,0 ); }unlock_mount_hash ();put_mountpoint (mp);
Signature · 1 line
2.2.1
PATCH 3/9 mnt: In umount_tree reuse mnt_list instead of mnt_hash
In reply to a message not in the archiveumount_tree builds a list of mounts that need to be unmounted. Utilize mnt_list for this purpose instead of mnt_hash as mnt_list is an ordianry list_head, allowing the use of list_splice and list_move instead of rolling our own.
This also begins to allow keeping a mount on the mnt_hash after it is unmounted. Which is necessary for a properly functioning MNT_LOCKED implementation.
--- fs/namespace.c | 45 +++++++++++++++++++-------------------------- fs/pnode.c | 6 +++--- fs/pnode.h | 2 +- 3 files changed, 23 insertions(+), 30 deletions(-)
fs/namespace.c +19 -26
@@ -1285,23 +1285,22 @@ int may_umount(struct vfsmount *mnt)EXPORT_SYMBOL (may_umount); -static HLIST_HEAD (unmounted);/* protected by namespace_sem */ +static LIST_HEAD (unmounted);/* protected by namespace_sem */ static void namespace_unlock (void ) {struct mount *mnt; -struct hlist_head head = unmounted; +LIST_HEAD (head); -if (likely (hlist_empty (&head))) { +if (likely (list_empty (&unmounted))) {up_write (&namespace_sem);return ; } - head.first->pprev = &head.first; -INIT_HLIST_HEAD (&unmounted); +list_splice_init (&unmounted, &head);/* undo decrements we'd done in umount_tree() */ -hlist_for_each_entry (mnt, &head, mnt_hash) +list_for_each_entry (mnt, &head, mnt_list)if (mnt->mnt_ex_mountpoint.mnt)mntget (mnt->mnt_ex_mountpoint.mnt);
@@ -1309,9 +1308,9 @@ static void namespace_unlock(void)synchronize_rcu (); -while (!hlist_empty (&head)) { - mnt = hlist_entry(head.first, struct mount, mnt_hash); -hlist_del_init (&mnt->mnt_hash); +while (!list_empty (&head)) { + mnt = list_first_entry(&head, struct mount, mnt_list); +list_del_init (&mnt->mnt_list);if (mnt->mnt_ex_mountpoint.mnt)path_put (&mnt->mnt_ex_mountpoint);mntput (&mnt->mnt);
@@ -1333,24 +1332,25 @@ enum umount_tree_flags {*/ static void umount_tree (struct mount *mnt,enum umount_tree_flags how) { -HLIST_HEAD (tmp_list); +LIST_HEAD (tmp_list);struct mount *p; -struct mount *last =NULL ; -for (p = mnt; p; p =next_mnt (p, mnt)) { -hlist_del_init_rcu (&p->mnt_hash); -hlist_add_head (&p->mnt_hash, &tmp_list); - } +/* Gather the mounts to umount */ +for (p = mnt; p; p =next_mnt (p, mnt)) +list_move (&p->mnt_list, &tmp_list); -hlist_for_each_entry (p, &tmp_list, mnt_hash) +/* Hide the mounts from lookup_mnt and mnt_mounts */ +list_for_each_entry (p, &tmp_list, mnt_list) { +hlist_del_init_rcu (&p->mnt_hash);list_del_init (&p->mnt_child); + } +/* Add propogated mounts to the tmp_list */ if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list); -hlist_for_each_entry (p, &tmp_list, mnt_hash) { +list_for_each_entry (p, &tmp_list, mnt_list) {list_del_init (&p->mnt_expire); -list_del_init (&p->mnt_list);__touch_mnt_namespace (p->mnt_ns); p->mnt_ns =NULL ;if (how &UMOUNT_SYNC )
@@ -1367,15 +1367,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt_mp =NULL ; }change_mnt_propagation (p,MS_PRIVATE ); - last = p; - } -if (last) { - last->mnt_hash.next = unmounted.first; -if (unmounted.first) - unmounted.first->pprev = &last->mnt_hash.next; - unmounted.first = tmp_list.first; - unmounted.first->pprev = &unmounted.first; } +list_splice (&tmp_list, &unmounted); }static void shrink_submounts (struct mount *mnt);
fs/pnode.c +3 -3
@@ -384,7 +384,7 @@ static void __propagate_umount(struct mount *mnt)if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child);hlist_del_init_rcu (&child->mnt_hash); -hlist_add_before_rcu (&child->mnt_hash, &mnt->mnt_hash); +list_move_tail (&child->mnt_list, &mnt->mnt_list); } } }
@@ -396,11 +396,11 @@ static void __propagate_umount(struct mount *mnt)* * vfsmount lock must be held for write */ -int propagate_umount (struct hlist_head *list) +int propagate_umount (struct list_head *list) {struct mount *mnt; -hlist_for_each_entry (mnt, list, mnt_hash) +list_for_each_entry (mnt, list, mnt_list)__propagate_umount (mnt);return 0 ; }
fs/pnode.h +1 -1
@@ -40,7 +40,7 @@ static inline void set_mnt_shared(struct mount *mnt)void change_mnt_propagation (struct mount *,int );int propagate_mnt (struct mount *,struct mountpoint *,struct mount *,struct hlist_head *); -int propagate_umount (struct hlist_head *); +int propagate_umount (struct list_head *);int propagate_mount_busy (struct mount *,int );void mnt_release_group_id (struct mount *);int get_dominating_id (struct mount *mnt,const struct path *root);
Signature · 1 line
2.2.1
PATCH 4/9 mnt: Add MNT_UMOUNT flag
In reply to a message not in the archiveIn some instances it is necessary to know if the the unmounting process has begun on a mount. Add MNT_UMOUNT to make that reliably testable.
This fix gets used in fixing locked mounts in MNT_DETACH
--- fs/namespace.c | 4 +++- fs/pnode.c | 1 + include/linux/mount.h | 1 + 3 files changed, 5 insertions(+), 1 deletion(-)
fs/namespace.c +3 -1
@@ -1336,8 +1336,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)struct mount *p;/* Gather the mounts to umount */ -for (p = mnt; p; p =next_mnt (p, mnt)) +for (p = mnt; p; p =next_mnt (p, mnt)) { + p->mnt.mnt_flags |=MNT_UMOUNT ;list_move (&p->mnt_list, &tmp_list); + }/* Hide the mounts from lookup_mnt and mnt_mounts */ list_for_each_entry (p, &tmp_list, mnt_list) {
fs/pnode.c +1 -0
@@ -384,6 +384,7 @@ static void __propagate_umount(struct mount *mnt)if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child);hlist_del_init_rcu (&child->mnt_hash); + child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list); } }
include/linux/mount.h +1 -0
@@ -61,6 +61,7 @@ struct mnt_namespace;#define MNT_DOOMED 0x1000000#define MNT_SYNC_UMOUNT 0x2000000#define MNT_MARKED 0x4000000 +#define MNT_UMOUNT 0x8000000struct vfsmount {struct dentry *mnt_root;/* root of the mounted tree */
Signature · 1 line
2.2.1
PATCH 5/9 mnt: Delay removal from the mount hash.
In reply to a message not in the archive- Modify __lookup_mnt_hash_last to ignore mounts that have MNT_UMOUNTED set.
- Don't remove mounts from the mount hash table in propogate_umount
- Don't remove mounts from the mount hash table in umount_tree before the entire list of mounts to be umounted is selected.
- Remove mounts from the mount hash table as the last thing that happens in the case where a mount has a parent in umount_tree. Mounts without parents are not hashed (by definition).
This paves the way for delaying removal from the mount hash table even farther and fixing the MNT_LOCKED vs MNT_DETACH issue.
--- fs/namespace.c | 13 ++++++++----- fs/pnode.c | 1 - 2 files changed, 8 insertions(+), 6 deletions(-)
fs/namespace.c +8 -5
@@ -623,14 +623,17 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)*/ struct mount *__lookup_mnt_last (struct vfsmount *mnt,struct dentry *dentry) { -struct mount *p, *res; - res = p =__lookup_mnt (mnt, dentry); +struct mount *p, *res =NULL ; + p =__lookup_mnt (mnt, dentry);if (!p) goto out; +if (!(p->mnt.mnt_flags &MNT_UMOUNT )) + res = p;hlist_for_each_entry_continue (p, mnt_hash) {if (&p->mnt_parent->mnt != mnt || p->mnt_mountpoint != dentry)break ; - res = p; +if (!(p->mnt.mnt_flags &MNT_UMOUNT )) + res = p; } out:return res;
@@ -1341,9 +1344,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)list_move (&p->mnt_list, &tmp_list); } -/* Hide the mounts from lookup_mnt and mnt_mounts */ +/* Hide the mounts from mnt_mounts */ list_for_each_entry (p, &tmp_list, mnt_list) { -hlist_del_init_rcu (&p->mnt_hash);list_del_init (&p->mnt_child); }
@@ -1367,6 +1369,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt_mountpoint = p->mnt.mnt_root; p->mnt_parent = p; p->mnt_mp =NULL ; +hlist_del_init_rcu (&p->mnt_hash); }change_mnt_propagation (p,MS_PRIVATE ); }
fs/pnode.c +0 -1
@@ -383,7 +383,6 @@ static void __propagate_umount(struct mount *mnt)*/ if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child); -hlist_del_init_rcu (&child->mnt_hash); child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list); }
Signature · 1 line
2.2.1
PATCH 6/9 mnt: Factor out __detach_mnt from detach_mnt
In reply to a message not in the archiveAn upcoming change is going to need a version of detach_mnt that leaves the mount on the parents mnt_mounts list. Create that version of detach_mnt now and call it __detach_mnt.
--- fs/namespace.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
fs/namespace.c +10 -2
@@ -789,13 +789,12 @@ static void __touch_mnt_namespace(struct mnt_namespace *ns)/* * vfsmount lock must be held for write */ -static void detach_mnt (struct mount *mnt,struct path *old_path) +static void __detach_mnt (struct mount *mnt,struct path *old_path) { old_path->dentry = mnt->mnt_mountpoint; old_path->mnt = &mnt->mnt_parent->mnt; mnt->mnt_parent = mnt; mnt->mnt_mountpoint = mnt->mnt.mnt_root; -list_del_init (&mnt->mnt_child);hlist_del_init_rcu (&mnt->mnt_hash);hlist_del_init (&mnt->mnt_mp_list);put_mountpoint (mnt->mnt_mp);
@@ -805,6 +804,15 @@ static void detach_mnt(struct mount *mnt, struct path *old_path)/* * vfsmount lock must be held for write */ +static void detach_mnt (struct mount *mnt,struct path *old_path) +{ +__detach_mnt (mnt, old_path); +list_del_init (&mnt->mnt_child); +} + +/* +* vfsmount lock must be held for write +*/ void mnt_set_mountpoint (struct mount *mnt,struct mountpoint *mp,struct mount *child_mnt)
Signature · 1 line
2.2.1
PATCH 7/9 mnt: Simplify umount_tree
In reply to a message not in the archiveReplace the open coded __detach_mnt with __detach_mnt.
--- fs/namespace.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-)
fs/namespace.c +1 -9
@@ -1368,16 +1368,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ;if (mnt_has_parent (p)) { -hlist_del_init (&p->mnt_mp_list); -put_mountpoint (p->mnt_mp);mnt_add_count (p->mnt_parent,-1 ); -/* move the reference to mountpoint into ->mnt_ex_mountpoint */ - p->mnt_ex_mountpoint.dentry = p->mnt_mountpoint; - p->mnt_ex_mountpoint.mnt = &p->mnt_parent->mnt; - p->mnt_mountpoint = p->mnt.mnt_root; - p->mnt_parent = p; - p->mnt_mp =NULL ; -hlist_del_init_rcu (&p->mnt_hash); +__detach_mnt (p, &p->mnt_ex_mountpoint); }change_mnt_propagation (p,MS_PRIVATE ); }
Signature · 1 line
2.2.1
PATCH 8/9 mnt: Remove redundant NULL tests in namespace_unlock
In reply to a message not in the archivemntget, mntput, dput and pathput all test their arguments to see if they are NULL before taking any action, so testing for NULL in namespace_unlock is redundant.
Remove the redundant checks making namespace_unlock a little shorter and easier to read.
This also makes it possible for mnt_ex_mountpoint.mnt to be NULL allowing putting a dentry without a mount. This is will be needed in __detach_mounts when detaching already unmounted children, as part of the fix for MNT_DETACH on MNT_LOCKED mounts.
--- fs/namespace.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
@@ -1312,8 +1312,7 @@ static void namespace_unlock(void)/* undo decrements we'd done in umount_tree() */ list_for_each_entry (mnt, &head, mnt_list) -if (mnt->mnt_ex_mountpoint.mnt) -mntget (mnt->mnt_ex_mountpoint.mnt); +mntget (mnt->mnt_ex_mountpoint.mnt);up_write (&namespace_sem);
@@ -1322,8 +1321,7 @@ static void namespace_unlock(void)while (!list_empty (&head)) { mnt = list_first_entry(&head, struct mount, mnt_list);list_del_init (&mnt->mnt_list); -if (mnt->mnt_ex_mountpoint.mnt) -path_put (&mnt->mnt_ex_mountpoint); +path_put (&mnt->mnt_ex_mountpoint);mntput (&mnt->mnt); } }
Signature · 1 line
2.2.1
PATCH 9/9 mnt: Honor MNT_LOCKED when detaching mounts
In reply to a message not in the archiveModify umount(MNT_DETACH) to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted. In doing this invert the reference count so that the parent holds a reference to the children instead of the children holding a reference to the parent.
Then in mntput_no_expire detach the children and in cleanup_mnt mntput the children and dput the dentry they were mounted on.
In __detach_mounts if there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, detach those mounts and schedule them to be mntput and their reference to the dentry to be put when it becomes safe to sleep.
--- fs/namespace.c | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-)
fs/namespace.c +43 -4
@@ -1017,6 +1017,17 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,return ERR_PTR (err); } +static void mntput_children (struct mount *mnt) +{ +struct mount *p, *tmp; + +list_for_each_entry_safe (p, tmp, &mnt->mnt_mounts, mnt_child) { +list_del_init (&p->mnt_child); +path_put (&p->mnt_ex_mountpoint); +mntput (&p->mnt); + } +} +static void cleanup_mnt (struct mount *mnt) {/*
@@ -1030,6 +1041,8 @@ static void cleanup_mnt(struct mount *mnt)* so mnt_get_writers() below is safe. */ WARN_ON (mnt_get_writers (mnt)); +if (unlikely (!list_empty (&mnt->mnt_mounts))) +mntput_children (mnt);if (unlikely (mnt->mnt_pins.first))mnt_pin_kill (mnt);fsnotify_vfsmount_delete (&mnt->mnt);
@@ -1080,6 +1093,15 @@ static void mntput_no_expire(struct mount *mnt)rcu_read_unlock ();list_del (&mnt->mnt_instance); + +if (unlikely (!list_empty (&mnt->mnt_mounts))) { +struct mount *p; +list_for_each_entry (p, &mnt->mnt_mounts, mnt_child) { +__detach_mnt (p, &p->mnt_ex_mountpoint); +/* No need to mntput mnt */ + p->mnt_ex_mountpoint.mnt =NULL ; + } + }unlock_mount_hash ();if (likely (!(mnt->mnt.mnt_flags &MNT_INTERNAL ))) {
@@ -1342,7 +1364,7 @@ enum umount_tree_flags {static void umount_tree (struct mount *mnt,enum umount_tree_flags how) {LIST_HEAD (tmp_list); -struct mount *p; +struct mount *tmp, *p;/* Gather the mounts to umount */ for (p = mnt; p; p =next_mnt (p, mnt)) {
@@ -1359,7 +1381,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list); -list_for_each_entry (p, &tmp_list, mnt_list) { +list_for_each_entry_safe (p, tmp, &tmp_list, mnt_list) {list_del_init (&p->mnt_expire);__touch_mnt_namespace (p->mnt_ns); p->mnt_ns =NULL ;
@@ -1367,7 +1389,15 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ;if (mnt_has_parent (p)) {mnt_add_count (p->mnt_parent,-1 ); -__detach_mnt (p, &p->mnt_ex_mountpoint); +if ((p->mnt_parent->mnt.mnt_flags &MNT_UMOUNT ) && + ((p->mnt.mnt_flags & (MNT_LOCKED |MNT_SYNC_UMOUNT )) ==MNT_LOCKED )) { +/* Don't mntput p in namespace_unlock */ +list_del_init (&p->mnt_list); +/* Don't forget about p */ +list_add_tail (&p->mnt_child, &p->mnt_parent->mnt_mounts); + }else { +__detach_mnt (p, &p->mnt_ex_mountpoint); + } }change_mnt_propagation (p,MS_PRIVATE ); }
@@ -1493,7 +1523,16 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,0 ); +if (mnt->mnt.mnt_flags &MNT_UMOUNT ) { +struct mount *p, *tmp; +list_for_each_entry_safe (p, tmp, &mnt->mnt_mounts, mnt_child) { +detach_mnt (p, &p->mnt_ex_mountpoint); +/* p->mnt_parent has already been mntput */ + p->mnt_ex_mountpoint.mnt =NULL ; +list_add_tail (&p->mnt_list, &unmounted); + } + } +else umount_tree (mnt,0 ); }unlock_mount_hash ();put_mountpoint (mp);
Signature · 1 line
2.2.1
[PATCH review 0/11 Call for testing and review of mount detach fixes (take 2)
In reply to a message not in the archiveWay back in October Andrey Vagin reported that umount(MNT_DETACH) could be used to defeat MNT_LOCKED. As I worked to fix this I discovered that combined with mount propagation and an appropriate selection of shared subtrees a reference to a directory on an unmounted filesystem is not necessary.
That MNT_DETACH is allowed in user namespace in a form that can break MNT_LOCKED comes from my early misunderstanding what MNT_DETACH does.
To avoid breaking existing userspace the conflict between MNT_DETACH and MNT_LOCKED is fixed by leaving mounts that are locked to their parents in the mount hash table until the last reference goes away.
While investigating this issue I also found an issue with __detach_mounts. The code was unnecessarily and incorrectly triggering mount propagation. Resulting in too many mounts going away when a directory is deleted, and too many cpu cycles burned while doing that.
For those who like to see everything in a single tree the code is at:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-testing
Eric W. Biederman (11):
mnt: Improve the umount_tree flags
mnt: Don't propagate umounts in __detach_mounts
mnt: In umount_tree reuse mnt_list instead of mnt_hash
mnt: Add MNT_UMOUNT flag
mnt: Delay removal from the mount hash.
mnt: Factor out __detach_mnt from detach_mnt
mnt: Simplify umount_tree
mnt: Remove redundant NULL tests in namespace_unlock
mnt: On an unmount propagate clearing of MNT_LOCKED
mnt: Don't propagate unmounts to locked mounts
mnt: Honor MNT_LOCKED when detaching mounts
fs/namespace.c | 152 +++++++++++++++++++++++++++++++------------------- fs/pnode.c | 60 +++++++++++++++++--- fs/pnode.h | 7 ++- include/linux/mount.h | 1 + 4 files changed, 154 insertions(+), 66 deletions(-)
A message that is not in the archive (sent off-list or not yet received): [email protected]
PATCH 1/11 mnt: Improve the umount_tree flags
- Remove the unneeded declaration from pnode.h
- Mark umount_tree static as it has no callers outside of namespace.c
- Define an enumeration of umount_tree's flags.
- Pass umount_tree's flags in by name
This removes the magic numbers 0, 1 and 2 making the code a little clearer and makes it possible for there to be lazy unmounts that don't propagate. Which is what __detach_mounts actually wants for example.
--- fs/namespace.c | 31 ++++++++++++++++--------------- fs/pnode.h | 1 - 2 files changed, 16 insertions(+), 16 deletions(-)
fs/namespace.c +16 -15
@@ -1323,14 +1323,15 @@ static inline void namespace_lock(void)down_write (&namespace_sem); } +enum umount_tree_flags { +UMOUNT_SYNC =1 , +UMOUNT_PROPAGATE =2 , +};/* * mount_lock must be held * namespace_sem must be held for write -* how = 0 => just this tree, don't propagate -* how = 1 => propagate; we know that nobody else has reference to any victims -* how = 2 => lazy umount */ -void umount_tree (struct mount *mnt,int how) +static void umount_tree (struct mount *mnt,enum umount_tree_flags how) {HLIST_HEAD (tmp_list);struct mount *p;
@@ -1344,7 +1345,7 @@ void umount_tree(struct mount *mnt, int how)hlist_for_each_entry (p, &tmp_list, mnt_hash)list_del_init (&p->mnt_child); -if (how) +if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list);hlist_for_each_entry (p, &tmp_list, mnt_hash) {
@@ -1352,7 +1353,7 @@ void umount_tree(struct mount *mnt, int how)list_del_init (&p->mnt_list);__touch_mnt_namespace (p->mnt_ns); p->mnt_ns =NULL ; -if (how <2 ) +if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ;if (mnt_has_parent (p)) {hlist_del_init (&p->mnt_mp_list);
@@ -1457,14 +1458,14 @@ static int do_umount(struct mount *mnt, int flags)if (flags &MNT_DETACH ) {if (!list_empty (&mnt->mnt_list)) -umount_tree (mnt,2 ); +umount_tree (mnt,UMOUNT_PROPAGATE ); retval =0 ; }else {shrink_submounts (mnt); retval = -EBUSY ;if (!propagate_mount_busy (mnt,2 )) {if (!list_empty (&mnt->mnt_list)) -umount_tree (mnt,1 ); +umount_tree (mnt,UMOUNT_PROPAGATE |UMOUNT_SYNC ); retval =0 ; } }
@@ -1496,7 +1497,7 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,2 ); +umount_tree (mnt,UMOUNT_PROPAGATE ); }unlock_mount_hash ();put_mountpoint (mp);
@@ -1658,7 +1659,7 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry, out:if (res) {lock_mount_hash (); -umount_tree (res,0 ); +umount_tree (res,UMOUNT_SYNC );unlock_mount_hash (); }return q;
@@ -1682,7 +1683,7 @@ void drop_collected_mounts(struct vfsmount *mnt) {namespace_lock ();lock_mount_hash (); -umount_tree (real_mount (mnt),0 ); +umount_tree (real_mount (mnt),UMOUNT_SYNC );unlock_mount_hash ();namespace_unlock (); }
@@ -1865,7 +1866,7 @@ static int attach_recursive_mnt(struct mount *source_mnt, out_cleanup_ids:while (!hlist_empty (&tree_list)) { child = hlist_entry(tree_list.first, struct mount, mnt_hash); -umount_tree (child,0 ); +umount_tree (child,UMOUNT_SYNC ); }unlock_mount_hash ();cleanup_group_ids (source_mnt,NULL );
@@ -2045,7 +2046,7 @@ static int do_loopback(struct path *path, const char *old_name, err =graft_tree (mnt, parent, mp);if (err) {lock_mount_hash (); -umount_tree (mnt,0 ); +umount_tree (mnt,UMOUNT_SYNC );unlock_mount_hash (); } out2:
@@ -2416,7 +2417,7 @@ void mark_mounts_for_expiry(struct list_head *mounts)while (!list_empty (&graveyard)) { mnt = list_first_entry(&graveyard, struct mount, mnt_expire);touch_mnt_namespace (mnt->mnt_ns); -umount_tree (mnt,1 ); +umount_tree (mnt,UMOUNT_PROPAGATE |UMOUNT_SYNC ); }unlock_mount_hash ();namespace_unlock ();
@@ -2487,7 +2488,7 @@ static void shrink_submounts(struct mount *mnt) m = list_first_entry(&graveyard, struct mount, mnt_expire);touch_mnt_namespace (m->mnt_ns); -umount_tree (m,1 ); +umount_tree (m,UMOUNT_PROPAGATE |UMOUNT_SYNC ); } } }
fs/pnode.h +0 -1
@@ -47,7 +47,6 @@ int get_dominating_id(struct mount *mnt, const struct path *root);unsigned int mnt_get_count (struct mount *mnt);void mnt_set_mountpoint (struct mount *,struct mountpoint *,struct mount *); -void umount_tree (struct mount *,int );struct mount *copy_tree (struct mount *,struct dentry *,int );bool is_path_reachable (struct mount *,struct dentry *,const struct path *root);
Signature · 1 line
2.2.1
PATCH 2/11 mnt: Don't propagate umounts in __detach_mounts
In reply to a message not in the archiveInvoking mount propagation from __detach_mounts is inefficient and wrong.
It is inefficient because __detach_mounts already walks the list of mounts that where something needs to be done, and mount propagation walks some subset of those mounts again.
It is actively wrong because if the dentry that is passed to __detach_mounts is not part of the path to a mount that mount should not be affected.
change_mnt_propagation(p,MS_PRIVATE) modifies the mount propagation tree of a master mount so it's slaves are connected to another master if possible. Which means even removing a mount from the middle of a mount tree with __detach_mounts will not deprive any mount propagated mount events.
--- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
@@ -1497,7 +1497,7 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,UMOUNT_PROPAGATE ); +umount_tree (mnt,0 ); }unlock_mount_hash ();put_mountpoint (mp);
Signature · 1 line
2.2.1
PATCH 3/11 mnt: In umount_tree reuse mnt_list instead of mnt_hash
In reply to a message not in the archiveumount_tree builds a list of mounts that need to be unmounted. Utilize mnt_list for this purpose instead of mnt_hash as mnt_list is an ordianry list_head, allowing the use of list_splice and list_move instead of rolling our own.
This also begins to allow keeping a mount on the mnt_hash after it is unmounted. Which is necessary for a properly functioning MNT_LOCKED implementation.
--- fs/namespace.c | 45 +++++++++++++++++++-------------------------- fs/pnode.c | 6 +++--- fs/pnode.h | 2 +- 3 files changed, 23 insertions(+), 30 deletions(-)
fs/namespace.c +19 -26
@@ -1285,23 +1285,22 @@ int may_umount(struct vfsmount *mnt)EXPORT_SYMBOL (may_umount); -static HLIST_HEAD (unmounted);/* protected by namespace_sem */ +static LIST_HEAD (unmounted);/* protected by namespace_sem */ static void namespace_unlock (void ) {struct mount *mnt; -struct hlist_head head = unmounted; +LIST_HEAD (head); -if (likely (hlist_empty (&head))) { +if (likely (list_empty (&unmounted))) {up_write (&namespace_sem);return ; } - head.first->pprev = &head.first; -INIT_HLIST_HEAD (&unmounted); +list_splice_init (&unmounted, &head);/* undo decrements we'd done in umount_tree() */ -hlist_for_each_entry (mnt, &head, mnt_hash) +list_for_each_entry (mnt, &head, mnt_list)if (mnt->mnt_ex_mountpoint.mnt)mntget (mnt->mnt_ex_mountpoint.mnt);
@@ -1309,9 +1308,9 @@ static void namespace_unlock(void)synchronize_rcu (); -while (!hlist_empty (&head)) { - mnt = hlist_entry(head.first, struct mount, mnt_hash); -hlist_del_init (&mnt->mnt_hash); +while (!list_empty (&head)) { + mnt = list_first_entry(&head, struct mount, mnt_list); +list_del_init (&mnt->mnt_list);if (mnt->mnt_ex_mountpoint.mnt)path_put (&mnt->mnt_ex_mountpoint);mntput (&mnt->mnt);
@@ -1333,24 +1332,25 @@ enum umount_tree_flags {*/ static void umount_tree (struct mount *mnt,enum umount_tree_flags how) { -HLIST_HEAD (tmp_list); +LIST_HEAD (tmp_list);struct mount *p; -struct mount *last =NULL ; -for (p = mnt; p; p =next_mnt (p, mnt)) { -hlist_del_init_rcu (&p->mnt_hash); -hlist_add_head (&p->mnt_hash, &tmp_list); - } +/* Gather the mounts to umount */ +for (p = mnt; p; p =next_mnt (p, mnt)) +list_move (&p->mnt_list, &tmp_list); -hlist_for_each_entry (p, &tmp_list, mnt_hash) +/* Hide the mounts from lookup_mnt and mnt_mounts */ +list_for_each_entry (p, &tmp_list, mnt_list) { +hlist_del_init_rcu (&p->mnt_hash);list_del_init (&p->mnt_child); + } +/* Add propogated mounts to the tmp_list */ if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list); -hlist_for_each_entry (p, &tmp_list, mnt_hash) { +list_for_each_entry (p, &tmp_list, mnt_list) {list_del_init (&p->mnt_expire); -list_del_init (&p->mnt_list);__touch_mnt_namespace (p->mnt_ns); p->mnt_ns =NULL ;if (how &UMOUNT_SYNC )
@@ -1367,15 +1367,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt_mp =NULL ; }change_mnt_propagation (p,MS_PRIVATE ); - last = p; - } -if (last) { - last->mnt_hash.next = unmounted.first; -if (unmounted.first) - unmounted.first->pprev = &last->mnt_hash.next; - unmounted.first = tmp_list.first; - unmounted.first->pprev = &unmounted.first; } +list_splice (&tmp_list, &unmounted); }static void shrink_submounts (struct mount *mnt);
fs/pnode.c +3 -3
@@ -384,7 +384,7 @@ static void __propagate_umount(struct mount *mnt)if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child);hlist_del_init_rcu (&child->mnt_hash); -hlist_add_before_rcu (&child->mnt_hash, &mnt->mnt_hash); +list_move_tail (&child->mnt_list, &mnt->mnt_list); } } }
@@ -396,11 +396,11 @@ static void __propagate_umount(struct mount *mnt)* * vfsmount lock must be held for write */ -int propagate_umount (struct hlist_head *list) +int propagate_umount (struct list_head *list) {struct mount *mnt; -hlist_for_each_entry (mnt, list, mnt_hash) +list_for_each_entry (mnt, list, mnt_list)__propagate_umount (mnt);return 0 ; }
fs/pnode.h +1 -1
@@ -40,7 +40,7 @@ static inline void set_mnt_shared(struct mount *mnt)void change_mnt_propagation (struct mount *,int );int propagate_mnt (struct mount *,struct mountpoint *,struct mount *,struct hlist_head *); -int propagate_umount (struct hlist_head *); +int propagate_umount (struct list_head *);int propagate_mount_busy (struct mount *,int );void mnt_release_group_id (struct mount *);int get_dominating_id (struct mount *mnt,const struct path *root);
Signature · 1 line
2.2.1
PATCH 4/11 mnt: Add MNT_UMOUNT flag
In reply to a message not in the archiveIn some instances it is necessary to know if the the unmounting process has begun on a mount. Add MNT_UMOUNT to make that reliably testable.
This fix gets used in fixing locked mounts in MNT_DETACH
--- fs/namespace.c | 4 +++- fs/pnode.c | 1 + include/linux/mount.h | 1 + 3 files changed, 5 insertions(+), 1 deletion(-)
fs/namespace.c +3 -1
@@ -1336,8 +1336,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)struct mount *p;/* Gather the mounts to umount */ -for (p = mnt; p; p =next_mnt (p, mnt)) +for (p = mnt; p; p =next_mnt (p, mnt)) { + p->mnt.mnt_flags |=MNT_UMOUNT ;list_move (&p->mnt_list, &tmp_list); + }/* Hide the mounts from lookup_mnt and mnt_mounts */ list_for_each_entry (p, &tmp_list, mnt_list) {
fs/pnode.c +1 -0
@@ -384,6 +384,7 @@ static void __propagate_umount(struct mount *mnt)if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child);hlist_del_init_rcu (&child->mnt_hash); + child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list); } }
include/linux/mount.h +1 -0
@@ -61,6 +61,7 @@ struct mnt_namespace;#define MNT_DOOMED 0x1000000#define MNT_SYNC_UMOUNT 0x2000000#define MNT_MARKED 0x4000000 +#define MNT_UMOUNT 0x8000000struct vfsmount {struct dentry *mnt_root;/* root of the mounted tree */
Signature · 1 line
2.2.1
PATCH 5/11 mnt: Delay removal from the mount hash.
In reply to a message not in the archive- Modify __lookup_mnt_hash_last to ignore mounts that have MNT_UMOUNTED set.
- Don't remove mounts from the mount hash table in propogate_umount
- Don't remove mounts from the mount hash table in umount_tree before the entire list of mounts to be umounted is selected.
- Remove mounts from the mount hash table as the last thing that happens in the case where a mount has a parent in umount_tree. Mounts without parents are not hashed (by definition).
This paves the way for delaying removal from the mount hash table even farther and fixing the MNT_LOCKED vs MNT_DETACH issue.
--- fs/namespace.c | 13 ++++++++----- fs/pnode.c | 1 - 2 files changed, 8 insertions(+), 6 deletions(-)
fs/namespace.c +8 -5
@@ -623,14 +623,17 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)*/ struct mount *__lookup_mnt_last (struct vfsmount *mnt,struct dentry *dentry) { -struct mount *p, *res; - res = p =__lookup_mnt (mnt, dentry); +struct mount *p, *res =NULL ; + p =__lookup_mnt (mnt, dentry);if (!p) goto out; +if (!(p->mnt.mnt_flags &MNT_UMOUNT )) + res = p;hlist_for_each_entry_continue (p, mnt_hash) {if (&p->mnt_parent->mnt != mnt || p->mnt_mountpoint != dentry)break ; - res = p; +if (!(p->mnt.mnt_flags &MNT_UMOUNT )) + res = p; } out:return res;
@@ -1341,9 +1344,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)list_move (&p->mnt_list, &tmp_list); } -/* Hide the mounts from lookup_mnt and mnt_mounts */ +/* Hide the mounts from mnt_mounts */ list_for_each_entry (p, &tmp_list, mnt_list) { -hlist_del_init_rcu (&p->mnt_hash);list_del_init (&p->mnt_child); }
@@ -1367,6 +1369,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt_mountpoint = p->mnt.mnt_root; p->mnt_parent = p; p->mnt_mp =NULL ; +hlist_del_init_rcu (&p->mnt_hash); }change_mnt_propagation (p,MS_PRIVATE ); }
fs/pnode.c +0 -1
@@ -383,7 +383,6 @@ static void __propagate_umount(struct mount *mnt)*/ if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child); -hlist_del_init_rcu (&child->mnt_hash); child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list); }
Signature · 1 line
2.2.1
PATCH 6/11 mnt: Factor out __detach_mnt from detach_mnt
In reply to a message not in the archiveAn upcoming change is going to need a version of detach_mnt that leaves the mount on the parents mnt_mounts list. Create that version of detach_mnt now and call it __detach_mnt.
--- fs/namespace.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
fs/namespace.c +10 -2
@@ -789,13 +789,12 @@ static void __touch_mnt_namespace(struct mnt_namespace *ns)/* * vfsmount lock must be held for write */ -static void detach_mnt (struct mount *mnt,struct path *old_path) +static void __detach_mnt (struct mount *mnt,struct path *old_path) { old_path->dentry = mnt->mnt_mountpoint; old_path->mnt = &mnt->mnt_parent->mnt; mnt->mnt_parent = mnt; mnt->mnt_mountpoint = mnt->mnt.mnt_root; -list_del_init (&mnt->mnt_child);hlist_del_init_rcu (&mnt->mnt_hash);hlist_del_init (&mnt->mnt_mp_list);put_mountpoint (mnt->mnt_mp);
@@ -805,6 +804,15 @@ static void detach_mnt(struct mount *mnt, struct path *old_path)/* * vfsmount lock must be held for write */ +static void detach_mnt (struct mount *mnt,struct path *old_path) +{ +__detach_mnt (mnt, old_path); +list_del_init (&mnt->mnt_child); +} + +/* +* vfsmount lock must be held for write +*/ void mnt_set_mountpoint (struct mount *mnt,struct mountpoint *mp,struct mount *child_mnt)
Signature · 1 line
2.2.1
PATCH 7/11 mnt: Simplify umount_tree
In reply to a message not in the archiveReplace the open coded __detach_mnt with __detach_mnt.
--- fs/namespace.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-)
fs/namespace.c +1 -9
@@ -1368,16 +1368,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ;if (mnt_has_parent (p)) { -hlist_del_init (&p->mnt_mp_list); -put_mountpoint (p->mnt_mp);mnt_add_count (p->mnt_parent,-1 ); -/* move the reference to mountpoint into ->mnt_ex_mountpoint */ - p->mnt_ex_mountpoint.dentry = p->mnt_mountpoint; - p->mnt_ex_mountpoint.mnt = &p->mnt_parent->mnt; - p->mnt_mountpoint = p->mnt.mnt_root; - p->mnt_parent = p; - p->mnt_mp =NULL ; -hlist_del_init_rcu (&p->mnt_hash); +__detach_mnt (p, &p->mnt_ex_mountpoint); }change_mnt_propagation (p,MS_PRIVATE ); }
Signature · 1 line
2.2.1
PATCH 8/11 mnt: Remove redundant NULL tests in namespace_unlock
In reply to a message not in the archivemntget, mntput, dput and pathput all test their arguments to see if they are NULL before taking any action, so testing for NULL in namespace_unlock is redundant.
Remove the redundant checks making namespace_unlock a little shorter and easier to read.
This also makes it possible for mnt_ex_mountpoint.mnt to be NULL allowing putting a dentry without a mount. This is will be needed in __detach_mounts when detaching already unmounted children, as part of the fix for MNT_DETACH on MNT_LOCKED mounts.
--- fs/namespace.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
@@ -1312,8 +1312,7 @@ static void namespace_unlock(void)/* undo decrements we'd done in umount_tree() */ list_for_each_entry (mnt, &head, mnt_list) -if (mnt->mnt_ex_mountpoint.mnt) -mntget (mnt->mnt_ex_mountpoint.mnt); +mntget (mnt->mnt_ex_mountpoint.mnt);up_write (&namespace_sem);
@@ -1322,8 +1321,7 @@ static void namespace_unlock(void)while (!list_empty (&head)) { mnt = list_first_entry(&head, struct mount, mnt_list);list_del_init (&mnt->mnt_list); -if (mnt->mnt_ex_mountpoint.mnt) -path_put (&mnt->mnt_ex_mountpoint); +path_put (&mnt->mnt_ex_mountpoint);mntput (&mnt->mnt); } }
Signature · 1 line
2.2.1
PATCH 9/11 mnt: On an unmount propagate clearing of MNT_LOCKED
In reply to a message not in the archiveA prerequisite of calling umount_tree is that the point where the tree is mounted at is valid to unmount.
If we are propagating the effect of the unmount clear MNT_LOCKED in every instance where the same filesystem is mounted on the same mountpoint in the mount tree, as we know (by virtue of the fact that umount_tree was called) that it is safe to reveal what is at that mountpoint.
--- fs/namespace.c | 3 +++ fs/pnode.c | 20 ++++++++++++++++++++ fs/pnode.h | 1 + 3 files changed, 24 insertions(+)
fs/namespace.c +3 -0
@@ -1344,6 +1344,9 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)LIST_HEAD (tmp_list);struct mount *p; +if (how &UMOUNT_PROPAGATE ) +propagate_mount_unlock (mnt); +/* Gather the mounts to umount */ for (p = mnt; p; p =next_mnt (p, mnt)) { p->mnt.mnt_flags |=MNT_UMOUNT ;
fs/pnode.c +20 -0
@@ -362,6 +362,26 @@ int propagate_mount_busy(struct mount *mnt, int refcnt) }/* +* Clear MNT_LOCKED when it can be shown to be safe. +* +* mount_lock lock must be held for write +*/ +void propagate_mount_unlock (struct mount *mnt) +{ +struct mount *parent = mnt->mnt_parent; +struct mount *m, *child; + +BUG_ON (parent == mnt); + +for (m =propagation_next (parent, parent); m; + m =propagation_next (m, parent)) { + child =__lookup_mnt_last (&m->mnt, mnt->mnt_mountpoint); +if (child) + child->mnt.mnt_flags &= ~MNT_LOCKED ; + } +} + +/* * NOTE: unmounting 'mnt' naturally propagates to all other mounts its * parent propagates to. */
fs/pnode.h +1 -0
@@ -42,6 +42,7 @@ int propagate_mnt(struct mount *, struct mountpoint *, struct mount *, struct hlist_head *);int propagate_umount (struct list_head *);int propagate_mount_busy (struct mount *,int ); +void propagate_mount_unlock (struct mount *);void mnt_release_group_id (struct mount *);int get_dominating_id (struct mount *mnt,const struct path *root);unsigned int mnt_get_count (struct mount *mnt);
Signature · 1 line
2.2.1
PATCH 10/11 mnt: Don't propagate unmounts to locked mounts
In reply to a message not in the archiveIf the first mount in shared subtree is locked don't unmount the shared subtree.
This is ensured by walking through the mounts parents before children and marking a mount as unmountable if it is not locked or it is locked but it's parent is marked.
This allows recursive mount detach to propagate through a set of mounts when unmounting them would not reveal what is under any locked mount.
--- fs/pnode.c | 32 +++++++++++++++++++++++++++++--- fs/pnode.h | 1 + 2 files changed, 30 insertions(+), 3 deletions(-)
fs/pnode.c +29 -3
@@ -382,6 +382,26 @@ void propagate_mount_unlock(struct mount *mnt) }/* +* Mark all mounts that the MNT_LOCKED logic will allow to be unmounted. +*/ +static void mark_umount_candidates (struct mount *mnt) +{ +struct mount *parent = mnt->mnt_parent; +struct mount *m; + +BUG_ON (parent == mnt); + +for (m =propagation_next (parent, parent); m; + m =propagation_next (m, parent)) { +struct mount *child =__lookup_mnt_last (&m->mnt, + mnt->mnt_mountpoint); +if (child && (!IS_MNT_LOCKED (child) ||IS_MNT_MARKED (m))) { +SET_MNT_MARK (child); + } + } +} + +/* * NOTE: unmounting 'mnt' naturally propagates to all other mounts its * parent propagates to. */
@@ -398,10 +418,13 @@ static void __propagate_umount(struct mount *mnt)struct mount *child =__lookup_mnt_last (&m->mnt, mnt->mnt_mountpoint);/* -* umount the child only if the child has no -* other children +* umount the child only if the child has no children +* and the child is marked safe to unmount. */ -if (child &&list_empty (&child->mnt_mounts)) { +if (!child || !IS_MNT_MARKED (child)) +continue ; +CLEAR_MNT_MARK (child); +if (list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child); child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list);
@@ -420,6 +443,9 @@ int propagate_umount(struct list_head *list) {struct mount *mnt; +list_for_each_entry_reverse (mnt, list, mnt_list) +mark_umount_candidates (mnt); +list_for_each_entry (mnt, list, mnt_list)__propagate_umount (mnt);return 0 ;
fs/pnode.h +1 -0
@@ -19,6 +19,7 @@#define IS_MNT_MARKED (m) ((m)->mnt.mnt_flags & MNT_MARKED)#define SET_MNT_MARK (m) ((m)->mnt.mnt_flags |= MNT_MARKED)#define CLEAR_MNT_MARK (m) ((m)->mnt.mnt_flags &= ~MNT_MARKED) +#define IS_MNT_LOCKED (m) ((m)->mnt.mnt_flags & MNT_LOCKED)#define CL_EXPIRE 0x01#define CL_SLAVE 0x02
Signature · 1 line
2.2.1
PATCH 11/11 mnt: Honor MNT_LOCKED when detaching mounts
In reply to a message not in the archiveModify umount(MNT_DETACH) to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted. In doing this invert the reference count so that the parent holds a reference to the children instead of the children holding a reference to the parent.
Then in mntput_no_expire detach the children and in cleanup_mnt mntput the children and dput the dentry they were mounted on.
In __detach_mounts if there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, detach those mounts and schedule them to be mntput and their reference to the dentry to be put when it becomes safe to sleep.
--- fs/namespace.c | 46 ++++++++++++++++++++++++++++++++++++++++++---- fs/pnode.h | 2 ++ 2 files changed, 44 insertions(+), 4 deletions(-)
fs/namespace.c +42 -4
@@ -1017,6 +1017,17 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,return ERR_PTR (err); } +static void mntput_children (struct mount *mnt) +{ +struct mount *p, *tmp; + +list_for_each_entry_safe (p, tmp, &mnt->mnt_mounts, mnt_child) { +list_del_init (&p->mnt_child); +path_put (&p->mnt_ex_mountpoint); +mntput (&p->mnt); + } +} +static void cleanup_mnt (struct mount *mnt) {/*
@@ -1030,6 +1041,8 @@ static void cleanup_mnt(struct mount *mnt)* so mnt_get_writers() below is safe. */ WARN_ON (mnt_get_writers (mnt)); +if (unlikely (!list_empty (&mnt->mnt_mounts))) +mntput_children (mnt);if (unlikely (mnt->mnt_pins.first))mnt_pin_kill (mnt);fsnotify_vfsmount_delete (&mnt->mnt);
@@ -1080,6 +1093,15 @@ static void mntput_no_expire(struct mount *mnt)rcu_read_unlock ();list_del (&mnt->mnt_instance); + +if (unlikely (!list_empty (&mnt->mnt_mounts))) { +struct mount *p; +list_for_each_entry (p, &mnt->mnt_mounts, mnt_child) { +__detach_mnt (p, &p->mnt_ex_mountpoint); +/* No need to mntput mnt */ + p->mnt_ex_mountpoint.mnt =NULL ; + } + }unlock_mount_hash ();if (likely (!(mnt->mnt.mnt_flags &MNT_INTERNAL ))) {
@@ -1342,7 +1364,7 @@ enum umount_tree_flags {static void umount_tree (struct mount *mnt,enum umount_tree_flags how) {LIST_HEAD (tmp_list); -struct mount *p; +struct mount *tmp, *p;if (how &UMOUNT_PROPAGATE )propagate_mount_unlock (mnt);
@@ -1362,7 +1384,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list); -list_for_each_entry (p, &tmp_list, mnt_list) { +list_for_each_entry_safe (p, tmp, &tmp_list, mnt_list) {list_del_init (&p->mnt_expire);__touch_mnt_namespace (p->mnt_ns); p->mnt_ns =NULL ;
@@ -1370,7 +1392,14 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ;if (mnt_has_parent (p)) {mnt_add_count (p->mnt_parent,-1 ); -__detach_mnt (p, &p->mnt_ex_mountpoint); +if (IS_MNT_LOCKED_AND_LAZY (p)) { +/* Don't mntput p in namespace_unlock */ +list_del_init (&p->mnt_list); +/* Don't forget about p */ +list_add_tail (&p->mnt_child, &p->mnt_parent->mnt_mounts); + }else { +__detach_mnt (p, &p->mnt_ex_mountpoint); + } }change_mnt_propagation (p,MS_PRIVATE ); }
@@ -1496,7 +1525,16 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,0 ); +if (mnt->mnt.mnt_flags &MNT_UMOUNT ) { +struct mount *p, *tmp; +list_for_each_entry_safe (p, tmp, &mnt->mnt_mounts, mnt_child) { +detach_mnt (p, &p->mnt_ex_mountpoint); +/* p->mnt_parent has already been mntput */ + p->mnt_ex_mountpoint.mnt =NULL ; +list_add_tail (&p->mnt_list, &unmounted); + } + } +else umount_tree (mnt,0 ); }unlock_mount_hash ();put_mountpoint (mp);
fs/pnode.h +2 -0
@@ -20,6 +20,8 @@#define SET_MNT_MARK (m) ((m)->mnt.mnt_flags |= MNT_MARKED)#define CLEAR_MNT_MARK (m) ((m)->mnt.mnt_flags &= ~MNT_MARKED)#define IS_MNT_LOCKED (m) ((m)->mnt.mnt_flags & MNT_LOCKED) +#define IS_MNT_LOCKED_AND_LAZY (m) \ + (((m)->mnt.mnt_flags & (MNT_LOCKED|MNT_SYNC_UMOUNT)) == MNT_LOCKED)#define CL_EXPIRE 0x01#define CL_SLAVE 0x02
Signature · 1 line
2.2.1
cover Locked mount and loopback mount fixes
In reply to a message not in the archiveWay back in October Andrey Vagin reported that umount(MNT_DETACH) could be used to defeat MNT_LOCKED. As I worked to fix this I discovered that combined with mount propagation and an appropriate selection of shared subtrees a reference to a directory on an unmounted filesystem is not necessary.
That MNT_DETACH is allowed in user namespace in a form that can break MNT_LOCKED comes from my early misunderstanding what MNT_DETACH does.
To avoid breaking existing userspace the conflict between MNT_DETACH and MNT_LOCKED is fixed by leaving mounts that are locked to their parents in the mount hash table until the last reference goes away.
While investigating this issue I also found an issue with __detach_mounts. The code was unnecessarily and incorrectly triggering mount propagation. Resulting in too many mounts going away when a directory is deleted, and too many cpu cycles are burned while doing that.
Looking some more I realized that __detach_mounts by only keeping mounts connected that were MNT_LOCKED it had the potential to still leak information so I tweaked the code to keep everything locked together that possibly could be.
In the middle of all of this bug hunting and fixing it was reported that with a strategically placed rename ".." on bind mounts could go up past their root of the bind mount. Which turned out to be very easy to understand and test for but tricky to actually fix in a way that would not slow down path name lookups in the common case.
These fixes are against on v4.0-rc6 which has all of Al's new fs_pin code.
I have tested the code and I don't see any issues but as I am human I may have missed a corner case or two. So any feedback is appreciated.
For those who like to see everything in a single tree the code is at:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-testing
Eric W. Biederman (19):
mnt: Use hlist_move_list in namespace_unlock
mnt: Improve the umount_tree flags
mnt: Don't propagate umounts in __detach_mounts
mnt: In umount_tree reuse mnt_list instead of mnt_hash
mnt: Add MNT_UMOUNT flag
mnt: Delay removal from the mount hash.
mnt: On an unmount propagate clearing of MNT_LOCKED
mnt: Don't propagate unmounts to locked mounts
mnt: Fail collect_mounts when applied to unmounted mounts
mnt: Factor out unhash_mnt from detach_mnt and umount_tree
mnt: Factor umount_mnt from umount_tree
fs_pin: Allow for the possibility that m_list or s_list go unused.
mnt: Honor MNT_LOCKED when detaching mounts
mnt: Fix the error check in __detach_mounts
mnt: Update detach_mounts to leave mounts connected
mnt: Track which mounts use a dentry as root.
vfs: Test for and handle paths that are unreachable from their mnt_root
vfs: Handle mounts whose parents are unreachable from their mountpoint
vfs: Do not allow escaping from bind mounts.
fs/dcache.c | 35 +++++- fs/fs_pin.c | 4 +- fs/internal.h | 2 + fs/mount.h | 8 ++ fs/namei.c | 34 +++++- fs/namespace.c | 325 +++++++++++++++++++++++++++++++++++++++++-------- fs/pnode.c | 60 +++++++-- fs/pnode.h | 7 +- include/linux/dcache.h | 7 ++ include/linux/fs_pin.h | 2 + include/linux/mount.h | 3 + include/linux/namei.h | 2 + 12 files changed, 424 insertions(+), 65 deletions(-)
PATCH 2/11 mnt: Don't propagate umounts in __detach_mounts
In reply to Eric W. BiedermanInvoking mount propagation from __detach_mounts is inefficient and wrong.
It is inefficient because __detach_mounts already walks the list of mounts that where something needs to be done, and mount propagation walks some subset of those mounts again.
It is actively wrong because if the dentry that is passed to __detach_mounts is not part of the path to a mount that mount should not be affected.
change_mnt_propagation(p,MS_PRIVATE) modifies the mount propagation tree of a master mount so it's slaves are connected to another master if possible. Which means even removing a mount from the middle of a mount tree with __detach_mounts will not deprive any mount propagated mount events.
--- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
@@ -1497,7 +1497,7 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,UMOUNT_PROPAGATE ); +umount_tree (mnt,0 ); }unlock_mount_hash ();put_mountpoint (mp);
Signature · 1 line
2.2.1
PATCH 3/11 mnt: In umount_tree reuse mnt_list instead of mnt_hash
In reply to Eric W. Biedermanumount_tree builds a list of mounts that need to be unmounted. Utilize mnt_list for this purpose instead of mnt_hash as mnt_list is an ordianry list_head, allowing the use of list_splice and list_move instead of rolling our own.
This also begins to allow keeping a mount on the mnt_hash after it is unmounted. Which is necessary for a properly functioning MNT_LOCKED implementation.
--- fs/namespace.c | 45 +++++++++++++++++++-------------------------- fs/pnode.c | 6 +++--- fs/pnode.h | 2 +- 3 files changed, 23 insertions(+), 30 deletions(-)
fs/namespace.c +19 -26
@@ -1285,23 +1285,22 @@ int may_umount(struct vfsmount *mnt)EXPORT_SYMBOL (may_umount); -static HLIST_HEAD (unmounted);/* protected by namespace_sem */ +static LIST_HEAD (unmounted);/* protected by namespace_sem */ static void namespace_unlock (void ) {struct mount *mnt; -struct hlist_head head = unmounted; +LIST_HEAD (head); -if (likely (hlist_empty (&head))) { +if (likely (list_empty (&unmounted))) {up_write (&namespace_sem);return ; } - head.first->pprev = &head.first; -INIT_HLIST_HEAD (&unmounted); +list_splice_init (&unmounted, &head);/* undo decrements we'd done in umount_tree() */ -hlist_for_each_entry (mnt, &head, mnt_hash) +list_for_each_entry (mnt, &head, mnt_list)if (mnt->mnt_ex_mountpoint.mnt)mntget (mnt->mnt_ex_mountpoint.mnt);
@@ -1309,9 +1308,9 @@ static void namespace_unlock(void)synchronize_rcu (); -while (!hlist_empty (&head)) { - mnt = hlist_entry(head.first, struct mount, mnt_hash); -hlist_del_init (&mnt->mnt_hash); +while (!list_empty (&head)) { + mnt = list_first_entry(&head, struct mount, mnt_list); +list_del_init (&mnt->mnt_list);if (mnt->mnt_ex_mountpoint.mnt)path_put (&mnt->mnt_ex_mountpoint);mntput (&mnt->mnt);
@@ -1333,24 +1332,25 @@ enum umount_tree_flags {*/ static void umount_tree (struct mount *mnt,enum umount_tree_flags how) { -HLIST_HEAD (tmp_list); +LIST_HEAD (tmp_list);struct mount *p; -struct mount *last =NULL ; -for (p = mnt; p; p =next_mnt (p, mnt)) { -hlist_del_init_rcu (&p->mnt_hash); -hlist_add_head (&p->mnt_hash, &tmp_list); - } +/* Gather the mounts to umount */ +for (p = mnt; p; p =next_mnt (p, mnt)) +list_move (&p->mnt_list, &tmp_list); -hlist_for_each_entry (p, &tmp_list, mnt_hash) +/* Hide the mounts from lookup_mnt and mnt_mounts */ +list_for_each_entry (p, &tmp_list, mnt_list) { +hlist_del_init_rcu (&p->mnt_hash);list_del_init (&p->mnt_child); + } +/* Add propogated mounts to the tmp_list */ if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list); -hlist_for_each_entry (p, &tmp_list, mnt_hash) { +list_for_each_entry (p, &tmp_list, mnt_list) {list_del_init (&p->mnt_expire); -list_del_init (&p->mnt_list);__touch_mnt_namespace (p->mnt_ns); p->mnt_ns =NULL ;if (how &UMOUNT_SYNC )
@@ -1367,15 +1367,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt_mp =NULL ; }change_mnt_propagation (p,MS_PRIVATE ); - last = p; - } -if (last) { - last->mnt_hash.next = unmounted.first; -if (unmounted.first) - unmounted.first->pprev = &last->mnt_hash.next; - unmounted.first = tmp_list.first; - unmounted.first->pprev = &unmounted.first; } +list_splice (&tmp_list, &unmounted); }static void shrink_submounts (struct mount *mnt);
fs/pnode.c +3 -3
@@ -384,7 +384,7 @@ static void __propagate_umount(struct mount *mnt)if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child);hlist_del_init_rcu (&child->mnt_hash); -hlist_add_before_rcu (&child->mnt_hash, &mnt->mnt_hash); +list_move_tail (&child->mnt_list, &mnt->mnt_list); } } }
@@ -396,11 +396,11 @@ static void __propagate_umount(struct mount *mnt)* * vfsmount lock must be held for write */ -int propagate_umount (struct hlist_head *list) +int propagate_umount (struct list_head *list) {struct mount *mnt; -hlist_for_each_entry (mnt, list, mnt_hash) +list_for_each_entry (mnt, list, mnt_list)__propagate_umount (mnt);return 0 ; }
fs/pnode.h +1 -1
@@ -40,7 +40,7 @@ static inline void set_mnt_shared(struct mount *mnt)void change_mnt_propagation (struct mount *,int );int propagate_mnt (struct mount *,struct mountpoint *,struct mount *,struct hlist_head *); -int propagate_umount (struct hlist_head *); +int propagate_umount (struct list_head *);int propagate_mount_busy (struct mount *,int );void mnt_release_group_id (struct mount *);int get_dominating_id (struct mount *mnt,const struct path *root);
Signature · 1 line
2.2.1
PATCH 4/11 mnt: Add MNT_UMOUNT flag
In reply to Eric W. BiedermanIn some instances it is necessary to know if the the unmounting process has begun on a mount. Add MNT_UMOUNT to make that reliably testable.
This fix gets used in fixing locked mounts in MNT_DETACH
--- fs/namespace.c | 4 +++- fs/pnode.c | 1 + include/linux/mount.h | 1 + 3 files changed, 5 insertions(+), 1 deletion(-)
fs/namespace.c +3 -1
@@ -1336,8 +1336,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)struct mount *p;/* Gather the mounts to umount */ -for (p = mnt; p; p =next_mnt (p, mnt)) +for (p = mnt; p; p =next_mnt (p, mnt)) { + p->mnt.mnt_flags |=MNT_UMOUNT ;list_move (&p->mnt_list, &tmp_list); + }/* Hide the mounts from lookup_mnt and mnt_mounts */ list_for_each_entry (p, &tmp_list, mnt_list) {
fs/pnode.c +1 -0
@@ -384,6 +384,7 @@ static void __propagate_umount(struct mount *mnt)if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child);hlist_del_init_rcu (&child->mnt_hash); + child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list); } }
include/linux/mount.h +1 -0
@@ -61,6 +61,7 @@ struct mnt_namespace;#define MNT_DOOMED 0x1000000#define MNT_SYNC_UMOUNT 0x2000000#define MNT_MARKED 0x4000000 +#define MNT_UMOUNT 0x8000000struct vfsmount {struct dentry *mnt_root;/* root of the mounted tree */
Signature · 1 line
2.2.1
PATCH 6/11 mnt: Factor out __detach_mnt from detach_mnt
In reply to Eric W. BiedermanAn upcoming change is going to need a version of detach_mnt that leaves the mount on the parents mnt_mounts list. Create that version of detach_mnt now and call it __detach_mnt.
--- fs/namespace.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
fs/namespace.c +10 -2
@@ -789,13 +789,12 @@ static void __touch_mnt_namespace(struct mnt_namespace *ns)/* * vfsmount lock must be held for write */ -static void detach_mnt (struct mount *mnt,struct path *old_path) +static void __detach_mnt (struct mount *mnt,struct path *old_path) { old_path->dentry = mnt->mnt_mountpoint; old_path->mnt = &mnt->mnt_parent->mnt; mnt->mnt_parent = mnt; mnt->mnt_mountpoint = mnt->mnt.mnt_root; -list_del_init (&mnt->mnt_child);hlist_del_init_rcu (&mnt->mnt_hash);hlist_del_init (&mnt->mnt_mp_list);put_mountpoint (mnt->mnt_mp);
@@ -805,6 +804,15 @@ static void detach_mnt(struct mount *mnt, struct path *old_path)/* * vfsmount lock must be held for write */ +static void detach_mnt (struct mount *mnt,struct path *old_path) +{ +__detach_mnt (mnt, old_path); +list_del_init (&mnt->mnt_child); +} + +/* +* vfsmount lock must be held for write +*/ void mnt_set_mountpoint (struct mount *mnt,struct mountpoint *mp,struct mount *child_mnt)
Signature · 1 line
2.2.1
PATCH 7/11 mnt: Simplify umount_tree
In reply to Eric W. BiedermanReplace the open coded __detach_mnt with __detach_mnt.
--- fs/namespace.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-)
fs/namespace.c +1 -9
@@ -1368,16 +1368,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ;if (mnt_has_parent (p)) { -hlist_del_init (&p->mnt_mp_list); -put_mountpoint (p->mnt_mp);mnt_add_count (p->mnt_parent,-1 ); -/* move the reference to mountpoint into ->mnt_ex_mountpoint */ - p->mnt_ex_mountpoint.dentry = p->mnt_mountpoint; - p->mnt_ex_mountpoint.mnt = &p->mnt_parent->mnt; - p->mnt_mountpoint = p->mnt.mnt_root; - p->mnt_parent = p; - p->mnt_mp =NULL ; -hlist_del_init_rcu (&p->mnt_hash); +__detach_mnt (p, &p->mnt_ex_mountpoint); }change_mnt_propagation (p,MS_PRIVATE ); }
Signature · 1 line
2.2.1
PATCH 8/11 mnt: Remove redundant NULL tests in namespace_unlock
In reply to Eric W. Biedermanmntget, mntput, dput and pathput all test their arguments to see if they are NULL before taking any action, so testing for NULL in namespace_unlock is redundant.
Remove the redundant checks making namespace_unlock a little shorter and easier to read.
This also makes it possible for mnt_ex_mountpoint.mnt to be NULL allowing putting a dentry without a mount. This is will be needed in __detach_mounts when detaching already unmounted children, as part of the fix for MNT_DETACH on MNT_LOCKED mounts.
--- fs/namespace.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
@@ -1312,8 +1312,7 @@ static void namespace_unlock(void)/* undo decrements we'd done in umount_tree() */ list_for_each_entry (mnt, &head, mnt_list) -if (mnt->mnt_ex_mountpoint.mnt) -mntget (mnt->mnt_ex_mountpoint.mnt); +mntget (mnt->mnt_ex_mountpoint.mnt);up_write (&namespace_sem);
@@ -1322,8 +1321,7 @@ static void namespace_unlock(void)while (!list_empty (&head)) { mnt = list_first_entry(&head, struct mount, mnt_list);list_del_init (&mnt->mnt_list); -if (mnt->mnt_ex_mountpoint.mnt) -path_put (&mnt->mnt_ex_mountpoint); +path_put (&mnt->mnt_ex_mountpoint);mntput (&mnt->mnt); } }
Signature · 1 line
2.2.1
PATCH 9/11 mnt: On an unmount propagate clearing of MNT_LOCKED
In reply to Eric W. BiedermanA prerequisite of calling umount_tree is that the point where the tree is mounted at is valid to unmount.
If we are propagating the effect of the unmount clear MNT_LOCKED in every instance where the same filesystem is mounted on the same mountpoint in the mount tree, as we know (by virtue of the fact that umount_tree was called) that it is safe to reveal what is at that mountpoint.
--- fs/namespace.c | 3 +++ fs/pnode.c | 20 ++++++++++++++++++++ fs/pnode.h | 1 + 3 files changed, 24 insertions(+)
fs/namespace.c +3 -0
@@ -1344,6 +1344,9 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)LIST_HEAD (tmp_list);struct mount *p; +if (how &UMOUNT_PROPAGATE ) +propagate_mount_unlock (mnt); +/* Gather the mounts to umount */ for (p = mnt; p; p =next_mnt (p, mnt)) { p->mnt.mnt_flags |=MNT_UMOUNT ;
fs/pnode.c +20 -0
@@ -362,6 +362,26 @@ int propagate_mount_busy(struct mount *mnt, int refcnt) }/* +* Clear MNT_LOCKED when it can be shown to be safe. +* +* mount_lock lock must be held for write +*/ +void propagate_mount_unlock (struct mount *mnt) +{ +struct mount *parent = mnt->mnt_parent; +struct mount *m, *child; + +BUG_ON (parent == mnt); + +for (m =propagation_next (parent, parent); m; + m =propagation_next (m, parent)) { + child =__lookup_mnt_last (&m->mnt, mnt->mnt_mountpoint); +if (child) + child->mnt.mnt_flags &= ~MNT_LOCKED ; + } +} + +/* * NOTE: unmounting 'mnt' naturally propagates to all other mounts its * parent propagates to. */
fs/pnode.h +1 -0
@@ -42,6 +42,7 @@ int propagate_mnt(struct mount *, struct mountpoint *, struct mount *, struct hlist_head *);int propagate_umount (struct list_head *);int propagate_mount_busy (struct mount *,int ); +void propagate_mount_unlock (struct mount *);void mnt_release_group_id (struct mount *);int get_dominating_id (struct mount *mnt,const struct path *root);unsigned int mnt_get_count (struct mount *mnt);
Signature · 1 line
2.2.1
PATCH 10/11 mnt: Don't propagate unmounts to locked mounts
In reply to Eric W. BiedermanIf the first mount in shared subtree is locked don't unmount the shared subtree.
This is ensured by walking through the mounts parents before children and marking a mount as unmountable if it is not locked or it is locked but it's parent is marked.
This allows recursive mount detach to propagate through a set of mounts when unmounting them would not reveal what is under any locked mount.
--- fs/pnode.c | 32 +++++++++++++++++++++++++++++--- fs/pnode.h | 1 + 2 files changed, 30 insertions(+), 3 deletions(-)
fs/pnode.c +29 -3
@@ -382,6 +382,26 @@ void propagate_mount_unlock(struct mount *mnt) }/* +* Mark all mounts that the MNT_LOCKED logic will allow to be unmounted. +*/ +static void mark_umount_candidates (struct mount *mnt) +{ +struct mount *parent = mnt->mnt_parent; +struct mount *m; + +BUG_ON (parent == mnt); + +for (m =propagation_next (parent, parent); m; + m =propagation_next (m, parent)) { +struct mount *child =__lookup_mnt_last (&m->mnt, + mnt->mnt_mountpoint); +if (child && (!IS_MNT_LOCKED (child) ||IS_MNT_MARKED (m))) { +SET_MNT_MARK (child); + } + } +} + +/* * NOTE: unmounting 'mnt' naturally propagates to all other mounts its * parent propagates to. */
@@ -398,10 +418,13 @@ static void __propagate_umount(struct mount *mnt)struct mount *child =__lookup_mnt_last (&m->mnt, mnt->mnt_mountpoint);/* -* umount the child only if the child has no -* other children +* umount the child only if the child has no children +* and the child is marked safe to unmount. */ -if (child &&list_empty (&child->mnt_mounts)) { +if (!child || !IS_MNT_MARKED (child)) +continue ; +CLEAR_MNT_MARK (child); +if (list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child); child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list);
@@ -420,6 +443,9 @@ int propagate_umount(struct list_head *list) {struct mount *mnt; +list_for_each_entry_reverse (mnt, list, mnt_list) +mark_umount_candidates (mnt); +list_for_each_entry (mnt, list, mnt_list)__propagate_umount (mnt);return 0 ;
fs/pnode.h +1 -0
@@ -19,6 +19,7 @@#define IS_MNT_MARKED (m) ((m)->mnt.mnt_flags & MNT_MARKED)#define SET_MNT_MARK (m) ((m)->mnt.mnt_flags |= MNT_MARKED)#define CLEAR_MNT_MARK (m) ((m)->mnt.mnt_flags &= ~MNT_MARKED) +#define IS_MNT_LOCKED (m) ((m)->mnt.mnt_flags & MNT_LOCKED)#define CL_EXPIRE 0x01#define CL_SLAVE 0x02
Signature · 1 line
2.2.1
PATCH 11/11 mnt: Honor MNT_LOCKED when detaching mounts
In reply to Eric W. BiedermanModify umount(MNT_DETACH) to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted. In doing this invert the reference count so that the parent holds a reference to the children instead of the children holding a reference to the parent.
Then in mntput_no_expire detach the children and in cleanup_mnt mntput the children and dput the dentry they were mounted on.
In __detach_mounts if there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, detach those mounts and schedule them to be mntput and their reference to the dentry to be put when it becomes safe to sleep.
--- fs/namespace.c | 46 ++++++++++++++++++++++++++++++++++++++++++---- fs/pnode.h | 2 ++ 2 files changed, 44 insertions(+), 4 deletions(-)
fs/namespace.c +42 -4
@@ -1017,6 +1017,17 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,return ERR_PTR (err); } +static void mntput_children (struct mount *mnt) +{ +struct mount *p, *tmp; + +list_for_each_entry_safe (p, tmp, &mnt->mnt_mounts, mnt_child) { +list_del_init (&p->mnt_child); +path_put (&p->mnt_ex_mountpoint); +mntput (&p->mnt); + } +} +static void cleanup_mnt (struct mount *mnt) {/*
@@ -1030,6 +1041,8 @@ static void cleanup_mnt(struct mount *mnt)* so mnt_get_writers() below is safe. */ WARN_ON (mnt_get_writers (mnt)); +if (unlikely (!list_empty (&mnt->mnt_mounts))) +mntput_children (mnt);if (unlikely (mnt->mnt_pins.first))mnt_pin_kill (mnt);fsnotify_vfsmount_delete (&mnt->mnt);
@@ -1080,6 +1093,15 @@ static void mntput_no_expire(struct mount *mnt)rcu_read_unlock ();list_del (&mnt->mnt_instance); + +if (unlikely (!list_empty (&mnt->mnt_mounts))) { +struct mount *p; +list_for_each_entry (p, &mnt->mnt_mounts, mnt_child) { +__detach_mnt (p, &p->mnt_ex_mountpoint); +/* No need to mntput mnt */ + p->mnt_ex_mountpoint.mnt =NULL ; + } + }unlock_mount_hash ();if (likely (!(mnt->mnt.mnt_flags &MNT_INTERNAL ))) {
@@ -1342,7 +1364,7 @@ enum umount_tree_flags {static void umount_tree (struct mount *mnt,enum umount_tree_flags how) {LIST_HEAD (tmp_list); -struct mount *p; +struct mount *tmp, *p;if (how &UMOUNT_PROPAGATE )propagate_mount_unlock (mnt);
@@ -1362,7 +1384,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list); -list_for_each_entry (p, &tmp_list, mnt_list) { +list_for_each_entry_safe (p, tmp, &tmp_list, mnt_list) {list_del_init (&p->mnt_expire);__touch_mnt_namespace (p->mnt_ns); p->mnt_ns =NULL ;
@@ -1370,7 +1392,14 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ;if (mnt_has_parent (p)) {mnt_add_count (p->mnt_parent,-1 ); -__detach_mnt (p, &p->mnt_ex_mountpoint); +if (IS_MNT_LOCKED_AND_LAZY (p)) { +/* Don't mntput p in namespace_unlock */ +list_del_init (&p->mnt_list); +/* Don't forget about p */ +list_add_tail (&p->mnt_child, &p->mnt_parent->mnt_mounts); + }else { +__detach_mnt (p, &p->mnt_ex_mountpoint); + } }change_mnt_propagation (p,MS_PRIVATE ); }
@@ -1496,7 +1525,16 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,0 ); +if (mnt->mnt.mnt_flags &MNT_UMOUNT ) { +struct mount *p, *tmp; +list_for_each_entry_safe (p, tmp, &mnt->mnt_mounts, mnt_child) { +detach_mnt (p, &p->mnt_ex_mountpoint); +/* p->mnt_parent has already been mntput */ + p->mnt_ex_mountpoint.mnt =NULL ; +list_add_tail (&p->mnt_list, &unmounted); + } + } +else umount_tree (mnt,0 ); }unlock_mount_hash ();put_mountpoint (mp);
fs/pnode.h +2 -0
@@ -20,6 +20,8 @@#define SET_MNT_MARK (m) ((m)->mnt.mnt_flags |= MNT_MARKED)#define CLEAR_MNT_MARK (m) ((m)->mnt.mnt_flags &= ~MNT_MARKED)#define IS_MNT_LOCKED (m) ((m)->mnt.mnt_flags & MNT_LOCKED) +#define IS_MNT_LOCKED_AND_LAZY (m) \ + (((m)->mnt.mnt_flags & (MNT_LOCKED|MNT_SYNC_UMOUNT)) == MNT_LOCKED)#define CL_EXPIRE 0x01#define CL_SLAVE 0x02
Signature · 1 line
2.2.1
A message that is not in the archive (sent off-list or not yet received): 1420490787-14387-11-git-send-email-ebiederm-aS9lmoZGLiVWk0Htik3J/[email protected]
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 6 lines · Eric W. Biederman
Modify
umount(MNT_DETACH)to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted. In doing this invert the reference count so that the parent holds a reference to the children instead of the children holding a reference to the parent.
Quoted 7 lines
Then in
mntput_no_expiredetach the children and incleanup_mntmntput the children and dput the dentry they were mounted on.In
__detach_mountsif there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, detach those mounts and schedule them to be mntput and their reference to the dentry to be put when it becomes safe to sleep.
Explicit description of your new refcounting rules, please. What's more, how do those non-pinning children interact with e.g. copy_tree()?
Al Viro
maintainerRe: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to Eric W. BiedermanQuoted 6 lines · Eric W. Biederman
Modify
umount(MNT_DETACH)to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted. In doing this invert the reference count so that the parent holds a reference to the children instead of the children holding a reference to the parent.
Quoted 7 lines
Then in
mntput_no_expiredetach the children and incleanup_mntmntput the children and dput the dentry they were mounted on.In
__detach_mountsif there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, detach those mounts and schedule them to be mntput and their reference to the dentry to be put when it becomes safe to sleep.
Explicit description of your new refcounting rules, please. What's more, how do those non-pinning children interact with e.g. copy_tree()?
A message that is not in the archive (sent off-list or not yet received): 20150107184334.GZ22149-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 10 lines · Al Viro
Quoted 9 lines · Eric W. Biederman
Modify
umount(MNT_DETACH)to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted. In doing this invert the reference count so that the parent holds a reference to the children instead of the children holding a reference to the parent.Then in
mntput_no_expiredetach the children and incleanup_mntmntput the children and dput the dentry they were mounted on.
Quoted 6 lines
Quoted 4 lines
In
__detach_mountsif there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, detach those mounts and schedule them to be mntput and their reference to the dentry to be put when it becomes safe to sleep.Explicit description of your new refcounting rules, please.
I really hate those path_put(&p->mnt_ex_mountpoint) in the resulting code... Can you ever get to your mntput_children() with non-NULL ->mnt_ex_mountpoint.mnt in one of the survivors? Looks like it shouldn't be possible at all...
What's to prevent such a survivor (not contributing to the refcount of parent, to be dropped when parent gets killed) in the child list of a parent that is still normally mounted? And what happens if it's hit again, in addition to "what does /proc/mounts of the namespace the parent's in look like"?
A message that is not in the archive (sent off-list or not yet received): 20150107192807.GA22149-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 18 lines · Al Viro
Quoted 10 lines · Al Viro
Quoted 9 lines · Eric W. Biederman
Modify
umount(MNT_DETACH)to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted. In doing this invert the reference count so that the parent holds a reference to the children instead of the children holding a reference to the parent.Then in
mntput_no_expiredetach the children and incleanup_mntmntput the children and dput the dentry they were mounted on.Quoted 6 lines
Quoted 4 lines
In
__detach_mountsif there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, detach those mounts and schedule them to be mntput and their reference to the dentry to be put when it becomes safe to sleep.Explicit description of your new refcounting rules, please.
Quoted 7 lines
I really hate those
path_put(&p->mnt_ex_mountpoint)in the resulting code... Can you ever get to yourmntput_children()with non-NULL ->mnt_ex_mountpoint.mnt in one of the survivors? Looks like it shouldn't be possible at all...What's to prevent such a survivor (not contributing to the refcount of parent, to be dropped when parent gets killed) in the child list of a parent that is still normally mounted?
umount_tree.
In the base case we know the parent is unmounted because we were called on the parent.
In the propagation case the code goes through one pass to identify trees where the parent -> child (MNT_LOCKED) state allows the subtree to be unmounted and we perform the normal are all of the children gone test.
That is what the entire MNT_MARK business is about.
Quoted 1 line
And what happens if it's hit again,
If I understand the question such a mount won't be hit again because the case you describe can't happen.
Quoted 2 lines
in addition to "what does
/proc/mountsof the namespace the parent's in look like"?
These mounts are unmounted so don't show up in /proc/mounts.
If the umount propagates somewhere the parents can't be detached from the propagation is discarded. Just like we discard propagation when the are additional children.
Eric
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to a message not in the archiveQuoted 10 lines · Al Viro
Quoted 9 lines · Eric W. Biederman
Modify
umount(MNT_DETACH)to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted. In doing this invert the reference count so that the parent holds a reference to the children instead of the children holding a reference to the parent.Then in
mntput_no_expiredetach the children and incleanup_mntmntput the children and dput the dentry they were mounted on.
Quoted 7 lines
Quoted 4 lines
In
__detach_mountsif there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, detach those mounts and schedule them to be mntput and their reference to the dentry to be put when it becomes safe to sleep.Explicit description of your new refcounting rules, please. What's more, how do those non-pinning children interact with e.g.
copy_tree()?
The parents hold a reference on the children, and the parent keeps track of it's children through the mnt_mounts list. The parents reference to a child is held until the final mntput of the parent.
As for how those mounts interact with copy_tree, they aren't designed to. I had overlooked that collect_mounts is weird and if the proper race exists can be called on an unmounted tree. So I expect the interaction with copy_tree is buggy. I will look at that and see what I can do to fix that (it shouldn't be hard). I expect I can just return an error if the mount has been unmounted.
Eric
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to Al ViroQuoted 10 lines · Al Viro
Quoted 9 lines · Eric W. Biederman
Modify
umount(MNT_DETACH)to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted. In doing this invert the reference count so that the parent holds a reference to the children instead of the children holding a reference to the parent.Then in
mntput_no_expiredetach the children and incleanup_mntmntput the children and dput the dentry they were mounted on.
Quoted 7 lines
Quoted 4 lines
In
__detach_mountsif there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, detach those mounts and schedule them to be mntput and their reference to the dentry to be put when it becomes safe to sleep.Explicit description of your new refcounting rules, please. What's more, how do those non-pinning children interact with e.g.
copy_tree()?
The parents hold a reference on the children, and the parent keeps track of it's children through the mnt_mounts list. The parents reference to a child is held until the final mntput of the parent.
As for how those mounts interact with copy_tree, they aren't designed to. I had overlooked that collect_mounts is weird and if the proper race exists can be called on an unmounted tree. So I expect the interaction with copy_tree is buggy. I will look at that and see what I can do to fix that (it shouldn't be hard). I expect I can just return an error if the mount has been unmounted.
Eric
A message that is not in the archive (sent off-list or not yet received): [email protected]
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 6 lines · Eric W. Biederman
Quoted 2 lines
Explicit description of your new refcounting rules, please. What's more, how do those non-pinning children interact with e.g.
copy_tree()?The parents hold a reference on the children, and the parent keeps track of it's children through the
mnt_mountslist. The parents reference to a child is held until the final mntput of the parent.
This is obvious crap. It doesn't match the resulting code, not to mention making no sense whatsoever.
If you (with your resulting tree) mount ten filesystems on /tmp/[0-9], you will have refcount of vfsmount on /tmp incremented by 10. Moreover, you really don't want /tmp *not* to be busy after having done that.
The current rules are
* take the number of external references
* add 1 if it's reachable from hash table
with mnt->mnt_parent being a counting reference unless it points to mnt itself.
One extra wart is namespace_unlock() treatment of ex-mountpoints - we have decremented their refcounts early (during umount_tree()) to avoid false positives on "is it busy" checks, but once we are done with calculating the set of victims we want proper ordering on the fs shutdown vs dput() of mountpoints, so namespace_unlock() starts with unrolling these decrements and then replays them in proper order.
You have modified that, but your description above doesn't match what you are doing. Moreover, in absense of those locked-and-lazy you *still* have the same rules as before. Full description of the new rules, please.
A message that is not in the archive (sent off-list or not yet received): 20150107205239.GB22149-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 9 lines · Al Viro
Quoted 6 lines · Eric W. Biederman
Quoted 2 lines
Explicit description of your new refcounting rules, please. What's more, how do those non-pinning children interact with e.g.
copy_tree()?The parents hold a reference on the children, and the parent keeps track of it's children through the
mnt_mountslist. The parents reference to a child is held until the final mntput of the parent.
Quoted 6 lines
This is obvious crap. It doesn't match the resulting code, not to mention making no sense whatsoever.
If you (with your resulting tree) mount ten filesystems on /tmp/[0-9], you will have refcount of vfsmount on /tmp incremented by 10. Moreover, you really don't want /tmp *not* to be busy after having done that.
You are complaining that I did not describe the part of the rules that are not new?
Quoted 12 lines
The current rules are
* take the number of external references
* add 1 if it's reachable from hash table
withmnt->mnt_parentbeing a counting reference unless it points to mnt itself.
One extra wart isnamespace_unlock()treatment of ex-mountpoints - we have decremented their refcounts early (duringumount_tree()) to avoid false positives on "is it busy" checks, but once we are done with calculating the set of victims we want proper ordering on the fs shutdown vsdput()of mountpoints, sonamespace_unlock()starts with unrolling these decrements and then replays them in proper order.
Quoted 3 lines
You have modified that, but your description above doesn't match what you are doing. Moreover, in absense of those locked-and-lazy you *still* have the same rules as before. Full description of the new rules, please.
Al I think you are big pig headed to be pig headed, you don't see to be trying to understand so I don't know if this conversation will be productive. I did describe how the rules change in the specific case that they change which is what I though you were asking for.
I disagree with how you have described the current rules.
Currently the reference count on a struct mount is:
- incremented by 1 for every child listed in mnt_mounts.
- incremented by 1 for every oridinary user that does not know the intimate details of how mounts are implemented.
- incremented by 1 if the mount is on the mnt_list and is mounted.
- Mounts that have been unmounted do not mounted do not have children.
What I have introduced is state where a mount that is unmounted has children, and the connection between the mount and those children remain in the mount hash table.
In this new case it becomes possible for a mount with children to reach the final mntput.
In this new case an unmounted mount with children that reaches mntput will hold a reference to each of it's children until it reaches mntput, and there the reference will be dropped.
To get into that state in umount_tree the code drops the reference to the parent from children as normal. The code repairs the child list which was torn down early. The code takes mnt_list and sets mnt_ns = NULL so the mount is clearly unmounted. The reference count from being mounted is given to the mount parent.
At the parents final mntput the children are walked through and removed from the mount hash table, and in cleanup_mnt where we don't have an ultra deep stack and are not holding locks, the dentry is dput and and the child mount is mntput.
For fs/namespace.c not a particularly complicated scenario, and not particulary weird. And a scenario that matches the description a parent holds a reference to the child instead of the other way around.
Now that I have fleshed out my description if you continue to think it is obvious crap and doesn't match the resulting code, your analysis is bollocks.
Eric
A message that is not in the archive (sent off-list or not yet received): [email protected]
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 1 line · Eric W. Biederman
I disagree with how you have described the current rules.
In which situation that description does not match what we do?
Quoted 3 lines
Currently the reference count on a struct mount is:
- incremented by 1 for every child listed in
mnt_mounts.
Yes - their ->mnt_parent does it.
Quoted 2 lines
- incremented by 1 for every oridinary user that does not know the intimate details of how mounts are implemented.
That'd be references other than ->mnt_parent.
Quoted 1 line
- incremented by 1 if the mount is on the
mnt_listand is mounted.
== reachable from hashtable, except that you include the reference from namespace to its root mount into that; in my description that goes as external reference.
Quoted 1 line
- Mounts that have been unmounted do not mounted do not have children.
Meaning?
Quoted 18 lines
What I have introduced is state where a mount that is unmounted has children, and the connection between the mount and those children remain in the mount hash table.
In this new case it becomes possible for a mount with children to reach the final mntput.
In this new case an unmounted mount with children that reaches mntput will hold a reference to each of it's children until it reaches mntput, and there the reference will be dropped.
To get into that state in
umount_treethe code drops the reference to the parent from children as normal. The code repairs the child list which was torn down early. The code takesmnt_listand setsmnt_ns= NULL so the mount is clearly unmounted. The reference count from being mounted is given to the mount parent.
Quoted 4 lines
At the parents final mntput the children are walked through and removed from the mount hash table, and in
cleanup_mntwhere we don't have an ultra deep stack and are not holding locks, the dentry is dput and and the child mount is mntput.
You are describing what your code does. Thank you, but I *can* read C just as easy as your text (easier, TBH). What I'm asking for is data structures invariants. As in, "outside of such and such locks, the following predicates are true"...
If I understand what you are saying, you have
* subset of mounts (ones that had MNT_LOCKED and went throughMNT_DETACH umount).
* mounts in that subset are
* hashed
* present in their parents' lists of children
* do _NOT_ contribute to parents' refcounts
* do _NOT_ participate in event propagation
* removed from the lists of children and dropped upon
parent's final mntput (dropping being done on shallow stack, just before the fs shutdown of parent).
I can buy that, *IF* one could add
* are guaranteed to be unreachable from any namespace root.
to the above. And yes, your logics around marking them is probably enough for that. However, if we go that way, I don't understand why do we need to involve MNT_LOCKED.
Look: your new rules for that subset are actually not that different from the old ones - they *are* hashed, so by the old rules we'd added 1 to refcount anyway. The only period when that is not true is from __detach_mnt() in mntput_no_expire() to cleanup_mnt(). The real difference, AFAICS, is that their ->mnt_parent does not contribute to refcount of its targets.
If the root of such a detached tree gets the last reference dropped, it gets killed as usual *and* its children become roots of detached trees. That is protected by mount hash lock (which prevents somebody inside a subtree from traversing .. and grabbing a reference to root after we'd started to detach stuff from it).
Fine, that makes sense, but IMO it would make a lot of sense to have a variant of MNT_DETACH that would do that for *all* mounts, locked or not. I'd be tempted to make MNT_DETACH itself do that, except that the current behaviour (dropping eagerly) is useful in situations like "we have a stuck filesystem and a bunch of stuff under it; let's do umount -l on that shit, hopefully the non-busy filesystems mounted down there will get a clean shutdown out of that".
I seriously dislike your use of path_put() on mnt_ex_mountpoint - it's actively misleading. We bloody better _not_ have non-NULL ->mnt_ex_mountpoint.mnt in your cleanup_mnt(), for obvious reasons. So it's just dput() + do something random if non-NULL mnt found.
Not sure if I like the use of mnt_child between mntput_no_expire() and cleanup_mnt() - it's probably safe, but the fewer lists we modify outside of mount hash lock, the better; hell knows, I'll need to stare at that code a bit more. FWIW, AFAICS the refcount rules with your variant are
* external references countribute
* mnt_parent contributes unless it points to ourselves *or* mnt_ns is
NULL
* reachable from mount hash => add 1
* in addition to the wart around namespace_unlock(), we have a similar
wart between mntput_no_expire() and cleanup_mnt(), only there we thread the suckers on mnt_child instead of mnt_list and use slightly different logics to prevent shutdown of parent fs before the dput(mountpoint).
I really hate synchronize_rcu() in namespace_unlock() ;-/ Not your fault, but it's a PITA every time one does analysis of all that code... It's about legitimize_mnt() vs. umount() - we want to make sure that any transient bumps of refcount from attempts to legitimize a lazy reference to vfsmount getting killed by umount() will be undone *before* we get to mntput_no_expire() in namespace_unlock(). It's only an issue for sync umounts, so we are fine here, but it definitely needs commenting upon - some vfsmounts *can* escape it with that approach, but they are not going to be marked sync-umount, so we don't need to worry about them...
Al Viro
maintainerRe: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to Eric W. BiedermanQuoted 1 line · Eric W. Biederman
I disagree with how you have described the current rules.
In which situation that description does not match what we do?
Quoted 3 lines
Currently the reference count on a struct mount is:
- incremented by 1 for every child listed in
mnt_mounts.
Yes - their ->mnt_parent does it.
Quoted 2 lines
- incremented by 1 for every oridinary user that does not know the intimate details of how mounts are implemented.
That'd be references other than ->mnt_parent.
Quoted 1 line
- incremented by 1 if the mount is on the
mnt_listand is mounted.
== reachable from hashtable, except that you include the reference from namespace to its root mount into that; in my description that goes as external reference.
Quoted 1 line
- Mounts that have been unmounted do not mounted do not have children.
Meaning?
Quoted 18 lines
What I have introduced is state where a mount that is unmounted has children, and the connection between the mount and those children remain in the mount hash table.
In this new case it becomes possible for a mount with children to reach the final mntput.
In this new case an unmounted mount with children that reaches mntput will hold a reference to each of it's children until it reaches mntput, and there the reference will be dropped.
To get into that state in
umount_treethe code drops the reference to the parent from children as normal. The code repairs the child list which was torn down early. The code takesmnt_listand setsmnt_ns= NULL so the mount is clearly unmounted. The reference count from being mounted is given to the mount parent.
Quoted 4 lines
At the parents final mntput the children are walked through and removed from the mount hash table, and in
cleanup_mntwhere we don't have an ultra deep stack and are not holding locks, the dentry is dput and and the child mount is mntput.
You are describing what your code does. Thank you, but I *can* read C just as easy as your text (easier, TBH). What I'm asking for is data structures invariants. As in, "outside of such and such locks, the following predicates are true"...
If I understand what you are saying, you have
* subset of mounts (ones that had MNT_LOCKED and went throughMNT_DETACH umount).
* mounts in that subset are
* hashed
* present in their parents' lists of children
* do _NOT_ contribute to parents' refcounts
* do _NOT_ participate in event propagation
* removed from the lists of children and dropped upon
parent's final mntput (dropping being done on shallow stack, just before the fs shutdown of parent).
I can buy that, *IF* one could add
* are guaranteed to be unreachable from any namespace root.
to the above. And yes, your logics around marking them is probably enough for that. However, if we go that way, I don't understand why do we need to involve MNT_LOCKED.
Look: your new rules for that subset are actually not that different from the old ones - they *are* hashed, so by the old rules we'd added 1 to refcount anyway. The only period when that is not true is from __detach_mnt() in mntput_no_expire() to cleanup_mnt(). The real difference, AFAICS, is that their ->mnt_parent does not contribute to refcount of its targets.
If the root of such a detached tree gets the last reference dropped, it gets killed as usual *and* its children become roots of detached trees. That is protected by mount hash lock (which prevents somebody inside a subtree from traversing .. and grabbing a reference to root after we'd started to detach stuff from it).
Fine, that makes sense, but IMO it would make a lot of sense to have a variant of MNT_DETACH that would do that for *all* mounts, locked or not. I'd be tempted to make MNT_DETACH itself do that, except that the current behaviour (dropping eagerly) is useful in situations like "we have a stuck filesystem and a bunch of stuff under it; let's do umount -l on that shit, hopefully the non-busy filesystems mounted down there will get a clean shutdown out of that".
I seriously dislike your use of path_put() on mnt_ex_mountpoint - it's actively misleading. We bloody better _not_ have non-NULL ->mnt_ex_mountpoint.mnt in your cleanup_mnt(), for obvious reasons. So it's just dput() + do something random if non-NULL mnt found.
Not sure if I like the use of mnt_child between mntput_no_expire() and cleanup_mnt() - it's probably safe, but the fewer lists we modify outside of mount hash lock, the better; hell knows, I'll need to stare at that code a bit more. FWIW, AFAICS the refcount rules with your variant are
* external references countribute
* mnt_parent contributes unless it points to ourselves *or* mnt_ns is
NULL
* reachable from mount hash => add 1
* in addition to the wart around namespace_unlock(), we have a similar
wart between mntput_no_expire() and cleanup_mnt(), only there we thread the suckers on mnt_child instead of mnt_list and use slightly different logics to prevent shutdown of parent fs before the dput(mountpoint).
I really hate synchronize_rcu() in namespace_unlock() ;-/ Not your fault, but it's a PITA every time one does analysis of all that code... It's about legitimize_mnt() vs. umount() - we want to make sure that any transient bumps of refcount from attempts to legitimize a lazy reference to vfsmount getting killed by umount() will be undone *before* we get to mntput_no_expire() in namespace_unlock(). It's only an issue for sync umounts, so we are fine here, but it definitely needs commenting upon - some vfsmounts *can* escape it with that approach, but they are not going to be marked sync-umount, so we don't need to worry about them...
A message that is not in the archive (sent off-list or not yet received): 20150108002227.GC22149-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 4 lines · Al Viro
Not sure if I like the use of
mnt_childbetweenmntput_no_expire()andcleanup_mnt()- it's probably safe, but the fewer lists we modify outside of mount hash lock, the better; hell knows, I'll need to stare at that code a bit more. FWIW, AFAICS the refcount rules with your variant are
Quoted 8 lines
* external references countribute
*mnt_parentcontributes unless it points to ourselves *or*mnt_nsis
NULL
* reachable from mount hash => add 1
* in addition to the wart aroundnamespace_unlock(), we have a similar
wart betweenmntput_no_expire()andcleanup_mnt(), only there we thread the suckers onmnt_childinstead ofmnt_listand use slightly different logics to prevent shutdown of parent fs before the dput(mountpoint).
BTW, why do you use detach_mnt() in __detach_mounts()? Unless I'm missing something really subtle, __detach_mnt() is the right thing there...
And while we are at it, all other callers of detach_mnt() are followed by attach_mnt() within the same namespace *and* all attach_mnt() follow detach_mnt(). So why bother with mnt_list in either? Or, put it another way, why have __detach_mnt() separate from detach_mnt()?
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to a message not in the archiveQuoted 8 lines · Al Viro
* external references countribute
*mnt_parentcontributes unless it points to ourselves *or*mnt_nsis
NULL
* reachable from mount hash => add 1
* in addition to the wart aroundnamespace_unlock(), we have a similar
wart betweenmntput_no_expire()andcleanup_mnt(), only there we thread the suckers onmnt_childinstead ofmnt_listand use slightly different logics to prevent shutdown of parent fs before the dput(mountpoint).
BTW, there's something I wanted to do for a while - these games with delaying dput(mountpoint) look a whole lot like a job for mnt_pin_kill().
Look: even now we have (at the exit from umount_tree()) a bunch of remnant mountpoints associated with ex-parents. And we want to have them taken out and shot before said ex-parents shut down. The tricky part here is ordering - at the beginning of namespace_unlock() we have a bunch of mounts about to be dropped; quite a few might be keeping references to the ex-mountpoints. We want mntput() on all of those *and* we want dput() on said ex-mountpoints. We'd already dropped the references to ex-parents (in umount_tree()). The trouble being, some of those ex-mountpoints might be on other victim mounts and we really do NOT want to drop such a victim until all dput() within it had been done.
That's why we play those games with bumping ex-parent mount counts first, so that dput() is immediately followed with matching mntput().
But we don't have to do it that way. Alternative, and IMO a cleaner one, would be to embed struct fs_pin into struct mount and, in umount_tree() simply add that fs_pin->m_list into the ->mnt_pins of parent and ->s_list into a global list (replacement of 'unmounted'). With ->kill() callback dropping references to dentry of mountpoint *and* containing mount. Then we don't need to care about the ordering at all - we just go through the global list of fs_pin and do ->kill() on all of them, in whatever order they happen to be. If mount gets dropped while there still are ex-mountpoints on it waiting to be dropped - fine, we'll just drop them there and then (from mnt_kill_pins()). No games with refcounts that way, no transiently busy mounts, which we do have right now.
And that scheme actually covers your "delayed detaching" just fine - if we want detaching to be delayed until the parent really buys it, all we need to do is to skip dropping the child from mnt_mounts of parent and putting fs_pin of child on the global list. And detach_mount() on all remaining children at mntput_no_expire() time, as you do, only with removal from child list.
No changes needed in cleanup_mnt() that way - remaining fs_pin of children will play out, yielding the effect you want.
The problem with that approach (and the reason why it hadn't been done yet) is that we'll need some careful tweaking of locking in fs/fs_pin.c. Back in 3.17 I really wanted to get the kernel/acct.c crap out of the way, so that umount-on-rmdir stuff could be done safely. So the minimal solution went in, with plans for using it to avoid the headache with ordering left for later. I had completely missed the possibility to do less eager dissolving of detached trees that way (and it was a long-standing wishlist thing) back then, or I probably would've bitten the bullet and gone for it.
What you are proposing is
a) easily expressed with that scheme (behaviour is almost identical
to what your series does, except that we'd empty mnt_child right in mntput_no_expire(), rather than keeping it until cleanup_mnt()) and
b) easily generalized to MNT_DETACH with lazy dissolving; in fact,
the only difference would be to treat *all* submounts as "don't put on global list, don't remove from child list", not just the MNT_LOCKED ones.
I'll play around with that today and tomorrow; hopefully, I'll have a postable variant by the weekend...
A message that is not in the archive (sent off-list or not yet received): 20150108223212.GF22149-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 9 lines · Al Viro
a) easily expressed with that scheme (behaviour is almost identical
to what your series does, except that we'd emptymnt_childright inmntput_no_expire(), rather than keeping it untilcleanup_mnt()) and
b) easily generalized toMNT_DETACHwith lazy dissolving; in fact,
the only difference would be to treat *all* submounts as "don't put on global list, don't remove from child list", not just theMNT_LOCKEDones.I'll play around with that today and tomorrow; hopefully, I'll have a postable variant by the weekend...
Hmm... Linus, what do you think of the following?
struct foo {
int done;
wait_queue_head_t wait;
...
};void kill_foo(struct foo *p)
{
wait_queue_t wait;
wait.flags = WQ_FLAG_EXCLUSIVE;
wait.private = current;
wait.func = autoremove_wake_function;
spin_lock_irq(&p->wait.lock);
if (likely(!p->done)) {
p->done = -1;
spin_unlock_irq(&p->wait.lock);
rcu_read_unlock();
/* do cleanup */
...
/* remove references to *p */
...
spin_lock_irq(&p->wait.lock);
p->done = 1;
wake_up_locked(&p->wait);
spin_unlock_irq(&p->wait.lock);
/* RCU-schedule freeing of p */
...
return;
}
if (p->done > 0) {
spin_unlock_irq(&p->wait.lock);
rcu_read_unlock();
return;
}
__add_wait_queue_tail(&p->wait, &wait);
while (1) {
set_current_state(TASK_UNINITERRUPTIBLE);
spin_unlock_irq(&p->wait.lock);
rcu_read_unlock();
schedule();
rcu_read_lock();
if (likely(list_empty(&wait.task_list)))
break;
/* OK, we know p couldn't have been freed yet */
spin_lock_irq(&p->wait.lock);
if (p->done > 0) {
spin_unlock_irq(&p->wait.lock);
break;
}
}
rcu_read_unlock();
}AFAICS, that ought to be safe - the first caller makes sure that everybody else will wait for it to finish, everybody else (ones who come via references before the first one has removed them) will be waiting for the first one to do wakeup *and* will take care not to touch the victim unless they knows it's still there.
Do you see any problems with the above? I wonder if I ended up open-coding something already existing in there...
Al Viro
maintainerRe: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to Al ViroQuoted 9 lines · Al Viro
a) easily expressed with that scheme (behaviour is almost identical
to what your series does, except that we'd emptymnt_childright inmntput_no_expire(), rather than keeping it untilcleanup_mnt()) and
b) easily generalized toMNT_DETACHwith lazy dissolving; in fact,
the only difference would be to treat *all* submounts as "don't put on global list, don't remove from child list", not just theMNT_LOCKEDones.I'll play around with that today and tomorrow; hopefully, I'll have a postable variant by the weekend...
Hmm... Linus, what do you think of the following?
struct foo {
int done;
wait_queue_head_t wait;
...
};void kill_foo(struct foo *p)
{
wait_queue_t wait;
wait.flags = WQ_FLAG_EXCLUSIVE;
wait.private = current;
wait.func = autoremove_wake_function;
spin_lock_irq(&p->wait.lock);
if (likely(!p->done)) {
p->done = -1;
spin_unlock_irq(&p->wait.lock);
rcu_read_unlock();
/* do cleanup */
...
/* remove references to *p */
...
spin_lock_irq(&p->wait.lock);
p->done = 1;
wake_up_locked(&p->wait);
spin_unlock_irq(&p->wait.lock);
/* RCU-schedule freeing of p */
...
return;
}
if (p->done > 0) {
spin_unlock_irq(&p->wait.lock);
rcu_read_unlock();
return;
}
__add_wait_queue_tail(&p->wait, &wait);
while (1) {
set_current_state(TASK_UNINITERRUPTIBLE);
spin_unlock_irq(&p->wait.lock);
rcu_read_unlock();
schedule();
rcu_read_lock();
if (likely(list_empty(&wait.task_list)))
break;
/* OK, we know p couldn't have been freed yet */
spin_lock_irq(&p->wait.lock);
if (p->done > 0) {
spin_unlock_irq(&p->wait.lock);
break;
}
}
rcu_read_unlock();
}AFAICS, that ought to be safe - the first caller makes sure that everybody else will wait for it to finish, everybody else (ones who come via references before the first one has removed them) will be waiting for the first one to do wakeup *and* will take care not to touch the victim unless they knows it's still there.
Do you see any problems with the above? I wonder if I ended up open-coding something already existing in there...
A message that is not in the archive (sent off-list or not yet received): 20150109203126.GI22149-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 7 lines · Al Viro
Quoted 6 lines · Al Viro
a) easily expressed with that scheme (behaviour is almost identical
to what your series does, except that we'd emptymnt_childright inmntput_no_expire(), rather than keeping it untilcleanup_mnt()) and
b) easily generalized toMNT_DETACHwith lazy dissolving; in fact,
the only difference would be to treat *all* submounts as "don't put on global list, don't remove from child list", not just theMNT_LOCKEDones.
I have no problem with treating all mounts whose parents are also being unmounted as "don't put on global list, don't remove from child list"
Replacing the mnt_ex_mountpoint struct path with a fs_pin seems reasonable.
We need to be careful with mounts that have parents that are not being unmounted, with respect to the mount hash table but that just looks like a detail in the overall scheme of things.
And if delaying the disolution of mounts in the mount detach case is a long held wishlist item I am all for going there. I have a patch I was playing with that did that, I just didn't include it because that is a bug fix kind of thing.
Quoted 11 lines
Quoted 2 lines
I'll play around with that today and tomorrow; hopefully, I'll have a postable variant by the weekend...
Hmm... Linus, what do you think of the following?
struct foo { int done; wait_queue_head_t wait; ... };
Where does the rcu_read_lock() happen?
I assume this is kill from fs_pin.kill?
Quoted 47 lines
void
kill_foo(struct foo *p){ wait_queue_t wait; wait.flags = WQ_FLAG_EXCLUSIVE; wait.private = current; wait.func = autoremove_wake_function; spin_lock_irq(&p->wait.lock); if (likely(!p->done)) { p->done = -1; spin_unlock_irq(&p->wait.lock); rcu_read_unlock(); /* do cleanup */ ... /* remove references to *p */ ... spin_lock_irq(&p->wait.lock); p->done = 1; wake_up_locked(&p->wait); spin_unlock_irq(&p->wait.lock); /* RCU-schedule freeing of p */ ... return; } if (p->done > 0) { spin_unlock_irq(&p->wait.lock); rcu_read_unlock(); return; } __add_wait_queue_tail(&p->wait, &wait); while (1) { set_current_state(TASK_UNINITERRUPTIBLE); spin_unlock_irq(&p->wait.lock); rcu_read_unlock(); schedule(); rcu_read_lock(); if (likely(list_empty(&wait.task_list))) break; /* OK, we know p couldn't have been freed yet */ spin_lock_irq(&p->wait.lock); if (p->done > 0) { spin_unlock_irq(&p->wait.lock); break; } } rcu_read_unlock(); }
Quoted 8 lines
AFAICS, that ought to be safe - the first caller makes sure that everybody else will wait for it to finish, everybody else (ones who come via references before the first one has removed them) will be waiting for the first one to do wakeup *and* will take care not to touch the victim unless they knows it's still there.
Do you see any problems with the above? I wonder if I ended up open-coding something already existing in there...
Your struct foo looks an awful lot like struct completion at first glance.
Eric
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to a message not in the archiveQuoted 8 lines · Al Viro
Quoted 7 lines · Al Viro
a) easily expressed with that scheme (behaviour is almost identical
to what your series does, except that we'd emptymnt_childright inmntput_no_expire(), rather than keeping it untilcleanup_mnt()) and
b) easily generalized toMNT_DETACHwith lazy dissolving; in fact,
the only difference would be to treat *all* submounts as "don't put on global list, don't remove from child list", not just theMNT_LOCKEDones.
Quoted 4 lines
Quoted 2 lines
I'll play around with that today and tomorrow; hopefully, I'll have a postable variant by the weekend...
Hmm... Linus, what do you think of the following?
You are just about defining a mutex here. The one difference is the single use property that makes this a good cleanup primitive.
WQ_FLAG_EXCLUSIVE appears wrong. We want to remove all of the waiters from the list and wake the all up. Otherwise if you have 3 different kernel threads all trying to clean up the same structure at the same time (say remount, mntput and acct_off) one of them will be left waiting forever.
I don't believe rcu anything in this function itself buys you anything, but structuring this primitive so that it can be called from an rcu list traversal seems interesting.
It might be simpler to have a single fs_pin_mutex and that guards all of the paths to a fs_pin so we don't have to be this clever. But I expect there are weird lock ordering issues that being this smart allows us to avoid. Certainly a primitive that allows us to clean things up without any locks held is easier to maintain.
Quoted 61 lines
struct foo { int done; wait_queue_head_t wait; ... };void
kill_foo(struct foo *p){ wait_queue_t wait; wait.flags = WQ_FLAG_EXCLUSIVE; wait.private = current; wait.func = autoremove_wake_function; spin_lock_irq(&p->wait.lock); if (likely(!p->done)) { p->done = -1; spin_unlock_irq(&p->wait.lock); rcu_read_unlock(); /* do cleanup */ ... /* remove references to *p */ ... spin_lock_irq(&p->wait.lock); p->done = 1; wake_up_locked(&p->wait); spin_unlock_irq(&p->wait.lock); /* RCU-schedule freeing of p */ ... return; } if (p->done > 0) { spin_unlock_irq(&p->wait.lock); rcu_read_unlock(); return; } __add_wait_queue_tail(&p->wait, &wait); while (1) { set_current_state(TASK_UNINITERRUPTIBLE); spin_unlock_irq(&p->wait.lock); rcu_read_unlock(); schedule(); rcu_read_lock(); if (likely(list_empty(&wait.task_list))) break; /* OK, we know p couldn't have been freed yet */ spin_lock_irq(&p->wait.lock); if (p->done > 0) { spin_unlock_irq(&p->wait.lock); break; } } rcu_read_unlock(); }AFAICS, that ought to be safe - the first caller makes sure that everybody else will wait for it to finish, everybody else (ones who come via references before the first one has removed them) will be waiting for the first one to do wakeup *and* will take care not to touch the victim unless they knows it's still there.
Do you see any problems with the above? I wonder if I ended up open-coding something already existing in there...
A message that is not in the archive (sent off-list or not yet received): [email protected]
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 3 lines · Eric W. Biederman
I don't believe rcu anything in this function itself buys you anything, but structuring this primitive so that it can be called from an rcu list traversal seems interesting.
???
Without RCU, what would prevent it being freed right under us?
The whole point is to avoid pinning it down - as it is, we can have several processes call ->kill() on the same object. The first one would end up doing cleanup, the rest would wait *without* *affecting* *fs_pin* *lifetime*.
Note that I'm using autoremove there for wait.func(), then in the wait loop I check (without locks) wait.task_list being empty. It is racy; deliberately so. All I really care about in there is checking that wait.func has not been called until after rcu_read_lock(). If that is true, we know that p->wait hadn't been woken until that point, i.e. p hadn't reached rcu delay on the way to being freed until after our rcu_read_lock(). Ergo, it can't get freed until we do rcu_read_unlock() and we can safely take p->wait.lock.
RCU is very much relevant there.
A message that is not in the archive (sent off-list or not yet received): 20150110055148.GY22149-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 24 lines · Al Viro
Quoted 3 lines · Eric W. Biederman
I don't believe rcu anything in this function itself buys you anything, but structuring this primitive so that it can be called from an rcu list traversal seems interesting.
???
Without RCU, what would prevent it being freed right under us?
The whole point is to avoid pinning it down - as it is, we can have several processes call ->kill() on the same object. The first one would end up doing cleanup, the rest would wait *without* *affecting* *fs_pin* *lifetime*.
Note that I'm using autoremove there for
wait.func(), then in the wait loop I check (without locks) wait.task_list being empty. It is racy; deliberately so. All I really care about in there is checking that wait.func has not been called until afterrcu_read_lock(). If that is true, we know thatp->waithadn't been woken until that point, i.e. p hadn't reached rcu delay on the way to being freed until after ourrcu_read_lock(). Ergo, it can't get freed until we dorcu_read_unlock()and we can safely takep->wait.lock.
Quoted 1 line
RCU is very much relevant there.
FWIW, I've just pushed a completely untested tree in #experimental-fs_pin; it definitely will be reordered, etc., probably with quite a few of the patches from the beginning of your series mixed in, but the current tree in there should show at least what I'm aiming at.
A message that is not in the archive (sent off-list or not yet received): 20150111020030.GF22149-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 4 lines · Al Viro
FWIW, I've just pushed a completely untested tree in #experimental-fs_pin; it definitely will be reordered, etc., probably with quite a few of the patches from the beginning of your series mixed in, but the current tree in there should show at least what I'm aiming at.
... and shockingly enough, it even seems to work, once I added the missing init_waitqueue_head() in pin_insert_group()...
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to a message not in the archiveQuoted 25 lines · Al Viro
Quoted 24 lines · Al Viro
Quoted 3 lines · Eric W. Biederman
I don't believe rcu anything in this function itself buys you anything, but structuring this primitive so that it can be called from an rcu list traversal seems interesting.
???
Without RCU, what would prevent it being freed right under us?
The whole point is to avoid pinning it down - as it is, we can have several processes call ->kill() on the same object. The first one would end up doing cleanup, the rest would wait *without* *affecting* *fs_pin* *lifetime*.
Note that I'm using autoremove there for
wait.func(), then in the wait loop I check (without locks) wait.task_list being empty. It is racy; deliberately so. All I really care about in there is checking that wait.func has not been called until afterrcu_read_lock(). If that is true, we know thatp->waithadn't been woken until that point, i.e. p hadn't reached rcu delay on the way to being freed until after ourrcu_read_lock(). Ergo, it can't get freed until we dorcu_read_unlock()and we can safely takep->wait.lock.
Quoted 6 lines
Quoted 1 line
RCU is very much relevant there.
FWIW, I've just pushed a completely untested tree in #experimental-fs_pin; it definitely will be reordered, etc., probably with quite a few of the patches from the beginning of your series mixed in, but the current tree in there should show at least what I'm aiming at.
I have merged the work you have been doing and what I have been doing and posted it to a branch #for-testing of my user-namespace.git tree.
And yes I managed to make the core of the pin primitive not care about rcu, and I think I will need that property to clean up some of the weirdness that I still see with using fs_pin.
pin_insert does not wind up being a clean primitive, adding to both lists at the same time does not end up with particularly clean or obvious locking rules or a clean locking impelementation.
Still the code works and is a good starting point for further discussion and thinking. I am posting the code while I go off to see if I can spot better ways to clean some of these things up.
Eric
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to Al ViroQuoted 25 lines · Al Viro
Quoted 24 lines · Al Viro
Quoted 3 lines · Eric W. Biederman
I don't believe rcu anything in this function itself buys you anything, but structuring this primitive so that it can be called from an rcu list traversal seems interesting.
???
Without RCU, what would prevent it being freed right under us?
The whole point is to avoid pinning it down - as it is, we can have several processes call ->kill() on the same object. The first one would end up doing cleanup, the rest would wait *without* *affecting* *fs_pin* *lifetime*.
Note that I'm using autoremove there for
wait.func(), then in the wait loop I check (without locks) wait.task_list being empty. It is racy; deliberately so. All I really care about in there is checking that wait.func has not been called until afterrcu_read_lock(). If that is true, we know thatp->waithadn't been woken until that point, i.e. p hadn't reached rcu delay on the way to being freed until after ourrcu_read_lock(). Ergo, it can't get freed until we dorcu_read_unlock()and we can safely takep->wait.lock.
Quoted 6 lines
Quoted 1 line
RCU is very much relevant there.
FWIW, I've just pushed a completely untested tree in #experimental-fs_pin; it definitely will be reordered, etc., probably with quite a few of the patches from the beginning of your series mixed in, but the current tree in there should show at least what I'm aiming at.
I have merged the work you have been doing and what I have been doing and posted it to a branch #for-testing of my user-namespace.git tree.
And yes I managed to make the core of the pin primitive not care about rcu, and I think I will need that property to clean up some of the weirdness that I still see with using fs_pin.
pin_insert does not wind up being a clean primitive, adding to both lists at the same time does not end up with particularly clean or obvious locking rules or a clean locking impelementation.
Still the code works and is a good starting point for further discussion and thinking. I am posting the code while I go off to see if I can spot better ways to clean some of these things up.
Eric
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to Al ViroQuoted 7 lines · Al Viro
Quoted 6 lines · Al Viro
a) easily expressed with that scheme (behaviour is almost identical
to what your series does, except that we'd emptymnt_childright inmntput_no_expire(), rather than keeping it untilcleanup_mnt()) and
b) easily generalized toMNT_DETACHwith lazy dissolving; in fact,
the only difference would be to treat *all* submounts as "don't put on global list, don't remove from child list", not just theMNT_LOCKEDones.
I have no problem with treating all mounts whose parents are also being unmounted as "don't put on global list, don't remove from child list"
Replacing the mnt_ex_mountpoint struct path with a fs_pin seems reasonable.
We need to be careful with mounts that have parents that are not being unmounted, with respect to the mount hash table but that just looks like a detail in the overall scheme of things.
And if delaying the disolution of mounts in the mount detach case is a long held wishlist item I am all for going there. I have a patch I was playing with that did that, I just didn't include it because that is a bug fix kind of thing.
Quoted 11 lines
Quoted 2 lines
I'll play around with that today and tomorrow; hopefully, I'll have a postable variant by the weekend...
Hmm... Linus, what do you think of the following?
struct foo { int done; wait_queue_head_t wait; ... };
Where does the rcu_read_lock() happen?
I assume this is kill from fs_pin.kill?
Quoted 47 lines
void
kill_foo(struct foo *p){ wait_queue_t wait; wait.flags = WQ_FLAG_EXCLUSIVE; wait.private = current; wait.func = autoremove_wake_function; spin_lock_irq(&p->wait.lock); if (likely(!p->done)) { p->done = -1; spin_unlock_irq(&p->wait.lock); rcu_read_unlock(); /* do cleanup */ ... /* remove references to *p */ ... spin_lock_irq(&p->wait.lock); p->done = 1; wake_up_locked(&p->wait); spin_unlock_irq(&p->wait.lock); /* RCU-schedule freeing of p */ ... return; } if (p->done > 0) { spin_unlock_irq(&p->wait.lock); rcu_read_unlock(); return; } __add_wait_queue_tail(&p->wait, &wait); while (1) { set_current_state(TASK_UNINITERRUPTIBLE); spin_unlock_irq(&p->wait.lock); rcu_read_unlock(); schedule(); rcu_read_lock(); if (likely(list_empty(&wait.task_list))) break; /* OK, we know p couldn't have been freed yet */ spin_lock_irq(&p->wait.lock); if (p->done > 0) { spin_unlock_irq(&p->wait.lock); break; } } rcu_read_unlock(); }
Quoted 8 lines
AFAICS, that ought to be safe - the first caller makes sure that everybody else will wait for it to finish, everybody else (ones who come via references before the first one has removed them) will be waiting for the first one to do wakeup *and* will take care not to touch the victim unless they knows it's still there.
Do you see any problems with the above? I wonder if I ended up open-coding something already existing in there...
Your struct foo looks an awful lot like struct completion at first glance.
Eric
A message that is not in the archive (sent off-list or not yet received): [email protected]
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 2 lines · Eric W. Biederman
Where does the
rcu_read_lock()happen?
I assume this is kill from fs_pin.kill?
This is what I'd rather have *calling* fs_pin.kill (the ... part in there being the callback, with wakeup done as part of pin_remove(), which would be called by ->kill()). The thing is, I don't want mnt_pin_kill() et.al. to grab refcount on fs_pin (or for the refcount to be necessary there).
IOW, any refcounting belong on the same level as ->kill() implementation itself; for ex-mountpoint-related ones we'd need none whatsoever (->kill() would do
dput(ex-mountpoint dentry); pin_remove(pin); mntput_no_expire(containing struct mount);
and that would be it), for kernel/acct.c ones we do need some refcounting, but only for the local reasons - note that it's playing directly with refcount anyway, which is a pretty clear indication that we'd be better off with fs/fs_pin.c _not_ messing with that refcount in the first place. Getting rid of pin_put() also wouldn't hurt.
A message that is not in the archive (sent off-list or not yet received): 20150109221715.GN22149-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 13 lines · Al Viro
Quoted 2 lines · Eric W. Biederman
Where does the
rcu_read_lock()happen?
I assume this is kill from fs_pin.kill?This is what I'd rather have *calling* fs_pin.kill (the ... part in there being the callback, with wakeup done as part of
pin_remove(), which would be called by ->kill()). The thing is, I don't wantmnt_pin_kill()et.al. to grab refcount onfs_pin(or for the refcount to be necessary there).IOW, any refcounting belong on the same level as ->kill() implementation itself; for ex-mountpoint-related ones we'd need none whatsoever (->kill() would do
Quoted 8 lines
dput(ex-mountpoint dentry); pin_remove(pin); mntput_no_expire(containing struct mount);and that would be it), for
kernel/acct.cones we do need some refcounting, but only for the local reasons - note that it's playing directly with refcount anyway, which is a pretty clear indication that we'd be better off withfs/fs_pin.c_not_ messing with that refcount in the first place. Getting rid ofpin_put()also wouldn't hurt.
Got it. I agree the infrastructure related to fs_pin is pretty awakward right now.
I am digging in and seeing if I can figure out what the awkardness you are seeing that needs the suggested changes, and then I will give you my feedback.
Eric
Al Viro
maintainerRe: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
In reply to Al ViroQuoted 4 lines · Al Viro
Not sure if I like the use of
mnt_childbetweenmntput_no_expire()andcleanup_mnt()- it's probably safe, but the fewer lists we modify outside of mount hash lock, the better; hell knows, I'll need to stare at that code a bit more. FWIW, AFAICS the refcount rules with your variant are
Quoted 8 lines
* external references countribute
*mnt_parentcontributes unless it points to ourselves *or*mnt_nsis
NULL
* reachable from mount hash => add 1
* in addition to the wart aroundnamespace_unlock(), we have a similar
wart betweenmntput_no_expire()andcleanup_mnt(), only there we thread the suckers onmnt_childinstead ofmnt_listand use slightly different logics to prevent shutdown of parent fs before the dput(mountpoint).
BTW, why do you use detach_mnt() in __detach_mounts()? Unless I'm missing something really subtle, __detach_mnt() is the right thing there...
And while we are at it, all other callers of detach_mnt() are followed by attach_mnt() within the same namespace *and* all attach_mnt() follow detach_mnt(). So why bother with mnt_list in either? Or, put it another way, why have __detach_mnt() separate from detach_mnt()?
A message that is not in the archive (sent off-list or not yet received): 20150108030229.GD22149-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 11/11] mnt: Honor MNT_LOCKED when detaching mounts
Quoted 7 lines · Al Viro
BTW, why do you use
detach_mnt()in__detach_mounts()? Unless I'm missing something really subtle,__detach_mnt()is the right thing there...And while we are at it, all other callers of
detach_mnt()are followed byattach_mnt()within the same namespace *and* allattach_mnt()followdetach_mnt(). So why bother withmnt_listin either? Or, put it another way, why have__detach_mnt()separate fromdetach_mnt()?
I really don't like the look of __detach_mounts() after those changes. Suppose we have a single "locked-and-lazy" vfsmount mounted on that dentry. With nothing mounted under it. Your loop will do absolutely nothing to it - it'll just keep spinning.
Why are you doing anything to the stuff mounted under than one, anyway? It's this sucker you want to detach and kill... Normal case (umount_tree()) will detach the vfsmount we are giving it; this one should have the same effect...
cover Locked mount and loopback mount fixes
In reply to Eric W. BiedermanWay back in October Andrey Vagin reported that umount(MNT_DETACH) could be used to defeat MNT_LOCKED. As I worked to fix this I discovered that combined with mount propagation and an appropriate selection of shared subtrees a reference to a directory on an unmounted filesystem is not necessary.
That MNT_DETACH is allowed in user namespace in a form that can break MNT_LOCKED comes from my early misunderstanding what MNT_DETACH does.
To avoid breaking existing userspace the conflict between MNT_DETACH and MNT_LOCKED is fixed by leaving mounts that are locked to their parents in the mount hash table until the last reference goes away.
While investigating this issue I also found an issue with __detach_mounts. The code was unnecessarily and incorrectly triggering mount propagation. Resulting in too many mounts going away when a directory is deleted, and too many cpu cycles are burned while doing that.
Looking some more I realized that __detach_mounts by only keeping mounts connected that were MNT_LOCKED it had the potential to still leak information so I tweaked the code to keep everything locked together that possibly could be.
In the middle of all of this bug hunting and fixing it was reported that with a strategically placed rename ".." on bind mounts could go up past their root of the bind mount. Which turned out to be very easy to understand and test for but tricky to actually fix in a way that would not slow down path name lookups in the common case.
These fixes are against on v4.0-rc6 which has all of Al's new fs_pin code.
I have tested the code and I don't see any issues but as I am human I may have missed a corner case or two. So any feedback is appreciated.
For those who like to see everything in a single tree the code is at:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-testing
Eric W. Biederman (19):
mnt: Use hlist_move_list in namespace_unlock
mnt: Improve the umount_tree flags
mnt: Don't propagate umounts in __detach_mounts
mnt: In umount_tree reuse mnt_list instead of mnt_hash
mnt: Add MNT_UMOUNT flag
mnt: Delay removal from the mount hash.
mnt: On an unmount propagate clearing of MNT_LOCKED
mnt: Don't propagate unmounts to locked mounts
mnt: Fail collect_mounts when applied to unmounted mounts
mnt: Factor out unhash_mnt from detach_mnt and umount_tree
mnt: Factor umount_mnt from umount_tree
fs_pin: Allow for the possibility that m_list or s_list go unused.
mnt: Honor MNT_LOCKED when detaching mounts
mnt: Fix the error check in __detach_mounts
mnt: Update detach_mounts to leave mounts connected
mnt: Track which mounts use a dentry as root.
vfs: Test for and handle paths that are unreachable from their mnt_root
vfs: Handle mounts whose parents are unreachable from their mountpoint
vfs: Do not allow escaping from bind mounts.
fs/dcache.c | 35 +++++- fs/fs_pin.c | 4 +- fs/internal.h | 2 + fs/mount.h | 8 ++ fs/namei.c | 34 +++++- fs/namespace.c | 325 +++++++++++++++++++++++++++++++++++++++++-------- fs/pnode.c | 60 +++++++-- fs/pnode.h | 7 +- include/linux/dcache.h | 7 ++ include/linux/fs_pin.h | 2 + include/linux/mount.h | 3 + include/linux/namei.h | 2 + 12 files changed, 424 insertions(+), 65 deletions(-)
A message that is not in the archive (sent off-list or not yet received): 87a8yqou41.fsf_-_-JOvCrm2gF+uungPnsOpG7nhyD016LWXt@public.gmane.org
PATCH 1/19 mnt: Use hlist_move_list in namespace_unlock
Small cleanup to make the code more readable and maintainable.
--- fs/namespace.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-)
fs/namespace.c +5 -7
@@ -1298,17 +1298,15 @@ static HLIST_HEAD(unmounted); /* protected by namespace_sem */static void namespace_unlock (void ) { -struct hlist_head head = unmounted; +struct hlist_head head; -if (likely (hlist_empty (&head))) { -up_write (&namespace_sem); -return ; - } +hlist_move_list (&unmounted, &head); - head.first->pprev = &head.first; -INIT_HLIST_HEAD (&unmounted);up_write (&namespace_sem); +if (likely (hlist_empty (&head))) +return ; +synchronize_rcu ();group_pin_kill (&head);
Signature · 1 line
2.2.1
PATCH 2/19 mnt: Improve the umount_tree flags
In reply to a message not in the archive- Remove the unneeded declaration from pnode.h
- Mark umount_tree static as it has no callers outside of namespace.c
- Define an enumeration of umount_tree's flags.
- Pass umount_tree's flags in by name
This removes the magic numbers 0, 1 and 2 making the code a little clearer and makes it possible for there to be lazy unmounts that don't propagate. Which is what __detach_mounts actually wants for example.
--- fs/namespace.c | 31 ++++++++++++++++--------------- fs/pnode.h | 1 - 2 files changed, 16 insertions(+), 16 deletions(-)
fs/namespace.c +16 -15
@@ -1317,14 +1317,15 @@ static inline void namespace_lock(void)down_write (&namespace_sem); } +enum umount_tree_flags { +UMOUNT_SYNC =1 , +UMOUNT_PROPAGATE =2 , +};/* * mount_lock must be held * namespace_sem must be held for write -* how = 0 => just this tree, don't propagate -* how = 1 => propagate; we know that nobody else has reference to any victims -* how = 2 => lazy umount */ -void umount_tree (struct mount *mnt,int how) +static void umount_tree (struct mount *mnt,enum umount_tree_flags how) {HLIST_HEAD (tmp_list);struct mount *p;
@@ -1337,7 +1338,7 @@ void umount_tree(struct mount *mnt, int how)hlist_for_each_entry (p, &tmp_list, mnt_hash)list_del_init (&p->mnt_child); -if (how) +if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list);while (!hlist_empty (&tmp_list)) {
@@ -1347,7 +1348,7 @@ void umount_tree(struct mount *mnt, int how)list_del_init (&p->mnt_list);__touch_mnt_namespace (p->mnt_ns); p->mnt_ns =NULL ; -if (how <2 ) +if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ;pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, &unmounted);
@@ -1445,14 +1446,14 @@ static int do_umount(struct mount *mnt, int flags)if (flags &MNT_DETACH ) {if (!list_empty (&mnt->mnt_list)) -umount_tree (mnt,2 ); +umount_tree (mnt,UMOUNT_PROPAGATE ); retval =0 ; }else {shrink_submounts (mnt); retval = -EBUSY ;if (!propagate_mount_busy (mnt,2 )) {if (!list_empty (&mnt->mnt_list)) -umount_tree (mnt,1 ); +umount_tree (mnt,UMOUNT_PROPAGATE |UMOUNT_SYNC ); retval =0 ; } }
@@ -1484,7 +1485,7 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,2 ); +umount_tree (mnt,UMOUNT_PROPAGATE ); }unlock_mount_hash ();put_mountpoint (mp);
@@ -1646,7 +1647,7 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry, out:if (res) {lock_mount_hash (); -umount_tree (res,0 ); +umount_tree (res,UMOUNT_SYNC );unlock_mount_hash (); }return q;
@@ -1670,7 +1671,7 @@ void drop_collected_mounts(struct vfsmount *mnt) {namespace_lock ();lock_mount_hash (); -umount_tree (real_mount (mnt),0 ); +umount_tree (real_mount (mnt),UMOUNT_SYNC );unlock_mount_hash ();namespace_unlock (); }
@@ -1853,7 +1854,7 @@ static int attach_recursive_mnt(struct mount *source_mnt, out_cleanup_ids:while (!hlist_empty (&tree_list)) { child = hlist_entry(tree_list.first, struct mount, mnt_hash); -umount_tree (child,0 ); +umount_tree (child,UMOUNT_SYNC ); }unlock_mount_hash ();cleanup_group_ids (source_mnt,NULL );
@@ -2033,7 +2034,7 @@ static int do_loopback(struct path *path, const char *old_name, err =graft_tree (mnt, parent, mp);if (err) {lock_mount_hash (); -umount_tree (mnt,0 ); +umount_tree (mnt,UMOUNT_SYNC );unlock_mount_hash (); } out2:
@@ -2404,7 +2405,7 @@ void mark_mounts_for_expiry(struct list_head *mounts)while (!list_empty (&graveyard)) { mnt = list_first_entry(&graveyard, struct mount, mnt_expire);touch_mnt_namespace (mnt->mnt_ns); -umount_tree (mnt,1 ); +umount_tree (mnt,UMOUNT_PROPAGATE |UMOUNT_SYNC ); }unlock_mount_hash ();namespace_unlock ();
@@ -2475,7 +2476,7 @@ static void shrink_submounts(struct mount *mnt) m = list_first_entry(&graveyard, struct mount, mnt_expire);touch_mnt_namespace (m->mnt_ns); -umount_tree (m,1 ); +umount_tree (m,UMOUNT_PROPAGATE |UMOUNT_SYNC ); } } }
fs/pnode.h +0 -1
@@ -47,7 +47,6 @@ int get_dominating_id(struct mount *mnt, const struct path *root);unsigned int mnt_get_count (struct mount *mnt);void mnt_set_mountpoint (struct mount *,struct mountpoint *,struct mount *); -void umount_tree (struct mount *,int );struct mount *copy_tree (struct mount *,struct dentry *,int );bool is_path_reachable (struct mount *,struct dentry *,const struct path *root);
Signature · 1 line
2.2.1
PATCH 3/19 mnt: Don't propagate umounts in __detach_mounts
In reply to a message not in the archiveInvoking mount propagation from __detach_mounts is inefficient and wrong.
It is inefficient because __detach_mounts already walks the list of mounts that where something needs to be done, and mount propagation walks some subset of those mounts again.
It is actively wrong because if the dentry that is passed to __detach_mounts is not part of the path to a mount that mount should not be affected.
change_mnt_propagation(p,MS_PRIVATE) modifies the mount propagation tree of a master mount so it's slaves are connected to another master if possible. Which means even removing a mount from the middle of a mount tree with __detach_mounts will not deprive any mount propagated mount events.
--- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
@@ -1485,7 +1485,7 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,UMOUNT_PROPAGATE ); +umount_tree (mnt,0 ); }unlock_mount_hash ();put_mountpoint (mp);
Signature · 1 line
2.2.1
PATCH 4/19 mnt: In umount_tree reuse mnt_list instead of mnt_hash
In reply to a message not in the archiveumount_tree builds a list of mounts that need to be unmounted. Utilize mnt_list for this purpose instead of mnt_hash. This begins to allow keeping a mount on the mnt_hash after it is unmounted, which is necessary for a properly functioning MNT_LOCKED implementation.
The fact that mnt_list is an ordinary list makding available list_move is nice bonus.
--- fs/namespace.c | 20 +++++++++++--------- fs/pnode.c | 6 +++--- fs/pnode.h | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-)
fs/namespace.c +11 -9
@@ -1327,23 +1327,25 @@ enum umount_tree_flags {*/ static void umount_tree (struct mount *mnt,enum umount_tree_flags how) { -HLIST_HEAD (tmp_list); +LIST_HEAD (tmp_list);struct mount *p; -for (p = mnt; p; p =next_mnt (p, mnt)) { -hlist_del_init_rcu (&p->mnt_hash); -hlist_add_head (&p->mnt_hash, &tmp_list); - } +/* Gather the mounts to umount */ +for (p = mnt; p; p =next_mnt (p, mnt)) +list_move (&p->mnt_list, &tmp_list); -hlist_for_each_entry (p, &tmp_list, mnt_hash) +/* Hide the mounts from lookup_mnt and mnt_mounts */ +list_for_each_entry (p, &tmp_list, mnt_list) { +hlist_del_init_rcu (&p->mnt_hash);list_del_init (&p->mnt_child); + } +/* Add propogated mounts to the tmp_list */ if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list); -while (!hlist_empty (&tmp_list)) { - p = hlist_entry(tmp_list.first, struct mount, mnt_hash); -hlist_del_init_rcu (&p->mnt_hash); +while (!list_empty (&tmp_list)) { + p = list_first_entry(&tmp_list, struct mount, mnt_list);list_del_init (&p->mnt_expire);list_del_init (&p->mnt_list);__touch_mnt_namespace (p->mnt_ns);
fs/pnode.c +3 -3
@@ -384,7 +384,7 @@ static void __propagate_umount(struct mount *mnt)if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child);hlist_del_init_rcu (&child->mnt_hash); -hlist_add_before_rcu (&child->mnt_hash, &mnt->mnt_hash); +list_move_tail (&child->mnt_list, &mnt->mnt_list); } } }
@@ -396,11 +396,11 @@ static void __propagate_umount(struct mount *mnt)* * vfsmount lock must be held for write */ -int propagate_umount (struct hlist_head *list) +int propagate_umount (struct list_head *list) {struct mount *mnt; -hlist_for_each_entry (mnt, list, mnt_hash) +list_for_each_entry (mnt, list, mnt_list)__propagate_umount (mnt);return 0 ; }
fs/pnode.h +1 -1
@@ -40,7 +40,7 @@ static inline void set_mnt_shared(struct mount *mnt)void change_mnt_propagation (struct mount *,int );int propagate_mnt (struct mount *,struct mountpoint *,struct mount *,struct hlist_head *); -int propagate_umount (struct hlist_head *); +int propagate_umount (struct list_head *);int propagate_mount_busy (struct mount *,int );void mnt_release_group_id (struct mount *);int get_dominating_id (struct mount *mnt,const struct path *root);
Signature · 1 line
2.2.1
PATCH 5/19 mnt: Add MNT_UMOUNT flag
In reply to a message not in the archiveIn some instances it is necessary to know if the the unmounting process has begun on a mount. Add MNT_UMOUNT to make that reliably testable.
This fix gets used in fixing locked mounts in MNT_DETACH
--- fs/namespace.c | 4 +++- fs/pnode.c | 1 + include/linux/mount.h | 1 + 3 files changed, 5 insertions(+), 1 deletion(-)
fs/namespace.c +3 -1
@@ -1331,8 +1331,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)struct mount *p;/* Gather the mounts to umount */ -for (p = mnt; p; p =next_mnt (p, mnt)) +for (p = mnt; p; p =next_mnt (p, mnt)) { + p->mnt.mnt_flags |=MNT_UMOUNT ;list_move (&p->mnt_list, &tmp_list); + }/* Hide the mounts from lookup_mnt and mnt_mounts */ list_for_each_entry (p, &tmp_list, mnt_list) {
fs/pnode.c +1 -0
@@ -384,6 +384,7 @@ static void __propagate_umount(struct mount *mnt)if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child);hlist_del_init_rcu (&child->mnt_hash); + child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list); } }
include/linux/mount.h +1 -0
@@ -61,6 +61,7 @@ struct mnt_namespace;#define MNT_DOOMED 0x1000000#define MNT_SYNC_UMOUNT 0x2000000#define MNT_MARKED 0x4000000 +#define MNT_UMOUNT 0x8000000struct vfsmount {struct dentry *mnt_root;/* root of the mounted tree */
Signature · 1 line
2.2.1
PATCH 6/19 mnt: Delay removal from the mount hash.
In reply to a message not in the archive- Modify __lookup_mnt_hash_last to ignore mounts that have MNT_UMOUNTED set.
- Don't remove mounts from the mount hash table in propogate_umount
- Don't remove mounts from the mount hash table in umount_tree before the entire list of mounts to be umounted is selected.
- Remove mounts from the mount hash table as the last thing that happens in the case where a mount has a parent in umount_tree. Mounts without parents are not hashed (by definition).
This paves the way for delaying removal from the mount hash table even farther and fixing the MNT_LOCKED vs MNT_DETACH issue.
--- fs/namespace.c | 13 ++++++++----- fs/pnode.c | 1 - 2 files changed, 8 insertions(+), 6 deletions(-)
fs/namespace.c +8 -5
@@ -632,14 +632,17 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)*/ struct mount *__lookup_mnt_last (struct vfsmount *mnt,struct dentry *dentry) { -struct mount *p, *res; - res = p =__lookup_mnt (mnt, dentry); +struct mount *p, *res =NULL ; + p =__lookup_mnt (mnt, dentry);if (!p) goto out; +if (!(p->mnt.mnt_flags &MNT_UMOUNT )) + res = p;hlist_for_each_entry_continue (p, mnt_hash) {if (&p->mnt_parent->mnt != mnt || p->mnt_mountpoint != dentry)break ; - res = p; +if (!(p->mnt.mnt_flags &MNT_UMOUNT )) + res = p; } out:return res;
@@ -1336,9 +1339,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)list_move (&p->mnt_list, &tmp_list); } -/* Hide the mounts from lookup_mnt and mnt_mounts */ +/* Hide the mounts from mnt_mounts */ list_for_each_entry (p, &tmp_list, mnt_list) { -hlist_del_init_rcu (&p->mnt_hash);list_del_init (&p->mnt_child); }
@@ -1365,6 +1367,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt_mountpoint = p->mnt.mnt_root; p->mnt_parent = p; p->mnt_mp =NULL ; +hlist_del_init_rcu (&p->mnt_hash); }change_mnt_propagation (p,MS_PRIVATE ); }
fs/pnode.c +0 -1
@@ -383,7 +383,6 @@ static void __propagate_umount(struct mount *mnt)*/ if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child); -hlist_del_init_rcu (&child->mnt_hash); child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list); }
Signature · 1 line
2.2.1
PATCH 7/19 mnt: On an unmount propagate clearing of MNT_LOCKED
In reply to a message not in the archiveA prerequisite of calling umount_tree is that the point where the tree is mounted at is valid to unmount.
If we are propagating the effect of the unmount clear MNT_LOCKED in every instance where the same filesystem is mounted on the same mountpoint in the mount tree, as we know (by virtue of the fact that umount_tree was called) that it is safe to reveal what is at that mountpoint.
--- fs/namespace.c | 3 +++ fs/pnode.c | 20 ++++++++++++++++++++ fs/pnode.h | 1 + 3 files changed, 24 insertions(+)
fs/namespace.c +3 -0
@@ -1333,6 +1333,9 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)LIST_HEAD (tmp_list);struct mount *p; +if (how &UMOUNT_PROPAGATE ) +propagate_mount_unlock (mnt); +/* Gather the mounts to umount */ for (p = mnt; p; p =next_mnt (p, mnt)) { p->mnt.mnt_flags |=MNT_UMOUNT ;
fs/pnode.c +20 -0
@@ -362,6 +362,26 @@ int propagate_mount_busy(struct mount *mnt, int refcnt) }/* +* Clear MNT_LOCKED when it can be shown to be safe. +* +* mount_lock lock must be held for write +*/ +void propagate_mount_unlock (struct mount *mnt) +{ +struct mount *parent = mnt->mnt_parent; +struct mount *m, *child; + +BUG_ON (parent == mnt); + +for (m =propagation_next (parent, parent); m; + m =propagation_next (m, parent)) { + child =__lookup_mnt_last (&m->mnt, mnt->mnt_mountpoint); +if (child) + child->mnt.mnt_flags &= ~MNT_LOCKED ; + } +} + +/* * NOTE: unmounting 'mnt' naturally propagates to all other mounts its * parent propagates to. */
fs/pnode.h +1 -0
@@ -42,6 +42,7 @@ int propagate_mnt(struct mount *, struct mountpoint *, struct mount *, struct hlist_head *);int propagate_umount (struct list_head *);int propagate_mount_busy (struct mount *,int ); +void propagate_mount_unlock (struct mount *);void mnt_release_group_id (struct mount *);int get_dominating_id (struct mount *mnt,const struct path *root);unsigned int mnt_get_count (struct mount *mnt);
Signature · 1 line
2.2.1
PATCH 8/19 mnt: Don't propagate unmounts to locked mounts
In reply to a message not in the archiveIf the first mount in shared subtree is locked don't unmount the shared subtree.
This is ensured by walking through the mounts parents before children and marking a mount as unmountable if it is not locked or it is locked but it's parent is marked.
This allows recursive mount detach to propagate through a set of mounts when unmounting them would not reveal what is under any locked mount.
--- fs/pnode.c | 32 +++++++++++++++++++++++++++++--- fs/pnode.h | 1 + 2 files changed, 30 insertions(+), 3 deletions(-)
fs/pnode.c +29 -3
@@ -382,6 +382,26 @@ void propagate_mount_unlock(struct mount *mnt) }/* +* Mark all mounts that the MNT_LOCKED logic will allow to be unmounted. +*/ +static void mark_umount_candidates (struct mount *mnt) +{ +struct mount *parent = mnt->mnt_parent; +struct mount *m; + +BUG_ON (parent == mnt); + +for (m =propagation_next (parent, parent); m; + m =propagation_next (m, parent)) { +struct mount *child =__lookup_mnt_last (&m->mnt, + mnt->mnt_mountpoint); +if (child && (!IS_MNT_LOCKED (child) ||IS_MNT_MARKED (m))) { +SET_MNT_MARK (child); + } + } +} + +/* * NOTE: unmounting 'mnt' naturally propagates to all other mounts its * parent propagates to. */
@@ -398,10 +418,13 @@ static void __propagate_umount(struct mount *mnt)struct mount *child =__lookup_mnt_last (&m->mnt, mnt->mnt_mountpoint);/* -* umount the child only if the child has no -* other children +* umount the child only if the child has no children +* and the child is marked safe to unmount. */ -if (child &&list_empty (&child->mnt_mounts)) { +if (!child || !IS_MNT_MARKED (child)) +continue ; +CLEAR_MNT_MARK (child); +if (list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child); child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list);
@@ -420,6 +443,9 @@ int propagate_umount(struct list_head *list) {struct mount *mnt; +list_for_each_entry_reverse (mnt, list, mnt_list) +mark_umount_candidates (mnt); +list_for_each_entry (mnt, list, mnt_list)__propagate_umount (mnt);return 0 ;
fs/pnode.h +1 -0
@@ -19,6 +19,7 @@#define IS_MNT_MARKED (m) ((m)->mnt.mnt_flags & MNT_MARKED)#define SET_MNT_MARK (m) ((m)->mnt.mnt_flags |= MNT_MARKED)#define CLEAR_MNT_MARK (m) ((m)->mnt.mnt_flags &= ~MNT_MARKED) +#define IS_MNT_LOCKED (m) ((m)->mnt.mnt_flags & MNT_LOCKED)#define CL_EXPIRE 0x01#define CL_SLAVE 0x02
Signature · 1 line
2.2.1
PATCH 9/19 mnt: Fail collect_mounts when applied to unmounted mounts
In reply to a message not in the archiveThe only users of collect_mounts are in audit_tree.c
In audit_tree_trees and audit_add_tree rule the path passed into collect_mounts is generated from kern_path passed an audit_tree
pathname which is guaranteed to be an absolute path. In those casescollect_mounts is obviously intended to work on mounted paths and if a race results in paths that are unmounted when collect_mounts it is reasonable to fail early.
The paths passed into audit_tag_tree don't have the absolute path check. But are used to play with fsnotify and otherwise interact with the audit_trees, so again operating only on mounted paths appears reasonable.
Avoid having to worry about what happens when we try and audit unmounted filesystems by restricting collect_mounts to mounts that appear in the mount tree.
--- fs/namespace.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-)
fs/namespace.c +5 -2
@@ -1669,8 +1669,11 @@ struct vfsmount *collect_mounts(struct path *path) {struct mount *tree;namespace_lock (); - tree =copy_tree (real_mount (path->mnt), path->dentry, -CL_COPY_ALL |CL_PRIVATE ); +if (!check_mnt (real_mount (path->mnt))) + tree =ERR_PTR (-EINVAL ); +else + tree =copy_tree (real_mount (path->mnt), path->dentry, +CL_COPY_ALL |CL_PRIVATE );namespace_unlock ();if (IS_ERR (tree))return ERR_CAST (tree);
Signature · 1 line
2.2.1
PATCH 10/19 mnt: Factor out unhash_mnt from detach_mnt and umount_tree
In reply to a message not in the archiveCreate a function unhash_mnt that contains the common code between detach_mnt and umount_tree, and use unhash_mnt in place of the common code. This add a unncessary list_del_init(mnt->mnt_child) into umount_tree but given that mnt_child is already empty this extra line is a noop.
--- fs/namespace.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-)
fs/namespace.c +12 -9
@@ -798,10 +798,8 @@ static void __touch_mnt_namespace(struct mnt_namespace *ns)/* * vfsmount lock must be held for write */ -static void detach_mnt (struct mount *mnt,struct path *old_path) +static void unhash_mnt (struct mount *mnt) { - old_path->dentry = mnt->mnt_mountpoint; - old_path->mnt = &mnt->mnt_parent->mnt; mnt->mnt_parent = mnt; mnt->mnt_mountpoint = mnt->mnt.mnt_root;list_del_init (&mnt->mnt_child);
@@ -814,6 +812,16 @@ static void detach_mnt(struct mount *mnt, struct path *old_path)/* * vfsmount lock must be held for write */ +static void detach_mnt (struct mount *mnt,struct path *old_path) +{ + old_path->dentry = mnt->mnt_mountpoint; + old_path->mnt = &mnt->mnt_parent->mnt; +unhash_mnt (mnt); +} + +/* +* vfsmount lock must be held for write +*/ void mnt_set_mountpoint (struct mount *mnt,struct mountpoint *mp,struct mount *child_mnt)
@@ -1362,15 +1370,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, &unmounted);if (mnt_has_parent (p)) { -hlist_del_init (&p->mnt_mp_list); -put_mountpoint (p->mnt_mp);mnt_add_count (p->mnt_parent,-1 );/* old mountpoint will be dropped when we can do that */ p->mnt_ex_mountpoint = p->mnt_mountpoint; - p->mnt_mountpoint = p->mnt.mnt_root; - p->mnt_parent = p; - p->mnt_mp =NULL ; -hlist_del_init_rcu (&p->mnt_hash); +unhash_mnt (p); }change_mnt_propagation (p,MS_PRIVATE ); }
Signature · 1 line
2.2.1
PATCH 11/19 mnt: Factor umount_mnt from umount_tree
In reply to a message not in the archiveFor future use factor out a function umount_mnt from umount_tree. This function unhashes a mount and remembers where the mount was mounted so that eventually when the code makes it to a sleeping context the mountpoint can be dput.
--- fs/namespace.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
fs/namespace.c +11 -3
@@ -822,6 +822,16 @@ static void detach_mnt(struct mount *mnt, struct path *old_path)/* * vfsmount lock must be held for write */ +static void umount_mnt (struct mount *mnt) +{ +/* old mountpoint will be dropped when we can do that */ + mnt->mnt_ex_mountpoint = mnt->mnt_mountpoint; +unhash_mnt (mnt); +} + +/* +* vfsmount lock must be held for write +*/ void mnt_set_mountpoint (struct mount *mnt,struct mountpoint *mp,struct mount *child_mnt)
@@ -1371,9 +1381,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, &unmounted);if (mnt_has_parent (p)) {mnt_add_count (p->mnt_parent,-1 ); -/* old mountpoint will be dropped when we can do that */ - p->mnt_ex_mountpoint = p->mnt_mountpoint; -unhash_mnt (p); +umount_mnt (p); }change_mnt_propagation (p,MS_PRIVATE ); }
Signature · 1 line
2.2.1
PATCH 12/19 fs_pin: Allow for the possibility that m_list or s_list go unused.
In reply to a message not in the archiveThis is needed to support lazily umounting locked mounts. Because the entire unmounted subtree needs to stay together until there are no users with references to any part of the subtree.
To support this guarantee that the fs_pin m_list and s_list nodes are initialized by initializing them in init_fs_pin allowing for the possibility that pin_insert_group does not touch them.
Further use hlist_del_init in pin_remove so that there is a hlist_unhashed test before the list we attempt to update the previous list item.
--- fs/fs_pin.c | 4 ++-- include/linux/fs_pin.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-)
fs/fs_pin.c +2 -2
@@ -9,8 +9,8 @@ static DEFINE_SPINLOCK(pin_lock);void pin_remove (struct fs_pin *pin) {spin_lock (&pin_lock); -hlist_del (&pin->m_list); -hlist_del (&pin->s_list); +hlist_del_init (&pin->m_list); +hlist_del_init (&pin->s_list);spin_unlock (&pin_lock);spin_lock_irq (&pin->wait.lock); pin->done =1 ;
include/linux/fs_pin.h +2 -0
@@ -13,6 +13,8 @@ struct vfsmount;static inline void init_fs_pin (struct fs_pin *p,void (*kill)(struct fs_pin *)) {init_waitqueue_head (&p->wait); +INIT_HLIST_NODE (&p->s_list); +INIT_HLIST_NODE (&p->m_list); p->kill = kill; }
Signature · 1 line
2.2.1
PATCH 13/19 mnt: Honor MNT_LOCKED when detaching mounts
In reply to a message not in the archiveModify umount(MNT_DETACH) to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted.
In mntput_no_expire detach the children from the hash table, depending on mnt_pin_kill in cleanup_mnt to decrement the mnt_count of the children.
In __detach_mounts if there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, remove their children from the mount hash table and those children to the unmounted list so they won't linger potentially indefinitely waiting for their final mntput, now that the mounts serve no purpose.
--- fs/namespace.c | 29 ++++++++++++++++++++++++++--- fs/pnode.h | 2 ++ 2 files changed, 28 insertions(+), 3 deletions(-)
fs/namespace.c +26 -3
@@ -1099,6 +1099,13 @@ static void mntput_no_expire(struct mount *mnt)rcu_read_unlock ();list_del (&mnt->mnt_instance); + +if (unlikely (!list_empty (&mnt->mnt_mounts))) { +struct mount *p, *tmp; +list_for_each_entry_safe (p, tmp, &mnt->mnt_mounts, mnt_child) { +umount_mnt (p); + } + }unlock_mount_hash ();if (likely (!(mnt->mnt.mnt_flags &MNT_INTERNAL ))) {
@@ -1370,6 +1377,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)propagate_umount (&tmp_list);while (!list_empty (&tmp_list)) { +bool disconnect; p = list_first_entry(&tmp_list, struct mount, mnt_list);list_del_init (&p->mnt_expire);list_del_init (&p->mnt_list);
@@ -1378,10 +1386,18 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ; -pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, &unmounted); + disconnect = !IS_MNT_LOCKED_AND_LAZY (p); + +pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, + disconnect ? &unmounted :NULL );if (mnt_has_parent (p)) {mnt_add_count (p->mnt_parent,-1 ); -umount_mnt (p); +if (!disconnect) { +/* Don't forget about p */ +list_add_tail (&p->mnt_child, &p->mnt_parent->mnt_mounts); + }else { +umount_mnt (p); + } }change_mnt_propagation (p,MS_PRIVATE ); }
@@ -1506,7 +1522,14 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,0 ); +if (mnt->mnt.mnt_flags &MNT_UMOUNT ) { +struct mount *p, *tmp; +list_for_each_entry_safe (p, tmp, &mnt->mnt_mounts, mnt_child) { +hlist_add_head (&p->mnt_umount.s_list, &unmounted); +umount_mnt (p); + } + } +else umount_tree (mnt,0 ); }unlock_mount_hash ();put_mountpoint (mp);
fs/pnode.h +2 -0
@@ -20,6 +20,8 @@#define SET_MNT_MARK (m) ((m)->mnt.mnt_flags |= MNT_MARKED)#define CLEAR_MNT_MARK (m) ((m)->mnt.mnt_flags &= ~MNT_MARKED)#define IS_MNT_LOCKED (m) ((m)->mnt.mnt_flags & MNT_LOCKED) +#define IS_MNT_LOCKED_AND_LAZY (m) \ + (((m)->mnt.mnt_flags & (MNT_LOCKED|MNT_SYNC_UMOUNT)) == MNT_LOCKED)#define CL_EXPIRE 0x01#define CL_SLAVE 0x02
Signature · 1 line
2.2.1
PATCH 14/19 mnt: Fix the error check in __detach_mounts
In reply to a message not in the archivelookup_mountpoint can return either NULL or an error value. Update the test in __detach_mounts to test for an error value to avoid pathological cases causing a NULL pointer dereferences.
The callers of __detach_mounts should prevent it from ever being called on an unlinked dentry but don't take any chances.
--- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
@@ -1516,7 +1516,7 @@ void __detach_mounts(struct dentry *dentry)namespace_lock (); mp =lookup_mountpoint (dentry); -if (!mp) +if (IS_ERR_OR_NULL (mp)) goto out_unlock;lock_mount_hash ();
Signature · 1 line
2.2.1
PATCH 15/19 mnt: Update detach_mounts to leave mounts connected
In reply to a message not in the archiveNow that it is possible to lazily unmount an entire mount tree and leave the individual mounts connected to each other add a new flag UMOUNT_CONNECTED to umount_tree to force this behavior and use this flag in detach_mounts.
This closes a bug where the deletion of a file or directory could trigger an unmount and reveal data under a mount point.
--- fs/namespace.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-)
fs/namespace.c +6 -2
@@ -1348,6 +1348,7 @@ static inline void namespace_lock(void)enum umount_tree_flags {UMOUNT_SYNC =1 ,UMOUNT_PROPAGATE =2 , +UMOUNT_CONNECTED =4 , };/* * mount_lock must be held
@@ -1386,7 +1387,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ; - disconnect = !IS_MNT_LOCKED_AND_LAZY (p); + disconnect = !(((how &UMOUNT_CONNECTED ) && +mnt_has_parent (p) && + (p->mnt_parent->mnt.mnt_flags &MNT_UMOUNT )) || +IS_MNT_LOCKED_AND_LAZY (p));pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, disconnect ? &unmounted :NULL );
@@ -1529,7 +1533,7 @@ void __detach_mounts(struct dentry *dentry)umount_mnt (p); } } -else umount_tree (mnt,0 ); +else umount_tree (mnt,UMOUNT_CONNECTED ); }unlock_mount_hash ();put_mountpoint (mp);
Signature · 1 line
2.2.1
PATCH 16/19 mnt: Track which mounts use a dentry as root.
In reply to a message not in the archive--- fs/mount.h | 7 +++ fs/namespace.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 7 +++ 3 files changed, 131 insertions(+), 5 deletions(-)
fs/mount.h +7 -0
@@ -27,6 +27,12 @@ struct mountpoint {int m_count; }; +struct mountroot { +struct hlist_node r_hash; +struct dentry *r_dentry; +struct hlist_head r_list; +}; +struct mount {struct hlist_node mnt_hash;struct mount *mnt_parent;
@@ -55,6 +61,7 @@ struct mount {struct mnt_namespace *mnt_ns;/* containing namespace */ struct mountpoint *mnt_mp;/* where is it mounted */ struct hlist_node mnt_mp_list;/* list mounts with the same mountpoint */ +struct hlist_node mnt_mr_list;/* list mounts with the same mountroot */ #ifdef CONFIG_FSNOTIFY struct hlist_head mnt_fsnotify_marks;__u32 mnt_fsnotify_mask;
fs/namespace.c +117 -5
202 lines not on this page · raw message
include/linux/dcache.h +7 -0
@@ -226,6 +226,8 @@ struct dentry_operations {#define DCACHE_MAY_FREE 0x00800000#define DCACHE_FALLTHRU 0x01000000/* Fall through to lower layer */ +#define DCACHE_MOUNTROOT 0x01000000/* is root of a vfsmount */ +extern seqlock_t rename_lock;/*
@@ -401,6 +403,11 @@ static inline bool d_mountpoint(const struct dentry *dentry)return dentry->d_flags &DCACHE_MOUNTED ; } +static inline bool d_mountroot (const struct dentry *dentry) +{ +return dentry->d_flags &DCACHE_MOUNTROOT ; +} +/* * Directory cache entry type accessor functions. */
Signature · 1 line
2.2.1
PATCH 17/19 vfs: Test for and handle paths that are unreachable from their mnt_root
In reply to a message not in the archive- Add a mount flag MNT_VIOLATED to mark loopback mounts that have had a dentry moved into a directory that does not descend from the mount root dentry.
- Add a function path_connected to verify a path.dentry is reachable from path.mnt.mnt_root. AKA rename did not do something nasty to the bind mount.
- Disable ".." when a path is not connected during lookup. (Maybe we want to stop ".." at this path instead?)
Following .. is not disabled after a transition to / and is never disabled when / is the directory we start
with. Because we already limit .. no higher than /
- In prepend_path and it's callers in the d_path family for a path that is not connected don't attempt to find parent directories.
--- fs/dcache.c | 3 +++ fs/internal.h | 1 + fs/namei.c | 30 ++++++++++++++++++++++++++++++ include/linux/mount.h | 1 + include/linux/namei.h | 2 ++ 5 files changed, 37 insertions(+)
fs/dcache.c +3 -0
@@ -2871,6 +2871,9 @@ static int prepend_path(const struct path *path,char *bptr;int blen; +if (!path_connected (path)) + root = path; +rcu_read_lock (); restart_mnt:read_seqbegin_or_lock (&mount_lock, &m_seq);
fs/internal.h +1 -0
@@ -51,6 +51,7 @@ extern void __init chrdev_init(void);extern int user_path_mountpoint_at (int ,const char __user *,unsigned int ,struct path *);extern int vfs_path_lookup (struct dentry *,struct vfsmount *,const char *,unsigned int ,struct path *); +extern bool path_connected (const struct path *);/* * namespace.c
fs/namei.c +30 -0
@@ -493,6 +493,22 @@ void path_put(const struct path *path) }EXPORT_SYMBOL (path_put); +/** +* path_connected - Verify that a path->dentry is below path->mnt->mnt.mnt_root +* @path: path to verify +* +* Rename can sometimes move a file or directory outside of bind mount +* don't honor paths where this has happened. +*/ +bool path_connected (const struct path *path) +{ +struct vfsmount *mnt = path->mnt; +if (!(mnt->mnt_flags &MNT_VIOLATED )) +return true; + +return is_subdir (path->dentry, mnt->mnt_root); +} +struct nameidata {struct path path;struct qstr last;
@@ -712,6 +728,7 @@ void nd_jump_link(struct nameidata *nd, struct path *path) nd->path = *path; nd->inode = nd->path.dentry->d_inode; nd->flags |=LOOKUP_JUMPED ; + nd->flags &= ~LOOKUP_NODOTDOT ; }void nd_set_link (struct nameidata *nd,char *path)
@@ -897,6 +914,7 @@ follow_link(struct path *link, struct nameidata *nd, void **p) nd->path = nd->root;path_get (&nd->root); nd->flags |=LOOKUP_JUMPED ; + nd->flags &= ~LOOKUP_NODOTDOT ; } nd->inode = nd->path.dentry->d_inode; error =link_path_walk (s, nd);
@@ -1161,6 +1179,7 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path, path->mnt = &mounted->mnt; path->dentry = mounted->mnt.mnt_root; nd->flags |=LOOKUP_JUMPED ; + nd->flags &= ~LOOKUP_NODOTDOT ; nd->seq =read_seqcount_begin (&path->dentry->d_seq);/* * Update the inode too. We don't need to re-check the
@@ -1176,6 +1195,10 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,static int follow_dotdot_rcu (struct nameidata *nd) {struct inode *inode = nd->inode; + +if (nd->flags &LOOKUP_NODOTDOT ) +return 0 ; +if (!nd->root.mnt)set_root_rcu (nd);
@@ -1293,6 +1316,9 @@ static void follow_mount(struct path *path)static void follow_dotdot (struct nameidata *nd) { +if (nd->flags &LOOKUP_NODOTDOT ) +return ; +if (!nd->root.mnt)set_root (nd);
@@ -1909,6 +1935,8 @@ static int path_init(int dfd, const char *name, unsigned int flags, }else {get_fs_pwd (current->fs, &nd->path); } +if (unlikely (!path_connected (&nd->path))) + nd->flags |=LOOKUP_NODOTDOT ; }else {/* Caller must check execute permissions on the starting path component */ struct fd f =fdget_raw (dfd);
@@ -1936,6 +1964,8 @@ static int path_init(int dfd, const char *name, unsigned int flags,path_get (&nd->path);fdput (f); } +if (unlikely (!path_connected (&nd->path))) + nd->flags |=LOOKUP_NODOTDOT ; } nd->inode = nd->path.dentry->d_inode;
include/linux/mount.h +1 -0
@@ -62,6 +62,7 @@ struct mnt_namespace;#define MNT_SYNC_UMOUNT 0x2000000#define MNT_MARKED 0x4000000#define MNT_UMOUNT 0x8000000 +#define MNT_VIOLATED 0x10000000struct vfsmount {struct dentry *mnt_root;/* root of the mounted tree */
include/linux/namei.h +2 -0
@@ -45,6 +45,8 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};#define LOOKUP_ROOT 0x2000#define LOOKUP_EMPTY 0x4000 +#define LOOKUP_NODOTDOT 0x10000 +extern int user_path_at (int ,const char __user *,unsigned ,struct path *);extern int user_path_at_empty (int ,const char __user *,unsigned ,struct path *,int *empty);
Signature · 1 line
2.2.1
PATCH 18/19 vfs: Handle mounts whose parents are unreachable from their mountpoint
In reply to a message not in the archive- Add a mount flag MNT_UNREACHABLE_PARENT to mark mounts that can not reach their parent mount's root from their mountpoint.
- In follup_up and follow_up_rcu don't follow up if the current mount's mountpoint can not reach the parent mount's root.
- In prepend_path and it's callers in the d_path family don't follow to the parent mount if the current mount's mountpoint can not reach the parent mount's root.
--- fs/dcache.c | 3 ++- fs/namei.c | 4 ++-- include/linux/mount.h | 1 + 3 files changed, 5 insertions(+), 3 deletions(-)
fs/dcache.c +2 -1
@@ -2893,7 +2893,8 @@ restart:if (dentry == vfsmnt->mnt_root ||IS_ROOT (dentry)) {struct mount *parent =ACCESS_ONCE (mnt->mnt_parent);/* Global root? */ -if (mnt != parent) { +if ((mnt != parent) && + !(mnt->mnt.mnt_flags &MNT_UNREACHABLE_PARENT )) { dentry =ACCESS_ONCE (mnt->mnt_mountpoint); mnt = parent; vfsmnt = &mnt->mnt;
fs/namei.c +2 -2
@@ -938,7 +938,7 @@ static int follow_up_rcu(struct path *path)struct dentry *mountpoint; parent = mnt->mnt_parent; -if (&parent->mnt == path->mnt) +if ((mnt->mnt.mnt_flags &MNT_UNREACHABLE_PARENT ) || (parent == mnt))return 0 ; mountpoint = mnt->mnt_mountpoint; path->dentry = mountpoint;
@@ -964,7 +964,7 @@ int follow_up(struct path *path)read_seqlock_excl (&mount_lock); parent = mnt->mnt_parent; -if (parent == mnt) { +if ((mnt->mnt.mnt_flags &MNT_UNREACHABLE_PARENT ) || (parent == mnt)) {read_sequnlock_excl (&mount_lock);return 0 ; }
include/linux/mount.h +1 -0
@@ -63,6 +63,7 @@ struct mnt_namespace;#define MNT_MARKED 0x4000000#define MNT_UMOUNT 0x8000000#define MNT_VIOLATED 0x10000000 +#define MNT_UNREACHABLE_PARENT 0x20000000struct vfsmount {struct dentry *mnt_root;/* root of the mounted tree */
Signature · 1 line
2.2.1
PATCH 19/19 vfs: Do not allow escaping from bind mounts.
In reply to a message not in the archiveRename can move a file or directory outside of a bind mount. This has allowed programs with paths below the renamed directory to traverse up their directory tree to the real root of the filesystem instead of just the root of their bind mount.
In the presence of such renames limit applications to what the bind mount intended to reveal by marking mounts that have had dentries renamed out of them with MNT_VIOLATED, marking mounts that can no longer walk up to their parent mounts with MNT_UMOUNT_PENDING and then lazily unmounting such mounts.
All moves go through __d_move so __d_move has been modified to mark all mounts whose dentries have been moved outside of them.
Once the root dentry of a violated mount has been found a new function mnt_set_violated is called to:
- mark all mounts that have that dentry as their root as violated
- to mark all children of violated mounts that can no longer reach their parents.
- to schedule for unmounting all children of violated mounts that can no longer reach their parents.
The children that can't reach their parents are only scheduled for unmounting because the sleeping namespace_sem can not be taken inside of __d_move which can not sleep.
This change adds a field to struct mount mnt_pending_umount that is used to thread the list of pending unmounts through struct mount. As there are small but unavioable races between scheduling an unmount and the possibility of userspace calling umount_tree, umount_tree has been modified to remove all mounts that are being unmounted from the pending_umount list.
This closes a hole where it was possible in some circumstances to follow .. past the root of a bind mount.
--- fs/dcache.c | 29 ++++++++++++++++++++++++++++ fs/internal.h | 1 + fs/mount.h | 1 + fs/namespace.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+)
fs/dcache.c +29 -0
@@ -2535,6 +2535,26 @@ static void dentry_unlock_for_move(struct dentry *dentry, struct dentry *target)spin_unlock (&dentry->d_lock); } +static void mark_violated_mounts (struct dentry *dentry,struct dentry *target) +{ +/* Mark all mountroots that are ancestors of dentry +* that do not share a common ancestor with target +* +* This function assumes both dentries are part of a DAG. +*/ +struct dentry *p; + +for (p = dentry->d_parent; !IS_ROOT (p); p = p->d_parent) { +if (!d_mountroot (p)) +continue ; + +if (d_ancestor (p, target)) +break ; + +mnt_set_violated (p, dentry); + } +} +/* * When switching names, the actual string doesn't strictly have to * be preserved in the target - because we're dropping the target
@@ -2569,6 +2589,15 @@ static void __d_move(struct dentry *dentry, struct dentry *target,BUG_ON (d_ancestor (dentry, target));BUG_ON (d_ancestor (target, dentry)); +/* If we are not splicing a dentry, mark mounts which may have +* paths that are no longer able to follow d_parent up to +* mnt_root after this move. +*/ +if (!IS_ROOT (dentry) && !IS_ROOT (target)) { +mark_violated_mounts (dentry, target); +mark_violated_mounts (target, dentry); + } +dentry_lock_for_move (dentry, target);write_seqcount_begin (&dentry->d_seq);
fs/internal.h +1 -0
@@ -71,6 +71,7 @@ extern int __mnt_want_write_file(struct file *);extern void __mnt_drop_write (struct vfsmount *);extern void __mnt_drop_write_file (struct file *); +extern void mnt_set_violated (struct dentry *root,struct dentry *moving);/* * fs_struct.c */
fs/mount.h +1 -0
@@ -41,6 +41,7 @@ struct mount {union {struct rcu_head mnt_rcu;struct llist_node mnt_llist; +struct hlist_node mnt_pending_umount; };#ifdef CONFIG_SMP struct mnt_pcp __percpu *mnt_pcp;
fs/namespace.c +61 -0
79 lines not on this page · raw message
Signature · 1 line
2.2.1
cover Loopback mount escape fixes
In reply to a message not in the archiveAfter the last round of feedback I sat down and played with my fix for the fact that a strategically placed rename, ".." on bind mounts go up past the root of the bind mount.
The code better handles the escaped directory returning into it's bind mount, and is now roughly a constant factor cost in all cases from what the code costs without the fix.
So I think I have found a better tradeoff between fixing this bug and not slowing down path name lookups in the common case.
These fixes are against on v4.0-rc6.
For those who like to see everything in a single tree the code is at:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-testing
Eric W. Biederman (4):
mnt: Track which mounts use a dentry as root.
vfs: Test for and handle paths that are unreachable from their mnt_root
vfs: Handle mounts whose parents are unreachable from their mountpoint
vfs: Do not allow escaping from bind mounts.
fs/dcache.c | 82 +++++++++++++++++++++++++++--- fs/internal.h | 2 + fs/mount.h | 6 +++ fs/namei.c | 57 +++++++++++++++++---- fs/namespace.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 13 +++++ include/linux/namei.h | 2 + 7 files changed, 277 insertions(+), 20 deletions(-)
[GIT PULL] Usernamespace related locked mount fixes
In reply to a message not in the archiveLinus,
Please pull the for-linus branch from the git tree:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-linus
HEAD: e0c9c0afd2fc958ffa34b697972721d81df8a56f mnt: Update detach_mounts to leave mounts connected
Way back in October Andrey Vagin reported that umount(MNT_DETACH) could be used to defeat MNT_LOCKED. As I worked to fix this I discovered that combined with mount propagation and an appropriate selection of shared subtrees a reference to a directory on an unmounted filesystem is not necessary.
That MNT_DETACH is allowed in user namespace in a form that can break MNT_LOCKED comes from my early misunderstanding what MNT_DETACH does.
To avoid breaking existing userspace the conflict between MNT_DETACH and MNT_LOCKED is fixed by leaving mounts that are locked to their parents in the mount hash table until the last reference goes away.
While investigating this issue I also found an issue with __detach_mounts. The code was unnecessarily and incorrectly triggering mount propagation. Resulting in too many mounts going away when a directory is deleted, and too many cpu cycles are burned while doing that.
Looking some more I realized that __detach_mounts by only keeping mounts connected that were MNT_LOCKED it had the potential to still leak information so I tweaked the code to keep everything locked together that possibly could be.
This code was almost ready last cycle but Al invented fs_pin which slightly simplifies this code but required rewrites and retesting, and I have not been in top form for a while so it took me a while to get all of that done. Similiarly this pull request is late because I have been feeling absolutely miserable all week.
The issue of being able to escape a bind mount has not yet been addressed, as the fixes are not yet mature.
Eric W. Biederman (15):
mnt: Use hlist_move_list in namespace_unlock
mnt: Improve the umount_tree flags
mnt: Don't propagate umounts in __detach_mounts
mnt: In umount_tree reuse mnt_list instead of mnt_hash
mnt: Add MNT_UMOUNT flag
mnt: Delay removal from the mount hash.
mnt: On an unmount propagate clearing of MNT_LOCKED
mnt: Don't propagate unmounts to locked mounts
mnt: Fail collect_mounts when applied to unmounted mounts
mnt: Factor out unhash_mnt from detach_mnt and umount_tree
mnt: Factor umount_mnt from umount_tree
fs_pin: Allow for the possibility that m_list or s_list go unused.
mnt: Honor MNT_LOCKED when detaching mounts
mnt: Fix the error check in __detach_mounts
mnt: Update detach_mounts to leave mounts connected
fs/fs_pin.c | 4 +- fs/namespace.c | 142 +++++++++++++++++++++++++++++++++---------------- fs/pnode.c | 60 ++++++++++++++++++--- fs/pnode.h | 7 ++- include/linux/fs_pin.h | 2 + include/linux/mount.h | 1 + 6 files changed, 159 insertions(+), 57 deletions(-)
PATCH 1/19 mnt: Use hlist_move_list in namespace_unlock
In reply to Eric W. BiedermanSmall cleanup to make the code more readable and maintainable.
--- fs/namespace.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-)
fs/namespace.c +5 -7
@@ -1298,17 +1298,15 @@ static HLIST_HEAD(unmounted); /* protected by namespace_sem */static void namespace_unlock (void ) { -struct hlist_head head = unmounted; +struct hlist_head head; -if (likely (hlist_empty (&head))) { -up_write (&namespace_sem); -return ; - } +hlist_move_list (&unmounted, &head); - head.first->pprev = &head.first; -INIT_HLIST_HEAD (&unmounted);up_write (&namespace_sem); +if (likely (hlist_empty (&head))) +return ; +synchronize_rcu ();group_pin_kill (&head);
Signature · 1 line
2.2.1
PATCH 2/19 mnt: Improve the umount_tree flags
In reply to Eric W. Biederman- Remove the unneeded declaration from pnode.h
- Mark umount_tree static as it has no callers outside of namespace.c
- Define an enumeration of umount_tree's flags.
- Pass umount_tree's flags in by name
This removes the magic numbers 0, 1 and 2 making the code a little clearer and makes it possible for there to be lazy unmounts that don't propagate. Which is what __detach_mounts actually wants for example.
--- fs/namespace.c | 31 ++++++++++++++++--------------- fs/pnode.h | 1 - 2 files changed, 16 insertions(+), 16 deletions(-)
fs/namespace.c +16 -15
@@ -1317,14 +1317,15 @@ static inline void namespace_lock(void)down_write (&namespace_sem); } +enum umount_tree_flags { +UMOUNT_SYNC =1 , +UMOUNT_PROPAGATE =2 , +};/* * mount_lock must be held * namespace_sem must be held for write -* how = 0 => just this tree, don't propagate -* how = 1 => propagate; we know that nobody else has reference to any victims -* how = 2 => lazy umount */ -void umount_tree (struct mount *mnt,int how) +static void umount_tree (struct mount *mnt,enum umount_tree_flags how) {HLIST_HEAD (tmp_list);struct mount *p;
@@ -1337,7 +1338,7 @@ void umount_tree(struct mount *mnt, int how)hlist_for_each_entry (p, &tmp_list, mnt_hash)list_del_init (&p->mnt_child); -if (how) +if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list);while (!hlist_empty (&tmp_list)) {
@@ -1347,7 +1348,7 @@ void umount_tree(struct mount *mnt, int how)list_del_init (&p->mnt_list);__touch_mnt_namespace (p->mnt_ns); p->mnt_ns =NULL ; -if (how <2 ) +if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ;pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, &unmounted);
@@ -1445,14 +1446,14 @@ static int do_umount(struct mount *mnt, int flags)if (flags &MNT_DETACH ) {if (!list_empty (&mnt->mnt_list)) -umount_tree (mnt,2 ); +umount_tree (mnt,UMOUNT_PROPAGATE ); retval =0 ; }else {shrink_submounts (mnt); retval = -EBUSY ;if (!propagate_mount_busy (mnt,2 )) {if (!list_empty (&mnt->mnt_list)) -umount_tree (mnt,1 ); +umount_tree (mnt,UMOUNT_PROPAGATE |UMOUNT_SYNC ); retval =0 ; } }
@@ -1484,7 +1485,7 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,2 ); +umount_tree (mnt,UMOUNT_PROPAGATE ); }unlock_mount_hash ();put_mountpoint (mp);
@@ -1646,7 +1647,7 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry, out:if (res) {lock_mount_hash (); -umount_tree (res,0 ); +umount_tree (res,UMOUNT_SYNC );unlock_mount_hash (); }return q;
@@ -1670,7 +1671,7 @@ void drop_collected_mounts(struct vfsmount *mnt) {namespace_lock ();lock_mount_hash (); -umount_tree (real_mount (mnt),0 ); +umount_tree (real_mount (mnt),UMOUNT_SYNC );unlock_mount_hash ();namespace_unlock (); }
@@ -1853,7 +1854,7 @@ static int attach_recursive_mnt(struct mount *source_mnt, out_cleanup_ids:while (!hlist_empty (&tree_list)) { child = hlist_entry(tree_list.first, struct mount, mnt_hash); -umount_tree (child,0 ); +umount_tree (child,UMOUNT_SYNC ); }unlock_mount_hash ();cleanup_group_ids (source_mnt,NULL );
@@ -2033,7 +2034,7 @@ static int do_loopback(struct path *path, const char *old_name, err =graft_tree (mnt, parent, mp);if (err) {lock_mount_hash (); -umount_tree (mnt,0 ); +umount_tree (mnt,UMOUNT_SYNC );unlock_mount_hash (); } out2:
@@ -2404,7 +2405,7 @@ void mark_mounts_for_expiry(struct list_head *mounts)while (!list_empty (&graveyard)) { mnt = list_first_entry(&graveyard, struct mount, mnt_expire);touch_mnt_namespace (mnt->mnt_ns); -umount_tree (mnt,1 ); +umount_tree (mnt,UMOUNT_PROPAGATE |UMOUNT_SYNC ); }unlock_mount_hash ();namespace_unlock ();
@@ -2475,7 +2476,7 @@ static void shrink_submounts(struct mount *mnt) m = list_first_entry(&graveyard, struct mount, mnt_expire);touch_mnt_namespace (m->mnt_ns); -umount_tree (m,1 ); +umount_tree (m,UMOUNT_PROPAGATE |UMOUNT_SYNC ); } } }
fs/pnode.h +0 -1
@@ -47,7 +47,6 @@ int get_dominating_id(struct mount *mnt, const struct path *root);unsigned int mnt_get_count (struct mount *mnt);void mnt_set_mountpoint (struct mount *,struct mountpoint *,struct mount *); -void umount_tree (struct mount *,int );struct mount *copy_tree (struct mount *,struct dentry *,int );bool is_path_reachable (struct mount *,struct dentry *,const struct path *root);
Signature · 1 line
2.2.1
PATCH 3/19 mnt: Don't propagate umounts in __detach_mounts
In reply to Eric W. BiedermanInvoking mount propagation from __detach_mounts is inefficient and wrong.
It is inefficient because __detach_mounts already walks the list of mounts that where something needs to be done, and mount propagation walks some subset of those mounts again.
It is actively wrong because if the dentry that is passed to __detach_mounts is not part of the path to a mount that mount should not be affected.
change_mnt_propagation(p,MS_PRIVATE) modifies the mount propagation tree of a master mount so it's slaves are connected to another master if possible. Which means even removing a mount from the middle of a mount tree with __detach_mounts will not deprive any mount propagated mount events.
--- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
@@ -1485,7 +1485,7 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,UMOUNT_PROPAGATE ); +umount_tree (mnt,0 ); }unlock_mount_hash ();put_mountpoint (mp);
Signature · 1 line
2.2.1
PATCH 4/19 mnt: In umount_tree reuse mnt_list instead of mnt_hash
In reply to Eric W. Biedermanumount_tree builds a list of mounts that need to be unmounted. Utilize mnt_list for this purpose instead of mnt_hash. This begins to allow keeping a mount on the mnt_hash after it is unmounted, which is necessary for a properly functioning MNT_LOCKED implementation.
The fact that mnt_list is an ordinary list makding available list_move is nice bonus.
--- fs/namespace.c | 20 +++++++++++--------- fs/pnode.c | 6 +++--- fs/pnode.h | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-)
fs/namespace.c +11 -9
@@ -1327,23 +1327,25 @@ enum umount_tree_flags {*/ static void umount_tree (struct mount *mnt,enum umount_tree_flags how) { -HLIST_HEAD (tmp_list); +LIST_HEAD (tmp_list);struct mount *p; -for (p = mnt; p; p =next_mnt (p, mnt)) { -hlist_del_init_rcu (&p->mnt_hash); -hlist_add_head (&p->mnt_hash, &tmp_list); - } +/* Gather the mounts to umount */ +for (p = mnt; p; p =next_mnt (p, mnt)) +list_move (&p->mnt_list, &tmp_list); -hlist_for_each_entry (p, &tmp_list, mnt_hash) +/* Hide the mounts from lookup_mnt and mnt_mounts */ +list_for_each_entry (p, &tmp_list, mnt_list) { +hlist_del_init_rcu (&p->mnt_hash);list_del_init (&p->mnt_child); + } +/* Add propogated mounts to the tmp_list */ if (how &UMOUNT_PROPAGATE )propagate_umount (&tmp_list); -while (!hlist_empty (&tmp_list)) { - p = hlist_entry(tmp_list.first, struct mount, mnt_hash); -hlist_del_init_rcu (&p->mnt_hash); +while (!list_empty (&tmp_list)) { + p = list_first_entry(&tmp_list, struct mount, mnt_list);list_del_init (&p->mnt_expire);list_del_init (&p->mnt_list);__touch_mnt_namespace (p->mnt_ns);
fs/pnode.c +3 -3
@@ -384,7 +384,7 @@ static void __propagate_umount(struct mount *mnt)if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child);hlist_del_init_rcu (&child->mnt_hash); -hlist_add_before_rcu (&child->mnt_hash, &mnt->mnt_hash); +list_move_tail (&child->mnt_list, &mnt->mnt_list); } } }
@@ -396,11 +396,11 @@ static void __propagate_umount(struct mount *mnt)* * vfsmount lock must be held for write */ -int propagate_umount (struct hlist_head *list) +int propagate_umount (struct list_head *list) {struct mount *mnt; -hlist_for_each_entry (mnt, list, mnt_hash) +list_for_each_entry (mnt, list, mnt_list)__propagate_umount (mnt);return 0 ; }
fs/pnode.h +1 -1
@@ -40,7 +40,7 @@ static inline void set_mnt_shared(struct mount *mnt)void change_mnt_propagation (struct mount *,int );int propagate_mnt (struct mount *,struct mountpoint *,struct mount *,struct hlist_head *); -int propagate_umount (struct hlist_head *); +int propagate_umount (struct list_head *);int propagate_mount_busy (struct mount *,int );void mnt_release_group_id (struct mount *);int get_dominating_id (struct mount *mnt,const struct path *root);
Signature · 1 line
2.2.1
PATCH 5/19 mnt: Add MNT_UMOUNT flag
In reply to Eric W. BiedermanIn some instances it is necessary to know if the the unmounting process has begun on a mount. Add MNT_UMOUNT to make that reliably testable.
This fix gets used in fixing locked mounts in MNT_DETACH
--- fs/namespace.c | 4 +++- fs/pnode.c | 1 + include/linux/mount.h | 1 + 3 files changed, 5 insertions(+), 1 deletion(-)
fs/namespace.c +3 -1
@@ -1331,8 +1331,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)struct mount *p;/* Gather the mounts to umount */ -for (p = mnt; p; p =next_mnt (p, mnt)) +for (p = mnt; p; p =next_mnt (p, mnt)) { + p->mnt.mnt_flags |=MNT_UMOUNT ;list_move (&p->mnt_list, &tmp_list); + }/* Hide the mounts from lookup_mnt and mnt_mounts */ list_for_each_entry (p, &tmp_list, mnt_list) {
fs/pnode.c +1 -0
@@ -384,6 +384,7 @@ static void __propagate_umount(struct mount *mnt)if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child);hlist_del_init_rcu (&child->mnt_hash); + child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list); } }
include/linux/mount.h +1 -0
@@ -61,6 +61,7 @@ struct mnt_namespace;#define MNT_DOOMED 0x1000000#define MNT_SYNC_UMOUNT 0x2000000#define MNT_MARKED 0x4000000 +#define MNT_UMOUNT 0x8000000struct vfsmount {struct dentry *mnt_root;/* root of the mounted tree */
Signature · 1 line
2.2.1
PATCH 6/19 mnt: Delay removal from the mount hash.
In reply to Eric W. Biederman- Modify __lookup_mnt_hash_last to ignore mounts that have MNT_UMOUNTED set.
- Don't remove mounts from the mount hash table in propogate_umount
- Don't remove mounts from the mount hash table in umount_tree before the entire list of mounts to be umounted is selected.
- Remove mounts from the mount hash table as the last thing that happens in the case where a mount has a parent in umount_tree. Mounts without parents are not hashed (by definition).
This paves the way for delaying removal from the mount hash table even farther and fixing the MNT_LOCKED vs MNT_DETACH issue.
--- fs/namespace.c | 13 ++++++++----- fs/pnode.c | 1 - 2 files changed, 8 insertions(+), 6 deletions(-)
fs/namespace.c +8 -5
@@ -632,14 +632,17 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)*/ struct mount *__lookup_mnt_last (struct vfsmount *mnt,struct dentry *dentry) { -struct mount *p, *res; - res = p =__lookup_mnt (mnt, dentry); +struct mount *p, *res =NULL ; + p =__lookup_mnt (mnt, dentry);if (!p) goto out; +if (!(p->mnt.mnt_flags &MNT_UMOUNT )) + res = p;hlist_for_each_entry_continue (p, mnt_hash) {if (&p->mnt_parent->mnt != mnt || p->mnt_mountpoint != dentry)break ; - res = p; +if (!(p->mnt.mnt_flags &MNT_UMOUNT )) + res = p; } out:return res;
@@ -1336,9 +1339,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)list_move (&p->mnt_list, &tmp_list); } -/* Hide the mounts from lookup_mnt and mnt_mounts */ +/* Hide the mounts from mnt_mounts */ list_for_each_entry (p, &tmp_list, mnt_list) { -hlist_del_init_rcu (&p->mnt_hash);list_del_init (&p->mnt_child); }
@@ -1365,6 +1367,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p->mnt_mountpoint = p->mnt.mnt_root; p->mnt_parent = p; p->mnt_mp =NULL ; +hlist_del_init_rcu (&p->mnt_hash); }change_mnt_propagation (p,MS_PRIVATE ); }
fs/pnode.c +0 -1
@@ -383,7 +383,6 @@ static void __propagate_umount(struct mount *mnt)*/ if (child &&list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child); -hlist_del_init_rcu (&child->mnt_hash); child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list); }
Signature · 1 line
2.2.1
PATCH 7/19 mnt: On an unmount propagate clearing of MNT_LOCKED
In reply to Eric W. BiedermanA prerequisite of calling umount_tree is that the point where the tree is mounted at is valid to unmount.
If we are propagating the effect of the unmount clear MNT_LOCKED in every instance where the same filesystem is mounted on the same mountpoint in the mount tree, as we know (by virtue of the fact that umount_tree was called) that it is safe to reveal what is at that mountpoint.
--- fs/namespace.c | 3 +++ fs/pnode.c | 20 ++++++++++++++++++++ fs/pnode.h | 1 + 3 files changed, 24 insertions(+)
fs/namespace.c +3 -0
@@ -1333,6 +1333,9 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)LIST_HEAD (tmp_list);struct mount *p; +if (how &UMOUNT_PROPAGATE ) +propagate_mount_unlock (mnt); +/* Gather the mounts to umount */ for (p = mnt; p; p =next_mnt (p, mnt)) { p->mnt.mnt_flags |=MNT_UMOUNT ;
fs/pnode.c +20 -0
@@ -362,6 +362,26 @@ int propagate_mount_busy(struct mount *mnt, int refcnt) }/* +* Clear MNT_LOCKED when it can be shown to be safe. +* +* mount_lock lock must be held for write +*/ +void propagate_mount_unlock (struct mount *mnt) +{ +struct mount *parent = mnt->mnt_parent; +struct mount *m, *child; + +BUG_ON (parent == mnt); + +for (m =propagation_next (parent, parent); m; + m =propagation_next (m, parent)) { + child =__lookup_mnt_last (&m->mnt, mnt->mnt_mountpoint); +if (child) + child->mnt.mnt_flags &= ~MNT_LOCKED ; + } +} + +/* * NOTE: unmounting 'mnt' naturally propagates to all other mounts its * parent propagates to. */
fs/pnode.h +1 -0
@@ -42,6 +42,7 @@ int propagate_mnt(struct mount *, struct mountpoint *, struct mount *, struct hlist_head *);int propagate_umount (struct list_head *);int propagate_mount_busy (struct mount *,int ); +void propagate_mount_unlock (struct mount *);void mnt_release_group_id (struct mount *);int get_dominating_id (struct mount *mnt,const struct path *root);unsigned int mnt_get_count (struct mount *mnt);
Signature · 1 line
2.2.1
PATCH 8/19 mnt: Don't propagate unmounts to locked mounts
In reply to Eric W. BiedermanIf the first mount in shared subtree is locked don't unmount the shared subtree.
This is ensured by walking through the mounts parents before children and marking a mount as unmountable if it is not locked or it is locked but it's parent is marked.
This allows recursive mount detach to propagate through a set of mounts when unmounting them would not reveal what is under any locked mount.
--- fs/pnode.c | 32 +++++++++++++++++++++++++++++--- fs/pnode.h | 1 + 2 files changed, 30 insertions(+), 3 deletions(-)
fs/pnode.c +29 -3
@@ -382,6 +382,26 @@ void propagate_mount_unlock(struct mount *mnt) }/* +* Mark all mounts that the MNT_LOCKED logic will allow to be unmounted. +*/ +static void mark_umount_candidates (struct mount *mnt) +{ +struct mount *parent = mnt->mnt_parent; +struct mount *m; + +BUG_ON (parent == mnt); + +for (m =propagation_next (parent, parent); m; + m =propagation_next (m, parent)) { +struct mount *child =__lookup_mnt_last (&m->mnt, + mnt->mnt_mountpoint); +if (child && (!IS_MNT_LOCKED (child) ||IS_MNT_MARKED (m))) { +SET_MNT_MARK (child); + } + } +} + +/* * NOTE: unmounting 'mnt' naturally propagates to all other mounts its * parent propagates to. */
@@ -398,10 +418,13 @@ static void __propagate_umount(struct mount *mnt)struct mount *child =__lookup_mnt_last (&m->mnt, mnt->mnt_mountpoint);/* -* umount the child only if the child has no -* other children +* umount the child only if the child has no children +* and the child is marked safe to unmount. */ -if (child &&list_empty (&child->mnt_mounts)) { +if (!child || !IS_MNT_MARKED (child)) +continue ; +CLEAR_MNT_MARK (child); +if (list_empty (&child->mnt_mounts)) {list_del_init (&child->mnt_child); child->mnt.mnt_flags |=MNT_UMOUNT ;list_move_tail (&child->mnt_list, &mnt->mnt_list);
@@ -420,6 +443,9 @@ int propagate_umount(struct list_head *list) {struct mount *mnt; +list_for_each_entry_reverse (mnt, list, mnt_list) +mark_umount_candidates (mnt); +list_for_each_entry (mnt, list, mnt_list)__propagate_umount (mnt);return 0 ;
fs/pnode.h +1 -0
@@ -19,6 +19,7 @@#define IS_MNT_MARKED (m) ((m)->mnt.mnt_flags & MNT_MARKED)#define SET_MNT_MARK (m) ((m)->mnt.mnt_flags |= MNT_MARKED)#define CLEAR_MNT_MARK (m) ((m)->mnt.mnt_flags &= ~MNT_MARKED) +#define IS_MNT_LOCKED (m) ((m)->mnt.mnt_flags & MNT_LOCKED)#define CL_EXPIRE 0x01#define CL_SLAVE 0x02
Signature · 1 line
2.2.1
PATCH 9/19 mnt: Fail collect_mounts when applied to unmounted mounts
In reply to Eric W. BiedermanThe only users of collect_mounts are in audit_tree.c
In audit_tree_trees and audit_add_tree rule the path passed into collect_mounts is generated from kern_path passed an audit_tree
pathname which is guaranteed to be an absolute path. In those casescollect_mounts is obviously intended to work on mounted paths and if a race results in paths that are unmounted when collect_mounts it is reasonable to fail early.
The paths passed into audit_tag_tree don't have the absolute path check. But are used to play with fsnotify and otherwise interact with the audit_trees, so again operating only on mounted paths appears reasonable.
Avoid having to worry about what happens when we try and audit unmounted filesystems by restricting collect_mounts to mounts that appear in the mount tree.
--- fs/namespace.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-)
fs/namespace.c +5 -2
@@ -1669,8 +1669,11 @@ struct vfsmount *collect_mounts(struct path *path) {struct mount *tree;namespace_lock (); - tree =copy_tree (real_mount (path->mnt), path->dentry, -CL_COPY_ALL |CL_PRIVATE ); +if (!check_mnt (real_mount (path->mnt))) + tree =ERR_PTR (-EINVAL ); +else + tree =copy_tree (real_mount (path->mnt), path->dentry, +CL_COPY_ALL |CL_PRIVATE );namespace_unlock ();if (IS_ERR (tree))return ERR_CAST (tree);
Signature · 1 line
2.2.1
A message that is not in the archive (sent off-list or not yet received): 1428026183-14879-9-git-send-email-ebiederm-aS9lmoZGLiVWk0Htik3J/[email protected]
Re: [PATCH review 09/19] mnt: Fail collect_mounts when applied to unmounted mounts
Quoted 3 lines · Eric W. Biederman
The only users of
collect_mountsare inaudit_tree.cIn
audit_tree_treesandaudit_add_treerule the path passed into
I think you meant audit_trim_trees.
Also you missed a _ in audit_add_tree_rule.
Quoted 38 lines
collect_mountsis generated fromkern_pathpassed anaudit_tree
pathname which is guaranteed to be an absolute path. In those casescollect_mountsis obviously intended to work on mounted paths and if a race results in paths that are unmounted whencollect_mountsit is reasonable to fail early.The paths passed into
audit_tag_treedon't have the absolute path check. But are used to play with fsnotify and otherwise interact with theaudit_trees, so again operating only on mounted paths appears reasonable.Avoid having to worry about what happens when we try and audit unmounted filesystems by restricting
collect_mountsto mounts that appear in the mount tree.--- fs/namespace.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-)fs/namespace.c +5
-2@@ -1669,8 +1669,11 @@ struct vfsmount *collect_mounts(struct path *path) {struct mount *tree;namespace_lock (); - tree =copy_tree (real_mount (path->mnt), path->dentry, -CL_COPY_ALL |CL_PRIVATE ); +if (!check_mnt (real_mount (path->mnt))) + tree =ERR_PTR (-EINVAL ); +else + tree =copy_tree (real_mount (path->mnt), path->dentry, +CL_COPY_ALL |CL_PRIVATE );namespace_unlock ();if (IS_ERR (tree))return ERR_CAST (tree);
Signature · 3 lines
Lukasz Pawelczyk Samsung R&D Institute Poland Samsung Electronics
Re: [PATCH review 09/19] mnt: Fail collect_mounts when applied to unmounted mounts
In reply to Eric W. BiedermanQuoted 3 lines · Eric W. Biederman
The only users of
collect_mountsare inaudit_tree.cIn
audit_tree_treesandaudit_add_treerule the path passed into
I think you meant audit_trim_trees.
Also you missed a _ in audit_add_tree_rule.
Quoted 38 lines
collect_mountsis generated fromkern_pathpassed anaudit_tree
pathname which is guaranteed to be an absolute path. In those casescollect_mountsis obviously intended to work on mounted paths and if a race results in paths that are unmounted whencollect_mountsit is reasonable to fail early.The paths passed into
audit_tag_treedon't have the absolute path check. But are used to play with fsnotify and otherwise interact with theaudit_trees, so again operating only on mounted paths appears reasonable.Avoid having to worry about what happens when we try and audit unmounted filesystems by restricting
collect_mountsto mounts that appear in the mount tree.Signed-off-by "Eric W. Biederman" <[email protected]>--- fs/namespace.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-)fs/namespace.c +5
-2@@ -1669,8 +1669,11 @@ struct vfsmount *collect_mounts(struct path *path) {struct mount *tree;namespace_lock (); - tree =copy_tree (real_mount (path->mnt), path->dentry, -CL_COPY_ALL |CL_PRIVATE ); +if (!check_mnt (real_mount (path->mnt))) + tree =ERR_PTR (-EINVAL ); +else + tree =copy_tree (real_mount (path->mnt), path->dentry, +CL_COPY_ALL |CL_PRIVATE );namespace_unlock ();if (IS_ERR (tree))return ERR_CAST (tree);
Signature · 3 lines
Lukasz Pawelczyk Samsung R&D Institute Poland Samsung Electronics
A message that is not in the archive (sent off-list or not yet received): [email protected]
Re: [PATCH review 09/19] mnt: Fail collect_mounts when applied to unmounted mounts
Quoted 8 lines · Lukasz Pawelczyk
Quoted 3 lines · Eric W. Biederman
The only users of
collect_mountsare inaudit_tree.cIn
audit_tree_treesandaudit_add_treerule the path passed intoI think you meant
audit_trim_trees.Also you missed a _ in
audit_add_tree_rule.
I did thank you.
Eric
PATCH 10/19 mnt: Factor out unhash_mnt from detach_mnt and umount_tree
In reply to Eric W. BiedermanCreate a function unhash_mnt that contains the common code between detach_mnt and umount_tree, and use unhash_mnt in place of the common code. This add a unncessary list_del_init(mnt->mnt_child) into umount_tree but given that mnt_child is already empty this extra line is a noop.
--- fs/namespace.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-)
fs/namespace.c +12 -9
@@ -798,10 +798,8 @@ static void __touch_mnt_namespace(struct mnt_namespace *ns)/* * vfsmount lock must be held for write */ -static void detach_mnt (struct mount *mnt,struct path *old_path) +static void unhash_mnt (struct mount *mnt) { - old_path->dentry = mnt->mnt_mountpoint; - old_path->mnt = &mnt->mnt_parent->mnt; mnt->mnt_parent = mnt; mnt->mnt_mountpoint = mnt->mnt.mnt_root;list_del_init (&mnt->mnt_child);
@@ -814,6 +812,16 @@ static void detach_mnt(struct mount *mnt, struct path *old_path)/* * vfsmount lock must be held for write */ +static void detach_mnt (struct mount *mnt,struct path *old_path) +{ + old_path->dentry = mnt->mnt_mountpoint; + old_path->mnt = &mnt->mnt_parent->mnt; +unhash_mnt (mnt); +} + +/* +* vfsmount lock must be held for write +*/ void mnt_set_mountpoint (struct mount *mnt,struct mountpoint *mp,struct mount *child_mnt)
@@ -1362,15 +1370,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, &unmounted);if (mnt_has_parent (p)) { -hlist_del_init (&p->mnt_mp_list); -put_mountpoint (p->mnt_mp);mnt_add_count (p->mnt_parent,-1 );/* old mountpoint will be dropped when we can do that */ p->mnt_ex_mountpoint = p->mnt_mountpoint; - p->mnt_mountpoint = p->mnt.mnt_root; - p->mnt_parent = p; - p->mnt_mp =NULL ; -hlist_del_init_rcu (&p->mnt_hash); +unhash_mnt (p); }change_mnt_propagation (p,MS_PRIVATE ); }
Signature · 1 line
2.2.1
PATCH 11/19 mnt: Factor umount_mnt from umount_tree
In reply to Eric W. BiedermanFor future use factor out a function umount_mnt from umount_tree. This function unhashes a mount and remembers where the mount was mounted so that eventually when the code makes it to a sleeping context the mountpoint can be dput.
--- fs/namespace.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
fs/namespace.c +11 -3
@@ -822,6 +822,16 @@ static void detach_mnt(struct mount *mnt, struct path *old_path)/* * vfsmount lock must be held for write */ +static void umount_mnt (struct mount *mnt) +{ +/* old mountpoint will be dropped when we can do that */ + mnt->mnt_ex_mountpoint = mnt->mnt_mountpoint; +unhash_mnt (mnt); +} + +/* +* vfsmount lock must be held for write +*/ void mnt_set_mountpoint (struct mount *mnt,struct mountpoint *mp,struct mount *child_mnt)
@@ -1371,9 +1381,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, &unmounted);if (mnt_has_parent (p)) {mnt_add_count (p->mnt_parent,-1 ); -/* old mountpoint will be dropped when we can do that */ - p->mnt_ex_mountpoint = p->mnt_mountpoint; -unhash_mnt (p); +umount_mnt (p); }change_mnt_propagation (p,MS_PRIVATE ); }
Signature · 1 line
2.2.1
PATCH 12/19 fs_pin: Allow for the possibility that m_list or s_list go unused.
In reply to Eric W. BiedermanThis is needed to support lazily umounting locked mounts. Because the entire unmounted subtree needs to stay together until there are no users with references to any part of the subtree.
To support this guarantee that the fs_pin m_list and s_list nodes are initialized by initializing them in init_fs_pin allowing for the possibility that pin_insert_group does not touch them.
Further use hlist_del_init in pin_remove so that there is a hlist_unhashed test before the list we attempt to update the previous list item.
--- fs/fs_pin.c | 4 ++-- include/linux/fs_pin.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-)
fs/fs_pin.c +2 -2
@@ -9,8 +9,8 @@ static DEFINE_SPINLOCK(pin_lock);void pin_remove (struct fs_pin *pin) {spin_lock (&pin_lock); -hlist_del (&pin->m_list); -hlist_del (&pin->s_list); +hlist_del_init (&pin->m_list); +hlist_del_init (&pin->s_list);spin_unlock (&pin_lock);spin_lock_irq (&pin->wait.lock); pin->done =1 ;
include/linux/fs_pin.h +2 -0
@@ -13,6 +13,8 @@ struct vfsmount;static inline void init_fs_pin (struct fs_pin *p,void (*kill)(struct fs_pin *)) {init_waitqueue_head (&p->wait); +INIT_HLIST_NODE (&p->s_list); +INIT_HLIST_NODE (&p->m_list); p->kill = kill; }
Signature · 1 line
2.2.1
A message that is not in the archive (sent off-list or not yet received): 1428026183-14879-12-git-send-email-ebiederm-aS9lmoZGLiVWk0Htik3J/[email protected]
Re: [PATCH review 12/19] fs_pin: Allow for the possibility that m_list or s_list go unused.
I've seen crash in 4.0.2 while played with namespaces. This patch helped. So, it should be queued into stable@ for sure, but I don't know how many kernel versions are affected.
[29221.493301] BUG: unable to handle kernel NULL pointer dereference at (null) [29221.493396] IP: [<ffffffff811d4ad8>] pin_remove+0x58/0xc0 [29221.493456] PGD 0 [29221.493481] Oops: 0002 [#1] SMP [29221.493521] Modules linked in: iwldvm iwlwifi nfsd auth_rpcgss
oid_registry nfs_acl nfs lockd grace sunrpc ipt_MASQUERADE nf_nat_masquerade_ipv4 iptable_nat bridge stp llc vfat fat fuse snd_hda_codec_hdmi snd_hda_codec_conexant snd_hda_codec_generic iTCO_wdt intel_powerclamp coretemp kvm_intel uvcvideo kvm videobuf2_vmalloc videobuf2_memops videobuf2_core v4l2_common snd_hda_intel videodev snd_hda_controller snd_hda_codec snd_hwdep snd_pcm i915 lpc_ich mfd_core thinkpad_acpi snd_timer wmi snd soundcore drm_kms_helper sdhci_pci sdhci e1000e
[29221.494197] CPU: 2 PID: 30219 Comm: ct.sh Not tainted 4.0.2-zurg+ #167 [29221.494291] Hardware name: LENOVO 4291QY6/4291QY6, BIOS 8DET51WW (1.21 ) 08/02/2011 [29221.494392] task: ffff8803cb3e3bf0 ti: ffff8803ebf00000 task.ti: ffff8803ebf00000 [29221.494514] RIP: 0010:[<ffffffff811d4ad8>] [<ffffffff811d4ad8>] pin_remove+0x58/0xc0 [29221.494655] RSP: 0018:ffff8803ebf03da8 EFLAGS: 00010246 [29221.494746] RAX: 0000000000000000 RBX: ffff88040ad33620 RCX: 000000000000000d [29221.494854] RDX: 0000000000000000 RSI: 0000000000000003 RDI: ffffffff81ed92f0 [29221.494969] RBP: ffff8803ebf03db8 R08: ffffffff81cf9fa8 R09: 0000000000000246 [29221.495081] R10: 000000000001e001 R11: 0000000000000000 R12: ffff8803ebf03e08 [29221.495178] R13: ffff8803cb3e42e0 R14: ffff8803cb3e3bf0 R15: ffff88040a37cd68 [29221.495260] FS: 00007f02da4f8700(0000) GS:ffff88041e280000(0000) knlGS:0000000000000000 [29221.495343] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [29221.495395] CR2: 0000000000000000 CR3: 0000000002c0d000 CR4: 00000000000407e0 [29221.495456] Stack: [29221.495477] ffff8803cb3e3bf0 ffff88040ad33620 ffff8803ebf03dd8 ffffffff811c2f82 [29221.495554] ffff8803ebf03df8 ffff88040ad33620 ffff8803ebf03e38 ffffffff811d4c54 [29221.495630] ffffffff81c65b40 ffff880300000000 ffff8803cb3e3bf0 ffffffff810be600 [29221.495707] Call Trace: [29221.495739] [<ffffffff811c2f82>] drop_mountpoint+0x22/0x40 [29221.495773] [<ffffffff811d4c54>] pin_kill+0x64/0xf0 [29221.495790] [<ffffffff810be600>] ? wait_woken+0x90/0x90 [29221.495806] [<ffffffff811d4d09>] mnt_pin_kill+0x29/0x40 [29221.495822] [<ffffffff811c2410>] cleanup_mnt+0x90/0xa0 [29221.495838] [<ffffffff811c2472>] __cleanup_mnt+0x12/0x20 [29221.495855] [<ffffffff810a2d67>] task_work_run+0xb7/0xf0 [29221.495873] [<ffffffff81089332>] do_exit+0x2d2/0xac0 [29221.495890] [<ffffffff811a39d8>] ? __vfs_read+0x18/0x50 [29221.495905] [<ffffffff811a3a9a>] ? vfs_read+0x8a/0x120 [29221.495921] [<ffffffff8108a887>] do_group_exit+0x47/0xc0 [29221.495937] [<ffffffff8108a914>] SyS_exit_group+0x14/0x20 [29221.495961] [<ffffffff816da20d>] system_call_fastpath+0x16/0x1b [29221.495978] Code: 48 89 50 08 48 b8 00 01 10 00 00 00 ad de 48 8b 53 28 48 89 43 30 48 b8 00 02 20 00 00 00 ad de 48 89 43 38 48 8b 43 20 48 85 c0 <48> 89 02 74 04 48 89 50 08 48 b8 00 01 10 00 00 00 ad de 48 89 [29221.496108] RIP [<ffffffff811d4ad8>] pin_remove+0x58/0xc0 [29221.496126] RSP <ffff8803ebf03da8> [29221.496136] CR2: 0000000000000000
Quoted 53 lines · Eric W. Biederman
This is needed to support lazily umounting locked mounts. Because the entire unmounted subtree needs to stay together until there are no users with references to any part of the subtree.
To support this guarantee that the
fs_pinm_listands_listnodes are initialized by initializing them ininit_fs_pinallowing for the possibility thatpin_insert_groupdoes not touch them.Further use
hlist_del_initinpin_removeso that there is ahlist_unhashedtest before the list we attempt to update the previous list item.--- fs/fs_pin.c | 4 ++-- include/linux/fs_pin.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-)fs/fs_pin.c +2
-2@@ -9,8 +9,8 @@ static DEFINE_SPINLOCK(pin_lock);void pin_remove (struct fs_pin *pin) {spin_lock (&pin_lock); -hlist_del (&pin->m_list); -hlist_del (&pin->s_list); +hlist_del_init (&pin->m_list); +hlist_del_init (&pin->s_list);spin_unlock (&pin_lock);spin_lock_irq (&pin->wait.lock); pin->done =1 ;include/linux/fs_pin.h +2
-1@@ -13,6 +13,8 @@ struct vfsmount;static inline void init_fs_pin (struct fs_pin *p,void (*kill)(struct fs_pin *)) {init_waitqueue_head (&p->wait); +INIT_HLIST_NODE (&p->s_list); +INIT_HLIST_NODE (&p->m_list); p->kill = kill; } --2.2.1
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
PATCH 13/19 mnt: Honor MNT_LOCKED when detaching mounts
In reply to Eric W. BiedermanModify umount(MNT_DETACH) to keep mounts in the hash table that are locked to their parent mounts, when the parent is lazily unmounted.
In mntput_no_expire detach the children from the hash table, depending on mnt_pin_kill in cleanup_mnt to decrement the mnt_count of the children.
In __detach_mounts if there are any mounts that have been unmounted but still are on the list of mounts of a mountpoint, remove their children from the mount hash table and those children to the unmounted list so they won't linger potentially indefinitely waiting for their final mntput, now that the mounts serve no purpose.
--- fs/namespace.c | 29 ++++++++++++++++++++++++++--- fs/pnode.h | 2 ++ 2 files changed, 28 insertions(+), 3 deletions(-)
fs/namespace.c +26 -3
@@ -1099,6 +1099,13 @@ static void mntput_no_expire(struct mount *mnt)rcu_read_unlock ();list_del (&mnt->mnt_instance); + +if (unlikely (!list_empty (&mnt->mnt_mounts))) { +struct mount *p, *tmp; +list_for_each_entry_safe (p, tmp, &mnt->mnt_mounts, mnt_child) { +umount_mnt (p); + } + }unlock_mount_hash ();if (likely (!(mnt->mnt.mnt_flags &MNT_INTERNAL ))) {
@@ -1370,6 +1377,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)propagate_umount (&tmp_list);while (!list_empty (&tmp_list)) { +bool disconnect; p = list_first_entry(&tmp_list, struct mount, mnt_list);list_del_init (&p->mnt_expire);list_del_init (&p->mnt_list);
@@ -1378,10 +1386,18 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ; -pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, &unmounted); + disconnect = !IS_MNT_LOCKED_AND_LAZY (p); + +pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, + disconnect ? &unmounted :NULL );if (mnt_has_parent (p)) {mnt_add_count (p->mnt_parent,-1 ); -umount_mnt (p); +if (!disconnect) { +/* Don't forget about p */ +list_add_tail (&p->mnt_child, &p->mnt_parent->mnt_mounts); + }else { +umount_mnt (p); + } }change_mnt_propagation (p,MS_PRIVATE ); }
@@ -1506,7 +1522,14 @@ void __detach_mounts(struct dentry *dentry)lock_mount_hash ();while (!hlist_empty (&mp->m_list)) { mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list); -umount_tree (mnt,0 ); +if (mnt->mnt.mnt_flags &MNT_UMOUNT ) { +struct mount *p, *tmp; +list_for_each_entry_safe (p, tmp, &mnt->mnt_mounts, mnt_child) { +hlist_add_head (&p->mnt_umount.s_list, &unmounted); +umount_mnt (p); + } + } +else umount_tree (mnt,0 ); }unlock_mount_hash ();put_mountpoint (mp);
fs/pnode.h +2 -0
@@ -20,6 +20,8 @@#define SET_MNT_MARK (m) ((m)->mnt.mnt_flags |= MNT_MARKED)#define CLEAR_MNT_MARK (m) ((m)->mnt.mnt_flags &= ~MNT_MARKED)#define IS_MNT_LOCKED (m) ((m)->mnt.mnt_flags & MNT_LOCKED) +#define IS_MNT_LOCKED_AND_LAZY (m) \ + (((m)->mnt.mnt_flags & (MNT_LOCKED|MNT_SYNC_UMOUNT)) == MNT_LOCKED)#define CL_EXPIRE 0x01#define CL_SLAVE 0x02
Signature · 1 line
2.2.1
PATCH 14/19 mnt: Fix the error check in __detach_mounts
In reply to Eric W. Biedermanlookup_mountpoint can return either NULL or an error value. Update the test in __detach_mounts to test for an error value to avoid pathological cases causing a NULL pointer dereferences.
The callers of __detach_mounts should prevent it from ever being called on an unlinked dentry but don't take any chances.
--- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
@@ -1516,7 +1516,7 @@ void __detach_mounts(struct dentry *dentry)namespace_lock (); mp =lookup_mountpoint (dentry); -if (!mp) +if (IS_ERR_OR_NULL (mp)) goto out_unlock;lock_mount_hash ();
Signature · 1 line
2.2.1
PATCH 15/19 mnt: Update detach_mounts to leave mounts connected
In reply to Eric W. BiedermanNow that it is possible to lazily unmount an entire mount tree and leave the individual mounts connected to each other add a new flag UMOUNT_CONNECTED to umount_tree to force this behavior and use this flag in detach_mounts.
This closes a bug where the deletion of a file or directory could trigger an unmount and reveal data under a mount point.
--- fs/namespace.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-)
fs/namespace.c +6 -2
@@ -1348,6 +1348,7 @@ static inline void namespace_lock(void)enum umount_tree_flags {UMOUNT_SYNC =1 ,UMOUNT_PROPAGATE =2 , +UMOUNT_CONNECTED =4 , };/* * mount_lock must be held
@@ -1386,7 +1387,10 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)if (how &UMOUNT_SYNC ) p->mnt.mnt_flags |=MNT_SYNC_UMOUNT ; - disconnect = !IS_MNT_LOCKED_AND_LAZY (p); + disconnect = !(((how &UMOUNT_CONNECTED ) && +mnt_has_parent (p) && + (p->mnt_parent->mnt.mnt_flags &MNT_UMOUNT )) || +IS_MNT_LOCKED_AND_LAZY (p));pin_insert_group (&p->mnt_umount, &p->mnt_parent->mnt, disconnect ? &unmounted :NULL );
@@ -1529,7 +1533,7 @@ void __detach_mounts(struct dentry *dentry)umount_mnt (p); } } -else umount_tree (mnt,0 ); +else umount_tree (mnt,UMOUNT_CONNECTED ); }unlock_mount_hash ();put_mountpoint (mp);
Signature · 1 line
2.2.1
PATCH 16/19 mnt: Track which mounts use a dentry as root.
In reply to Eric W. Biederman--- fs/mount.h | 7 +++ fs/namespace.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 7 +++ 3 files changed, 131 insertions(+), 5 deletions(-)
fs/mount.h +7 -0
@@ -27,6 +27,12 @@ struct mountpoint {int m_count; }; +struct mountroot { +struct hlist_node r_hash; +struct dentry *r_dentry; +struct hlist_head r_list; +}; +struct mount {struct hlist_node mnt_hash;struct mount *mnt_parent;
@@ -55,6 +61,7 @@ struct mount {struct mnt_namespace *mnt_ns;/* containing namespace */ struct mountpoint *mnt_mp;/* where is it mounted */ struct hlist_node mnt_mp_list;/* list mounts with the same mountpoint */ +struct hlist_node mnt_mr_list;/* list mounts with the same mountroot */ #ifdef CONFIG_FSNOTIFY struct hlist_head mnt_fsnotify_marks;__u32 mnt_fsnotify_mask;
fs/namespace.c +117 -5
202 lines not on this page · raw message
include/linux/dcache.h +7 -0
@@ -226,6 +226,8 @@ struct dentry_operations {#define DCACHE_MAY_FREE 0x00800000#define DCACHE_FALLTHRU 0x01000000/* Fall through to lower layer */ +#define DCACHE_MOUNTROOT 0x01000000/* is root of a vfsmount */ +extern seqlock_t rename_lock;/*
@@ -401,6 +403,11 @@ static inline bool d_mountpoint(const struct dentry *dentry)return dentry->d_flags &DCACHE_MOUNTED ; } +static inline bool d_mountroot (const struct dentry *dentry) +{ +return dentry->d_flags &DCACHE_MOUNTROOT ; +} +/* * Directory cache entry type accessor functions. */
Signature · 1 line
2.2.1
A message that is not in the archive (sent off-list or not yet received): 1428026183-14879-16-git-send-email-ebiederm-aS9lmoZGLiVWk0Htik3J/[email protected]
Re: [PATCH review 16/19] mnt: Track which mounts use a dentry as root.
On Thu, Apr 02, 2015 at 08:56:20PM -0500, Eric W. Biederman wrote:
One general note - I'd probably put a pointer to that sucker into struct mount. For one thing, root-preserving clone_mnt() is a fairly common case. For another, searching for that thing in mnt_put_root() looks wrong. Matter of taste, but...
Another thing is that IMO it's better to preallocate that thing in vfs_kern_mount() and free if it turns out to be unused. Simpler cleanup path that way...
fs/namespace.c vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void · line 1039
Quoted 4 lines
fs/namespace.c +3
-1- mnt->mnt.mnt_root = root; + err =mnt_set_root (mnt, root); +if (err) { +dput (mnt->mnt.mnt_root);
Unless I'm misreading your code, mnt_set_root() does *not* set it
on failure, so what's going on here?
Quoted 1 line
#define
DCACHE_FALLTHRU0x01000000/* Fall through to lower layer */
^^^^^^^^^^
Quoted 1 line
+#define
DCACHE_MOUNTROOT0x01000000/* is root of a vfsmount */
^^^^^^^^^^ Er...
A message that is not in the archive (sent off-list or not yet received): 20150403055449.GE889-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 16/19] mnt: Track which mounts use a dentry as root.
Quoted 8 lines · Al Viro
On Thu, Apr 02, 2015 at 08:56:20PM -0500, Eric W. Biederman wrote:
One general note - I'd probably put a pointer to that sucker into struct
mount. For one thing, root-preservingclone_mnt()is a fairly common case. For another, searching for that thing inmnt_put_root()looks wrong. Matter of taste, but...
Quoted 4 lines
Another thing is that IMO it's better to preallocate that thing in
vfs_kern_mount()and free if it turns out to be unused. Simpler cleanup
path that way...
Those do sound like reasonable simplifications.
Quoted 7 lines
fs/namespace.c vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void · line 1039
Quoted 4 lines
fs/namespace.c +3
-1- mnt->mnt.mnt_root = root; + err =mnt_set_root (mnt, root); +if (err) { +dput (mnt->mnt.mnt_root);Unless I'm misreading your code,
mnt_set_root()does *not* set it
on failure, so what's going on here?
I will have to look when I get the code in front of me again.
Quoted 7 lines
Quoted 1 line
#define
DCACHE_FALLTHRU0x01000000/* Fall through to lower layer*/
^^^^^^^^^^Quoted 1 line
+#define
DCACHE_MOUNTROOT0x01000000/* is root of a vfsmount */^^^^^^^^^^ Er...
Good point. I don't think DCACHE_FALLTHRU existed when I wrote the patch and I missed this detail during the rebase. Sigh.
I will fix it for the next round. Hopefully DCACHE_FALLTHRU does not have implications for the rest of my changes.
Eric
Re: [PATCH review 16/19] mnt: Track which mounts use a dentry as root.
In reply to a message not in the archiveQuoted 6 lines · Al Viro
On Thu, Apr 02, 2015 at 08:56:20PM -0500, Eric W. Biederman wrote:
One general note - I'd probably put a pointer to that sucker into struct mount. For one thing, root-preserving
clone_mnt()is a fairly common case. For another, searching for that thing inmnt_put_root()looks wrong. Matter of taste, but...
So I just played with the possibilities and adding a field in struct mount makes the code more complicated not less. So I have developed a distaste for the idea of having a struct mountroot pointer in struct mount.
It especially complicates clone_mnt where I always have to have the code look at the dentry to find the associated a dentry. Resuing a current struct mountroot and/or preallocating one is just a complicated mess. The current implementation has a much more localized (and thus understandable and maintainable) implementation.
Quoted 3 lines
Another thing is that IMO it's better to preallocate that thing in
vfs_kern_mount()and free if it turns out to be unused. Simpler cleanup path that way...
It is a touch cleaner in vfs_kern_mount (not as many things need to be freed) and much uglier and in clone_mnt.
Quoted 7 lines
fs/namespace.c vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void · line 1039
Quoted 4 lines
fs/namespace.c +3
-1- mnt->mnt.mnt_root = root; + err =mnt_set_root (mnt, root); +if (err) { +dput (mnt->mnt.mnt_root);Unless I'm misreading your code,
mnt_set_root()does *not* set it
on failure, so what's going on here?
Typo. That should simply have been dput(root);
Eric
Re: [PATCH review 16/19] mnt: Track which mounts use a dentry as root.
In reply to Al ViroQuoted 6 lines · Al Viro
On Thu, Apr 02, 2015 at 08:56:20PM -0500, Eric W. Biederman wrote:
One general note - I'd probably put a pointer to that sucker into struct mount. For one thing, root-preserving
clone_mnt()is a fairly common case. For another, searching for that thing inmnt_put_root()looks wrong. Matter of taste, but...
So I just played with the possibilities and adding a field in struct mount makes the code more complicated not less. So I have developed a distaste for the idea of having a struct mountroot pointer in struct mount.
It especially complicates clone_mnt where I always have to have the code look at the dentry to find the associated a dentry. Resuing a current struct mountroot and/or preallocating one is just a complicated mess. The current implementation has a much more localized (and thus understandable and maintainable) implementation.
Quoted 3 lines
Another thing is that IMO it's better to preallocate that thing in
vfs_kern_mount()and free if it turns out to be unused. Simpler cleanup path that way...
It is a touch cleaner in vfs_kern_mount (not as many things need to be freed) and much uglier and in clone_mnt.
Quoted 7 lines
fs/namespace.c vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void · line 1039
Quoted 4 lines
fs/namespace.c +3
-1- mnt->mnt.mnt_root = root; + err =mnt_set_root (mnt, root); +if (err) { +dput (mnt->mnt.mnt_root);Unless I'm misreading your code,
mnt_set_root()does *not* set it
on failure, so what's going on here?
Typo. That should simply have been dput(root);
Eric
PATCH 17/19 vfs: Test for and handle paths that are unreachable from their mnt_root
In reply to Eric W. Biederman- Add a mount flag MNT_VIOLATED to mark loopback mounts that have had a dentry moved into a directory that does not descend from the mount root dentry.
- Add a function path_connected to verify a path.dentry is reachable from path.mnt.mnt_root. AKA rename did not do something nasty to the bind mount.
- Disable ".." when a path is not connected during lookup. (Maybe we want to stop ".." at this path instead?)
Following .. is not disabled after a transition to / and is never disabled when / is the directory we start
with. Because we already limit .. no higher than /
- In prepend_path and it's callers in the d_path family for a path that is not connected don't attempt to find parent directories.
--- fs/dcache.c | 3 +++ fs/internal.h | 1 + fs/namei.c | 30 ++++++++++++++++++++++++++++++ include/linux/mount.h | 1 + include/linux/namei.h | 2 ++ 5 files changed, 37 insertions(+)
fs/dcache.c +3 -0
@@ -2871,6 +2871,9 @@ static int prepend_path(const struct path *path,char *bptr;int blen; +if (!path_connected (path)) + root = path; +rcu_read_lock (); restart_mnt:read_seqbegin_or_lock (&mount_lock, &m_seq);
fs/internal.h +1 -0
@@ -51,6 +51,7 @@ extern void __init chrdev_init(void);extern int user_path_mountpoint_at (int ,const char __user *,unsigned int ,struct path *);extern int vfs_path_lookup (struct dentry *,struct vfsmount *,const char *,unsigned int ,struct path *); +extern bool path_connected (const struct path *);/* * namespace.c
fs/namei.c +30 -0
@@ -493,6 +493,22 @@ void path_put(const struct path *path) }EXPORT_SYMBOL (path_put); +/** +* path_connected - Verify that a path->dentry is below path->mnt->mnt.mnt_root +* @path: path to verify +* +* Rename can sometimes move a file or directory outside of bind mount +* don't honor paths where this has happened. +*/ +bool path_connected (const struct path *path) +{ +struct vfsmount *mnt = path->mnt; +if (!(mnt->mnt_flags &MNT_VIOLATED )) +return true; + +return is_subdir (path->dentry, mnt->mnt_root); +} +struct nameidata {struct path path;struct qstr last;
@@ -712,6 +728,7 @@ void nd_jump_link(struct nameidata *nd, struct path *path) nd->path = *path; nd->inode = nd->path.dentry->d_inode; nd->flags |=LOOKUP_JUMPED ; + nd->flags &= ~LOOKUP_NODOTDOT ; }void nd_set_link (struct nameidata *nd,char *path)
@@ -897,6 +914,7 @@ follow_link(struct path *link, struct nameidata *nd, void **p) nd->path = nd->root;path_get (&nd->root); nd->flags |=LOOKUP_JUMPED ; + nd->flags &= ~LOOKUP_NODOTDOT ; } nd->inode = nd->path.dentry->d_inode; error =link_path_walk (s, nd);
@@ -1161,6 +1179,7 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path, path->mnt = &mounted->mnt; path->dentry = mounted->mnt.mnt_root; nd->flags |=LOOKUP_JUMPED ; + nd->flags &= ~LOOKUP_NODOTDOT ; nd->seq =read_seqcount_begin (&path->dentry->d_seq);/* * Update the inode too. We don't need to re-check the
@@ -1176,6 +1195,10 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,static int follow_dotdot_rcu (struct nameidata *nd) {struct inode *inode = nd->inode; + +if (nd->flags &LOOKUP_NODOTDOT ) +return 0 ; +if (!nd->root.mnt)set_root_rcu (nd);
@@ -1293,6 +1316,9 @@ static void follow_mount(struct path *path)static void follow_dotdot (struct nameidata *nd) { +if (nd->flags &LOOKUP_NODOTDOT ) +return ; +if (!nd->root.mnt)set_root (nd);
@@ -1909,6 +1935,8 @@ static int path_init(int dfd, const char *name, unsigned int flags, }else {get_fs_pwd (current->fs, &nd->path); } +if (unlikely (!path_connected (&nd->path))) + nd->flags |=LOOKUP_NODOTDOT ; }else {/* Caller must check execute permissions on the starting path component */ struct fd f =fdget_raw (dfd);
@@ -1936,6 +1964,8 @@ static int path_init(int dfd, const char *name, unsigned int flags,path_get (&nd->path);fdput (f); } +if (unlikely (!path_connected (&nd->path))) + nd->flags |=LOOKUP_NODOTDOT ; } nd->inode = nd->path.dentry->d_inode;
include/linux/mount.h +1 -0
@@ -62,6 +62,7 @@ struct mnt_namespace;#define MNT_SYNC_UMOUNT 0x2000000#define MNT_MARKED 0x4000000#define MNT_UMOUNT 0x8000000 +#define MNT_VIOLATED 0x10000000struct vfsmount {struct dentry *mnt_root;/* root of the mounted tree */
include/linux/namei.h +2 -0
@@ -45,6 +45,8 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};#define LOOKUP_ROOT 0x2000#define LOOKUP_EMPTY 0x4000 +#define LOOKUP_NODOTDOT 0x10000 +extern int user_path_at (int ,const char __user *,unsigned ,struct path *);extern int user_path_at_empty (int ,const char __user *,unsigned ,struct path *,int *empty);
Signature · 1 line
2.2.1
PATCH 18/19 vfs: Handle mounts whose parents are unreachable from their mountpoint
In reply to Eric W. Biederman- Add a mount flag MNT_UNREACHABLE_PARENT to mark mounts that can not reach their parent mount's root from their mountpoint.
- In follup_up and follow_up_rcu don't follow up if the current mount's mountpoint can not reach the parent mount's root.
- In prepend_path and it's callers in the d_path family don't follow to the parent mount if the current mount's mountpoint can not reach the parent mount's root.
--- fs/dcache.c | 3 ++- fs/namei.c | 4 ++-- include/linux/mount.h | 1 + 3 files changed, 5 insertions(+), 3 deletions(-)
fs/dcache.c +2 -1
@@ -2893,7 +2893,8 @@ restart:if (dentry == vfsmnt->mnt_root ||IS_ROOT (dentry)) {struct mount *parent =ACCESS_ONCE (mnt->mnt_parent);/* Global root? */ -if (mnt != parent) { +if ((mnt != parent) && + !(mnt->mnt.mnt_flags &MNT_UNREACHABLE_PARENT )) { dentry =ACCESS_ONCE (mnt->mnt_mountpoint); mnt = parent; vfsmnt = &mnt->mnt;
fs/namei.c +2 -2
@@ -938,7 +938,7 @@ static int follow_up_rcu(struct path *path)struct dentry *mountpoint; parent = mnt->mnt_parent; -if (&parent->mnt == path->mnt) +if ((mnt->mnt.mnt_flags &MNT_UNREACHABLE_PARENT ) || (parent == mnt))return 0 ; mountpoint = mnt->mnt_mountpoint; path->dentry = mountpoint;
@@ -964,7 +964,7 @@ int follow_up(struct path *path)read_seqlock_excl (&mount_lock); parent = mnt->mnt_parent; -if (parent == mnt) { +if ((mnt->mnt.mnt_flags &MNT_UNREACHABLE_PARENT ) || (parent == mnt)) {read_sequnlock_excl (&mount_lock);return 0 ; }
include/linux/mount.h +1 -0
@@ -63,6 +63,7 @@ struct mnt_namespace;#define MNT_MARKED 0x4000000#define MNT_UMOUNT 0x8000000#define MNT_VIOLATED 0x10000000 +#define MNT_UNREACHABLE_PARENT 0x20000000struct vfsmount {struct dentry *mnt_root;/* root of the mounted tree */
Signature · 1 line
2.2.1
PATCH 19/19 vfs: Do not allow escaping from bind mounts.
In reply to Eric W. BiedermanRename can move a file or directory outside of a bind mount. This has allowed programs with paths below the renamed directory to traverse up their directory tree to the real root of the filesystem instead of just the root of their bind mount.
In the presence of such renames limit applications to what the bind mount intended to reveal by marking mounts that have had dentries renamed out of them with MNT_VIOLATED, marking mounts that can no longer walk up to their parent mounts with MNT_UMOUNT_PENDING and then lazily unmounting such mounts.
All moves go through __d_move so __d_move has been modified to mark all mounts whose dentries have been moved outside of them.
Once the root dentry of a violated mount has been found a new function mnt_set_violated is called to:
- mark all mounts that have that dentry as their root as violated
- to mark all children of violated mounts that can no longer reach their parents.
- to schedule for unmounting all children of violated mounts that can no longer reach their parents.
The children that can't reach their parents are only scheduled for unmounting because the sleeping namespace_sem can not be taken inside of __d_move which can not sleep.
This change adds a field to struct mount mnt_pending_umount that is used to thread the list of pending unmounts through struct mount. As there are small but unavioable races between scheduling an unmount and the possibility of userspace calling umount_tree, umount_tree has been modified to remove all mounts that are being unmounted from the pending_umount list.
This closes a hole where it was possible in some circumstances to follow .. past the root of a bind mount.
--- fs/dcache.c | 29 ++++++++++++++++++++++++++++ fs/internal.h | 1 + fs/mount.h | 1 + fs/namespace.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+)
fs/dcache.c +29 -0
@@ -2535,6 +2535,26 @@ static void dentry_unlock_for_move(struct dentry *dentry, struct dentry *target)spin_unlock (&dentry->d_lock); } +static void mark_violated_mounts (struct dentry *dentry,struct dentry *target) +{ +/* Mark all mountroots that are ancestors of dentry +* that do not share a common ancestor with target +* +* This function assumes both dentries are part of a DAG. +*/ +struct dentry *p; + +for (p = dentry->d_parent; !IS_ROOT (p); p = p->d_parent) { +if (!d_mountroot (p)) +continue ; + +if (d_ancestor (p, target)) +break ; + +mnt_set_violated (p, dentry); + } +} +/* * When switching names, the actual string doesn't strictly have to * be preserved in the target - because we're dropping the target
@@ -2569,6 +2589,15 @@ static void __d_move(struct dentry *dentry, struct dentry *target,BUG_ON (d_ancestor (dentry, target));BUG_ON (d_ancestor (target, dentry)); +/* If we are not splicing a dentry, mark mounts which may have +* paths that are no longer able to follow d_parent up to +* mnt_root after this move. +*/ +if (!IS_ROOT (dentry) && !IS_ROOT (target)) { +mark_violated_mounts (dentry, target); +mark_violated_mounts (target, dentry); + } +dentry_lock_for_move (dentry, target);write_seqcount_begin (&dentry->d_seq);
fs/internal.h +1 -0
@@ -71,6 +71,7 @@ extern int __mnt_want_write_file(struct file *);extern void __mnt_drop_write (struct vfsmount *);extern void __mnt_drop_write_file (struct file *); +extern void mnt_set_violated (struct dentry *root,struct dentry *moving);/* * fs_struct.c */
fs/mount.h +1 -0
@@ -41,6 +41,7 @@ struct mount {union {struct rcu_head mnt_rcu;struct llist_node mnt_llist; +struct hlist_node mnt_pending_umount; };#ifdef CONFIG_SMP struct mnt_pcp __percpu *mnt_pcp;
fs/namespace.c +61 -0
79 lines not on this page · raw message
Signature · 1 line
2.2.1
A message that is not in the archive (sent off-list or not yet received): 1428026183-14879-19-git-send-email-ebiederm-aS9lmoZGLiVWk0Htik3J/[email protected]
Re: [PATCH review 19/19] vfs: Do not allow escaping from bind mounts.
Quoted 5 lines · Eric W. Biederman
fs/dcache.c +5
-0+static void mark_violated_mounts (struct dentry *dentry,struct dentry *target) +{ +/* Mark all mountroots that are ancestors of dentry +* that do not share a common ancestor with target +*
fs/dcache.c static void dentry_unlock_for_move(struct dentry *dentry, struct dentry *target) · line 2552
Quoted 10 lines
fs/dcache.c +10
-0+* This function assumes both dentries are part of a DAG. +*/ +struct dentry *p; + +for (p = dentry->d_parent; !IS_ROOT (p); p = p->d_parent) { +if (!d_mountroot (p)) +continue ; + +if (d_ancestor (p, target)) +break ;
Egads... You do realize that you'll keep walking the path from target to root again and again? It's a tree and we have already walked up to the root. So we know the depths and tree topology can't change due to the rename_lock being held by caller.
Quoted 32 lines
(quoted diff) +30
-0+ if (!IS_ROOT(dentry) && !IS_ROOT(target)) { + mark_violated_mounts(dentry, target); + mark_violated_mounts(target, dentry); + } + dentry_lock_for_move(dentry, target); +void mnt_set_violated(struct dentry *root, struct dentry *moving) +{ + struct mountroot *mr; + struct mount *mnt; + + lock_mount_hash(); + mr = lookup_mountroot(root); + if (!mr) + goto out; + + hlist_for_each_entry(mnt, &mr->r_list, mnt_mr_list) { + struct mount *child; + /* Be wary of this mount */ + mnt->mnt.mnt_flags |= MNT_VIOLATED; + + list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) { + /* Ignore children that will continue to be connected */ + if ((child->mnt_mountpoint != moving) && + !d_ancestor(moving, child->mnt_mountpoint)) + continue; + + /* Deal with mounts loosing the connection to + * their parents + */ + if (!(child->mnt.mnt_flags & MNT_UMOUNT)) {
Review
Quoted 10 lines
(quoted diff) +10
-0+ child->mnt.mnt_flags |= MNT_UNREACHABLE_PARENT; + hlist_add_head(&child->mnt_pending_umount, &pending_umount); + schedule_work(&pending_umount_work); + } else { + umount_mnt(child); + } + } + } +out: + unlock_mount_hash();
And that can have non-trivial security implications - ability to expose something that has been overmounted is potentially very nasty. I might be missing something in the rest of the series (I'm half-asleep right now, so that's certainly possible), but that doesn't look obviously safe. Note that it's very different from the situation with umount-on-invalidation - there the thing we are uncovering is dead. In this one it might be very much alive and deliberately covered...
Al Viro
maintainerRe: [PATCH review 19/19] vfs: Do not allow escaping from bind mounts.
In reply to Eric W. BiedermanQuoted 5 lines · Eric W. Biederman
fs/dcache.c +5
-0+static void mark_violated_mounts (struct dentry *dentry,struct dentry *target) +{ +/* Mark all mountroots that are ancestors of dentry +* that do not share a common ancestor with target +*
fs/dcache.c static void dentry_unlock_for_move(struct dentry *dentry, struct dentry *target) · line 2552
Quoted 10 lines
fs/dcache.c +10
-0+* This function assumes both dentries are part of a DAG. +*/ +struct dentry *p; + +for (p = dentry->d_parent; !IS_ROOT (p); p = p->d_parent) { +if (!d_mountroot (p)) +continue ; + +if (d_ancestor (p, target)) +break ;
Egads... You do realize that you'll keep walking the path from target to root again and again? It's a tree and we have already walked up to the root. So we know the depths and tree topology can't change due to the rename_lock being held by caller.
Quoted 32 lines
(quoted diff) +30
-0+ if (!IS_ROOT(dentry) && !IS_ROOT(target)) { + mark_violated_mounts(dentry, target); + mark_violated_mounts(target, dentry); + } + dentry_lock_for_move(dentry, target); +void mnt_set_violated(struct dentry *root, struct dentry *moving) +{ + struct mountroot *mr; + struct mount *mnt; + + lock_mount_hash(); + mr = lookup_mountroot(root); + if (!mr) + goto out; + + hlist_for_each_entry(mnt, &mr->r_list, mnt_mr_list) { + struct mount *child; + /* Be wary of this mount */ + mnt->mnt.mnt_flags |= MNT_VIOLATED; + + list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) { + /* Ignore children that will continue to be connected */ + if ((child->mnt_mountpoint != moving) && + !d_ancestor(moving, child->mnt_mountpoint)) + continue; + + /* Deal with mounts loosing the connection to + * their parents + */ + if (!(child->mnt.mnt_flags & MNT_UMOUNT)) {
Review
Quoted 10 lines
(quoted diff) +10
-0+ child->mnt.mnt_flags |= MNT_UNREACHABLE_PARENT; + hlist_add_head(&child->mnt_pending_umount, &pending_umount); + schedule_work(&pending_umount_work); + } else { + umount_mnt(child); + } + } + } +out: + unlock_mount_hash();
And that can have non-trivial security implications - ability to expose something that has been overmounted is potentially very nasty. I might be missing something in the rest of the series (I'm half-asleep right now, so that's certainly possible), but that doesn't look obviously safe. Note that it's very different from the situation with umount-on-invalidation - there the thing we are uncovering is dead. In this one it might be very much alive and deliberately covered...
A message that is not in the archive (sent off-list or not yet received): 20150403062035.GF889-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 19/19] vfs: Do not allow escaping from bind mounts.
Quoted 18 lines · Al Viro
Quoted 1 line · Eric W. Biederman
+static void mark_violated_mounts(struct dentry *dentry, struct
dentry *target)
Quoted 4 lines
fs/dcache.c +4
-0+{ +/* Mark all mountroots that are ancestors of dentry +* that do not share a common ancestor with target +* fs/dcache.c static void dentry_unlock_for_move(struct dentry *dentry, struct dentry *target) · line 2552
Quoted 10 lines
fs/dcache.c +10
-0+* This function assumes both dentries are part of a DAG. +*/ +struct dentry *p; + +for (p = dentry->d_parent; !IS_ROOT (p); p = p->d_parent) { +if (!d_mountroot (p)) +continue ; + +if (d_ancestor (p, target)) +break ;
Quoted 7 lines
Egads... You do realize that you'll keep walking the path from target to
root again and again? It's a tree and we have already walked up to the root.
So we know the depths and tree topology can't change due to therename_lock
being held by caller.
In the common case when a mount is not violated we will either not encounter a mount root. Or the mount root will be a common ancestor and so we will break out of the loop.
If we can make the pathological cases perform better I am all for it. But I did not see anything immediately obvious.
Quoted 45 lines
Quoted 32 lines
(quoted diff) +30
-0+ if (!IS_ROOT(dentry) && !IS_ROOT(target)) { + mark_violated_mounts(dentry, target); + mark_violated_mounts(target, dentry); + } + dentry_lock_for_move(dentry, target); +void mnt_set_violated(struct dentry *root, struct dentry *moving) +{ + struct mountroot *mr; + struct mount *mnt; + + lock_mount_hash(); + mr = lookup_mountroot(root); + if (!mr) + goto out; + + hlist_for_each_entry(mnt, &mr->r_list, mnt_mr_list) { + struct mount *child; + /* Be wary of this mount */ + mnt->mnt.mnt_flags |= MNT_VIOLATED; + + list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) { + /* Ignore children that will continue to be connected */ + if ((child->mnt_mountpoint != moving) && + !d_ancestor(moving, child->mnt_mountpoint)) + continue; + + /* Deal with mounts loosing the connection to + * their parents + */ + if (!(child->mnt.mnt_flags & MNT_UMOUNT)) {Review
Quoted 10 lines
(quoted diff) +10
-0+ child->mnt.mnt_flags |= MNT_UNREACHABLE_PARENT; + hlist_add_head(&child->mnt_pending_umount, &pending_umount); + schedule_work(&pending_umount_work); + } else { + umount_mnt(child); + } + } + } +out: + unlock_mount_hash();And that can have non-trivial security implications - ability to expose something that has been overmounted is potentially very nasty. I might
Quoted 8 lines
be missing something in the rest of the series (I'm half-asleep right now,
so that's certainly possible), but that doesn't look obviously safe. Note that it's very different from the situation with umount-on-invalidation -
there the thing we are uncovering is dead. In this one it might be very
much alive and deliberately covered...
You are correct. I overlooked that corner case. It is hard to reach but not impossible.
It is not fundamental to the patchset that the unmount happen. So just taking out the unmount out should work.
I am wondering if we could performance some kind of weird half unmount like I do for submounts of what I am unmounting, but I do not think so.
I will have to take a look when I get back from the long weekend. At this point I expect the patches are close enough it should not be hard to iterate to a workable fix.
Eric
cover Loopback mount escape fixes
In reply to Eric W. BiedermanAfter the last round of feedback I sat down and played with my fix for the fact that a strategically placed rename, ".." on bind mounts go up past the root of the bind mount.
The code better handles the escaped directory returning into it's bind mount, and is now roughly a constant factor cost in all cases from what the code costs without the fix.
So I think I have found a better tradeoff between fixing this bug and not slowing down path name lookups in the common case.
These fixes are against on v4.0-rc6.
For those who like to see everything in a single tree the code is at:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-testing
Eric W. Biederman (4):
mnt: Track which mounts use a dentry as root.
vfs: Test for and handle paths that are unreachable from their mnt_root
vfs: Handle mounts whose parents are unreachable from their mountpoint
vfs: Do not allow escaping from bind mounts.
fs/dcache.c | 82 +++++++++++++++++++++++++++--- fs/internal.h | 2 + fs/mount.h | 6 +++ fs/namei.c | 57 +++++++++++++++++---- fs/namespace.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 13 +++++ include/linux/namei.h | 2 + 7 files changed, 277 insertions(+), 20 deletions(-)
A message that is not in the archive (sent off-list or not yet received): 874moq9oyb.fsf_-_-JOvCrm2gF+uungPnsOpG7nhyD016LWXt@public.gmane.org
PATCH 1/4 mnt: Track which mounts use a dentry as root.
--- fs/mount.h | 6 +++ fs/namespace.c | 118 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 7 +++ 3 files changed, 127 insertions(+), 4 deletions(-)
fs/mount.h +6 -0
@@ -27,6 +27,12 @@ struct mountpoint {int m_count; }; +struct mountroot { +struct hlist_node r_hash; +struct dentry *r_dentry; +long r_count; +}; +struct mount {struct hlist_node mnt_hash;struct mount *mnt_parent;
fs/namespace.c +114 -4
192 lines not on this page · raw message
include/linux/dcache.h +7 -0
@@ -226,6 +226,8 @@ struct dentry_operations {#define DCACHE_MAY_FREE 0x00800000#define DCACHE_FALLTHRU 0x01000000/* Fall through to lower layer */ +#define DCACHE_MOUNTROOT 0x02000000/* Root of a vfsmount */ +extern seqlock_t rename_lock;/*
@@ -401,6 +403,11 @@ static inline bool d_mountpoint(const struct dentry *dentry)return dentry->d_flags &DCACHE_MOUNTED ; } +static inline bool d_mountroot (const struct dentry *dentry) +{ +return dentry->d_flags &DCACHE_MOUNTROOT ; +} +/* * Directory cache entry type accessor functions. */
Signature · 1 line
2.2.1
PATCH 2/4 vfs: Test for and handle paths that are unreachable from their mnt_root
In reply to a message not in the archive- Add a dentry flag DCACHE_MOUNT_VIOLATED to mark loopback mounts that have had a dentry moved into a directory that does not descend from the mount root dentry.
- In mnt_put_root clear DCACHE_MOUNT_VIOLATED.
- Add a function path_connected to verify a path.dentry is reachable from path.mnt.mnt_root. AKA rename did not do something nasty to the bind mount.
- Disable ".." when a path is not connected during lookup. (Maybe we want to stop ".." at this path instead?)
Following .. is not disabled after a transition to / and is never disabled when / is the directory we start
with. Because we already limit .. no higher than /
- In prepend_path and it's callers in the d_path family for a path that is not connected don't attempt to find parent directories.
--- fs/dcache.c | 3 +++ fs/internal.h | 1 + fs/namei.c | 30 ++++++++++++++++++++++++++++++ fs/namespace.c | 2 +- include/linux/dcache.h | 6 ++++++ include/linux/namei.h | 2 ++ 6 files changed, 43 insertions(+), 1 deletion(-)
fs/dcache.c +3 -0
@@ -2871,6 +2871,9 @@ static int prepend_path(const struct path *path,char *bptr;int blen; +if (!path_connected (path)) + root = path; +rcu_read_lock (); restart_mnt:read_seqbegin_or_lock (&mount_lock, &m_seq);
fs/internal.h +1 -0
@@ -51,6 +51,7 @@ extern void __init chrdev_init(void);extern int user_path_mountpoint_at (int ,const char __user *,unsigned int ,struct path *);extern int vfs_path_lookup (struct dentry *,struct vfsmount *,const char *,unsigned int ,struct path *); +extern bool path_connected (const struct path *);/* * namespace.c
fs/namei.c +30 -0
@@ -493,6 +493,22 @@ void path_put(const struct path *path) }EXPORT_SYMBOL (path_put); +/** +* path_connected - Verify that a path->dentry is below path->mnt->mnt.mnt_root +* @path: path to verify +* +* Rename can sometimes move a file or directory outside of bind mount +* don't honor paths where this has happened. +*/ +bool path_connected (const struct path *path) +{ +struct dentry *root = path->mnt->mnt_root; +if (!d_mount_violated (root)) +return true; + +return is_subdir (path->dentry, root); +} +struct nameidata {struct path path;struct qstr last;
@@ -712,6 +728,7 @@ void nd_jump_link(struct nameidata *nd, struct path *path) nd->path = *path; nd->inode = nd->path.dentry->d_inode; nd->flags |=LOOKUP_JUMPED ; + nd->flags &= ~LOOKUP_NODOTDOT ; }void nd_set_link (struct nameidata *nd,char *path)
@@ -897,6 +914,7 @@ follow_link(struct path *link, struct nameidata *nd, void **p) nd->path = nd->root;path_get (&nd->root); nd->flags |=LOOKUP_JUMPED ; + nd->flags &= ~LOOKUP_NODOTDOT ; } nd->inode = nd->path.dentry->d_inode; error =link_path_walk (s, nd);
@@ -1161,6 +1179,7 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path, path->mnt = &mounted->mnt; path->dentry = mounted->mnt.mnt_root; nd->flags |=LOOKUP_JUMPED ; + nd->flags &= ~LOOKUP_NODOTDOT ; nd->seq =read_seqcount_begin (&path->dentry->d_seq);/* * Update the inode too. We don't need to re-check the
@@ -1176,6 +1195,10 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,static int follow_dotdot_rcu (struct nameidata *nd) {struct inode *inode = nd->inode; + +if (nd->flags &LOOKUP_NODOTDOT ) +return 0 ; +if (!nd->root.mnt)set_root_rcu (nd);
@@ -1293,6 +1316,9 @@ static void follow_mount(struct path *path)static void follow_dotdot (struct nameidata *nd) { +if (nd->flags &LOOKUP_NODOTDOT ) +return ; +if (!nd->root.mnt)set_root (nd);
@@ -1909,6 +1935,8 @@ static int path_init(int dfd, const char *name, unsigned int flags, }else {get_fs_pwd (current->fs, &nd->path); } +if (unlikely (!path_connected (&nd->path))) + nd->flags |=LOOKUP_NODOTDOT ; }else {/* Caller must check execute permissions on the starting path component */ struct fd f =fdget_raw (dfd);
@@ -1936,6 +1964,8 @@ static int path_init(int dfd, const char *name, unsigned int flags,path_get (&nd->path);fdput (f); } +if (unlikely (!path_connected (&nd->path))) + nd->flags |=LOOKUP_NODOTDOT ; } nd->inode = nd->path.dentry->d_inode;
fs/namespace.c +1 -1
@@ -850,7 +850,7 @@ static void mnt_put_root(struct mount *mnt)if (!--mr->r_count) {hlist_del (&mr->r_hash);spin_lock (&root->d_lock); - root->d_flags &= ~DCACHE_MOUNTROOT ; + root->d_flags &= ~(DCACHE_MOUNTROOT |DCACHE_MOUNT_VIOLATED );spin_unlock (&root->d_lock);kfree (mr); }
include/linux/dcache.h +6 -0
@@ -227,6 +227,7 @@ struct dentry_operations {#define DCACHE_FALLTHRU 0x01000000/* Fall through to lower layer */ #define DCACHE_MOUNTROOT 0x02000000/* Root of a vfsmount */ +#define DCACHE_MOUNT_VIOLATED 0x04000000/* Vfsmount with dentries moved out */ extern seqlock_t rename_lock;
@@ -408,6 +409,11 @@ static inline bool d_mountroot(const struct dentry *dentry)return dentry->d_flags &DCACHE_MOUNTROOT ; } +static inline bool d_mount_violated (const struct dentry *dentry) +{ +return dentry->d_flags &DCACHE_MOUNT_VIOLATED ; +} +/* * Directory cache entry type accessor functions. */
include/linux/namei.h +2 -0
@@ -45,6 +45,8 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};#define LOOKUP_ROOT 0x2000#define LOOKUP_EMPTY 0x4000 +#define LOOKUP_NODOTDOT 0x10000 +extern int user_path_at (int ,const char __user *,unsigned ,struct path *);extern int user_path_at_empty (int ,const char __user *,unsigned ,struct path *,int *empty);
Signature · 1 line
2.2.1
A message that is not in the archive (sent off-list or not yet received): 87sica8ac5.fsf_-_-JOvCrm2gF+uungPnsOpG7nhyD016LWXt@public.gmane.org
Re: [PATCH review 2/4] vfs: Test for and handle paths that are unreachable from their mnt_root
Quoted 10 lines · Eric W. Biederman
- Add a dentry flag
DCACHE_MOUNT_VIOLATEDto mark loopback mounts that have had a dentry moved into a directory that does not descend from the mount root dentry.- In
mnt_put_rootclearDCACHE_MOUNT_VIOLATED.- Add a function
path_connectedto verify a path.dentry is reachable from path.mnt.mnt_root. AKA rename did not do something nasty to the bind mount.
Quoted 6 lines
- Disable ".." when a path is not connected during lookup. (Maybe we want to stop ".." at this path instead?)
Following .. is not disabled after a transition to / and is never disabled when / is the directory we start
with. Because we already limit .. no higher than /
IDGI. Am I missing something, or you really only set that flag in the beginning of the pathwalk? At the bare minimum, you want to treat nd_jump_link() the same way, or your protection is trivially defeated by using /proc/self/cwd/$PATHNAME instead of $PATHNAME...
A message that is not in the archive (sent off-list or not yet received): 20150409231636.GW889-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 2/4] vfs: Test for and handle paths that are unreachable from their mnt_root
Quoted 14 lines · Al Viro
Quoted 13 lines · Eric W. Biederman
- Add a dentry flag
DCACHE_MOUNT_VIOLATEDto mark loopback mounts that have had a dentry moved into a directory that does not descend from the mount root dentry.- In
mnt_put_rootclearDCACHE_MOUNT_VIOLATED.- Add a function
path_connectedto verify a path.dentry is reachable from path.mnt.mnt_root. AKA rename did not do something nasty to the bind mount.- Disable ".." when a path is not connected during lookup. (Maybe we want to stop ".." at this path instead?)
Quoted 8 lines
Quoted 3 lines
Following .. is not disabled after a transition to / and is never disabled when / is the directory we start
with. Because we already limit .. no higher than /IDGI. Am I missing something, or you really only set that flag in the beginning of the pathwalk? At the bare minimum, you want to treat
nd_jump_link()the same way, or your protection is trivially defeated by using/proc/self/cwd/$PATHNAMEinstead of $PATHNAME...
nd_jump_link() is definitely an oversight. Doh!
Starting at the root or starting at mount_root of a mount point that flag is not necessary. As we can obviously walk up as far as it is possible to go on that mount.
Furthermore legitimize_mnt will fail if a problematic rename happens during the mount.
The next patch limits what follow_up and follow_nup_rcu can do.
So I have all of the normal operations covered, but I definitely need to take a second look to see if there are any additional locations like nd_jump_link where we can jump onto a path in the middle of a mount and need to test to see if it is connected.
Eric
PATCH 3/4 vfs: Handle mounts whose parents are unreachable from their mountpoint
In reply to a message not in the archive- In follup_up and follow_up_rcu don't follow up if the current mount's mountpoint can not reach the parent mount's root.
- In prepend_path and it's callers in the d_path family don't follow to the parent mount if the current mount's mountpoint can not reach the parent mount's root.
--- fs/dcache.c | 14 ++++++++++---- fs/namei.c | 27 +++++++++++++++++---------- 2 files changed, 27 insertions(+), 14 deletions(-)
fs/dcache.c +10 -4
@@ -2894,10 +2894,16 @@ restart:struct mount *parent =ACCESS_ONCE (mnt->mnt_parent);/* Global root? */ if (mnt != parent) { - dentry =ACCESS_ONCE (mnt->mnt_mountpoint); - mnt = parent; - vfsmnt = &mnt->mnt; -continue ; +struct path new = { + .dentry =ACCESS_ONCE (mnt->mnt_mountpoint), + .mnt = &parent->mnt, + }; +if (path_connected (&new)) { + mnt = parent; + dentry = new.dentry; + vfsmnt = new.mnt; +continue ; + } }/* * Filesystems needing to implement special "root names"
fs/namei.c +17 -10
@@ -935,14 +935,16 @@ static int follow_up_rcu(struct path *path) {struct mount *mnt =real_mount (path->mnt);struct mount *parent; -struct dentry *mountpoint; +struct path new; parent = mnt->mnt_parent; -if (&parent->mnt == path->mnt) +if (parent == mnt)return 0 ; - mountpoint = mnt->mnt_mountpoint; - path->dentry = mountpoint; - path->mnt = &parent->mnt; + new.dentry = mnt->mnt_mountpoint; + new.mnt = &parent->mnt; +if (!path_connected (&new)) +return 0 ; + *path = new;return 1 ; }
@@ -960,7 +962,7 @@ int follow_up(struct path *path) {struct mount *mnt =real_mount (path->mnt);struct mount *parent; -struct dentry *mountpoint; +struct path new;read_seqlock_excl (&mount_lock); parent = mnt->mnt_parent;
@@ -968,13 +970,18 @@ int follow_up(struct path *path)read_sequnlock_excl (&mount_lock);return 0 ; } -mntget (&parent->mnt); - mountpoint =dget (mnt->mnt_mountpoint); + new.dentry = mnt->mnt_mountpoint; + new.mnt = &parent->mnt; +if (!path_connected (&new)) { +read_sequnlock_excl (&mount_lock); +return 0 ; + } +mntget (new.mnt); +dget (new.dentry);read_sequnlock_excl (&mount_lock);dput (path->dentry); - path->dentry = mountpoint;mntput (path->mnt); - path->mnt = &parent->mnt; + *path = new;return 1 ; }EXPORT_SYMBOL (follow_up);
Signature · 1 line
2.2.1
PATCH 4/4 vfs: Do not allow escaping from bind mounts.
In reply to a message not in the archiveRename can move a file or directory outside of a bind mount. This has allowed programs with paths below the renamed directory to traverse up their directory tree to the real root of the filesystem instead of just the root of their bind mount.
In the presence of such renames limit applications to what the bind mount intended to reveal by marking mounts that have had dentries renamed out of them with MNT_VIOLATED, marking mounts that can no longer walk up to their parent mounts with MNT_UMOUNT_PENDING and then lazily unmounting such mounts.
All moves go through __d_move so __d_move has been modified to mark all mounts whose dentries have been moved outside of them.
Once the root dentry of a violated mount has been found a new function mnt_set_violated is called to mark all mounts that have that dentry as their root as violated.
The worst case performance of the changes to __d_move is two extra trip from dentry and target to the root of their respective dentry trees.
This closes a hole where it was possible in some circumstances to follow .. past the root of a bind mount.
--- fs/dcache.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- fs/internal.h | 1 + fs/namespace.c | 17 +++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-)
fs/dcache.c +63 -2
@@ -2535,6 +2535,56 @@ static void dentry_unlock_for_move(struct dentry *dentry, struct dentry *target)spin_unlock (&dentry->d_lock); } +static unsigned d_depth (const struct dentry *dentry) +{ +unsigned depth =0 ; + +while (!IS_ROOT (dentry)) { + dentry = dentry->d_parent; + depth++; + } +return depth; +} + +static const struct dentry *d_common_ancestor (const struct dentry *left, +const struct dentry *right) +{ +unsigned ldepth =d_depth (left); +unsigned rdepth =d_depth (right); + +if (ldepth > rdepth) { +swap (left, right); +swap (ldepth, rdepth); + } + +while (rdepth > ldepth) { + right = right->d_parent; + rdepth--; + } + +while (right != left) { +if (IS_ROOT (right)) +return NULL ; + right = right->d_parent; + left = left->d_parent; + } + +return right; +} + +static void mark_violated_mounts (struct dentry *dentry, +const struct dentry *ancestor) +{ +/* Mark all mountroots that are children of the common +* ancestor and ancestors of dentry. +*/ +struct dentry *p; +for (p = dentry->d_parent; p != ancestor; p = p->d_parent) { +if (d_mountroot (p)) +mnt_set_violated (p); + } +} +/* * When switching names, the actual string doesn't strictly have to * be preserved in the target - because we're dropping the target
@@ -2563,11 +2613,22 @@ static void dentry_unlock_for_move(struct dentry *dentry, struct dentry *target)static void __d_move (struct dentry *dentry,struct dentry *target,bool exchange) { +const struct dentry *ancestor =d_common_ancestor (dentry, target); +if (!dentry->d_inode)printk (KERN_WARNING "VFS: moving negative dcache entry\n" ); -BUG_ON (d_ancestor (dentry, target)); -BUG_ON (d_ancestor (target, dentry)); +BUG_ON (dentry == ancestor); +BUG_ON (target == ancestor); + +/* If there is a common ancestor, mark mounts which may have +* paths that are no longer able to follow d_parent up to +* mnt_root after this move. +*/ +if (ancestor) { +mark_violated_mounts (dentry, ancestor); +mark_violated_mounts (target, ancestor); + }dentry_lock_for_move (dentry, target);
fs/internal.h +1 -0
@@ -71,6 +71,7 @@ extern int __mnt_want_write_file(struct file *);extern void __mnt_drop_write (struct vfsmount *);extern void __mnt_drop_write_file (struct file *); +extern void mnt_set_violated (struct dentry *root);/* * fs_struct.c */
fs/namespace.c +17 -0
@@ -1644,6 +1644,23 @@ out_unlock:namespace_unlock (); } +void mnt_set_violated (struct dentry *root) +{ +struct mountroot *mr; + +/* Locking the mount guarantees that root is a mountpoint and +* pushes rcu path walkers onto the slow path. +*/ +lock_mount_hash (); + mr =lookup_mountroot (root); +if (mr) { +spin_lock (&root->d_lock); + root->d_flags |=DCACHE_MOUNT_VIOLATED ; +spin_unlock (&root->d_lock); + } +unlock_mount_hash (); +} +/* * Is the caller allowed to modify his namespace? */
Signature · 1 line
2.2.1
Re: [PATCH review 0/4] Loopback mount escape fixes
In reply to a message not in the archiveAl. Do you want involvement in any of these patches?
If not I will move them in the direction of linux-next and Linus. I expect they are just interesting enough that I don't want to send them as bug fixes during rc-late.
The feedback from the review I have recevied has been incorporated into:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-testing
While I have energy I would like to push these things and get these issues fixed.
Eric
Quoted 31 lines · ebiederm-aS9lmoZGLiVWk0Htik3J/[email protected] (Eric W. Biederman) writes:
After the last round of feedback I sat down and played with my fix for the fact that a strategically placed rename, ".." on bind mounts go up past the root of the bind mount.
The code better handles the escaped directory returning into it's bind mount, and is now roughly a constant factor cost in all cases from what the code costs without the fix.
So I think I have found a better tradeoff between fixing this bug and not slowing down path name lookups in the common case.
These fixes are against on v4.0-rc6.
For those who like to see everything in a single tree the code is at:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.gitfor-testingEric W. Biederman (4):
mnt: Track which mounts use a dentry as root.
vfs: Test for and handle paths that are unreachable from theirmnt_root
vfs: Handle mounts whose parents are unreachable from their mountpoint
vfs: Do not allow escaping from bind mounts.fs/dcache.c | 82 +++++++++++++++++++++++++++--- fs/internal.h | 2 + fs/mount.h | 6 +++ fs/namei.c | 57 +++++++++++++++++---- fs/namespace.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 13 +++++ include/linux/namei.h | 2 + 7 files changed, 277 insertions(+), 20 deletions(-)
A message that is not in the archive (sent off-list or not yet received): [email protected]
Re: [PATCH review 0/4] Loopback mount escape fixes
Quoted 6 lines · Eric W. Biederman
Al. Do you want involvement in any of these patches?
If not I will move them in the direction of linux-next and Linus. I expect they are just interesting enough that I don't want to send them as bug fixes during rc-late.
I'll post review in a few hours (in the middle of nasty bisect right now)
Al Viro
maintainerRe: [PATCH review 0/4] Loopback mount escape fixes
In reply to Eric W. BiedermanQuoted 6 lines · Eric W. Biederman
Al. Do you want involvement in any of these patches?
If not I will move them in the direction of linux-next and Linus. I expect they are just interesting enough that I don't want to send them as bug fixes during rc-late.
I'll post review in a few hours (in the middle of nasty bisect right now)
A message that is not in the archive (sent off-list or not yet received): 20150409191232.GV889-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 0/4] Loopback mount escape fixes
Quoted 9 lines · Al Viro
Quoted 6 lines · Eric W. Biederman
Al. Do you want involvement in any of these patches?
If not I will move them in the direction of linux-next and Linus. I expect they are just interesting enough that I don't want to send them as bug fixes during rc-late.
I'll post review in a few hours (in the middle of nasty bisect right now)
Sounds good.
Thank you very much.
Eric
Re: [PATCH review 0/4] Loopback mount escape fixes
In reply to Al ViroQuoted 9 lines · Al Viro
Quoted 6 lines · Eric W. Biederman
Al. Do you want involvement in any of these patches?
If not I will move them in the direction of linux-next and Linus. I expect they are just interesting enough that I don't want to send them as bug fixes during rc-late.
I'll post review in a few hours (in the middle of nasty bisect right now)
Sounds good.
Thank you very much.
Eric
Re: [PATCH review 0/4] Loopback mount escape fixes
In reply to a message not in the archiveQuoted 11 lines · Eric W. Biederman
After the last round of feedback I sat down and played with my fix for the fact that a strategically placed rename, ".." on bind mounts go up past the root of the bind mount.
The code better handles the escaped directory returning into it's bind mount, and is now roughly a constant factor cost in all cases from what the code costs without the fix.
So I think I have found a better tradeoff between fixing this bug and not slowing down path name lookups in the common case.
Maybe I'm missing something, but I see a much simpler fix:
- When following ".." first just check against the dentry being equal
to the root dentry.
- If so, then check mount being equal to root mount.
- If so, then we are fine, found the root.
- If mount is not root mount, then we either have a bind mount or the
escape scenario. So have a peek at the mount tree to see if we have a chance of reaching root or not.
- If yes, then we are fine, continue upward.
- Otherwise stop here and act like we found root.
This doesn't have to hook into d_move() and will only trigger the "violated" mode on an very specific and rare case.
I haven't thought about this very hard, but I don't see how the root dentry could be avoided without first having access to something outside the root.
Thanks,
Miklos
cover Bind mount escape fixes
In reply to a message not in the archiveIt is possible in some situations to rename a file or directory through one mount point such that it can start out inside of a bind mount and after the rename wind up outside of the bind mount. Unfortunately with user namespaces these conditions can be trivially created by creating a bind mount under an existing bind mount.
I have identified four situations in which this may be a problem.
- __d_path and d_absolute_path need to error on disconnected paths that can not reach some root directory or lsm path based security checks can incorrectly succeed.
- Normal path name resolution following .. can lead to a directory that is outside of the original loopback mount.
- file handle reconsititution aka exportfs_decode_fh can yield a dentry from which d_parent can be followed up to mnt->sb->s_root, but d_parent can not be followed up to mnt->mnt_root.
- Mounts on a path that has been renamed outside of a loopback mount become unreachable, as there is no possible path that can be passed to umount to unmount them.
My strategy:
o File handle reconsitituion problems can be prevented by enabling the nfsd subtree checks for nfs exports, and open_by_handle_at requires capable(CAP_DAC_READ_SEARCH) so is only usable by the global root. This makes any problems difficult if not impossible to exploit in practice so I have not yet written code to address that issue.
o The functions __d_path and d_absolute_path are agumented so that the security modules will not be fed a problematic path to work with.
o Following of .. has been agumented to test that after d_parent has been resolved the original directory is connected, and if not an error of -ENOENT is returned.
o I do not worry about mounts that are disconnected from their bind mount as these mounts can always be freed by either umount -l on the bind mount they have escaped from, or by freeing the mount namespace. So I do not believe there is an actual problem.
That name resolution is a common fast path and most of the code in this patchset is to support keeping following .. from becoming quadratic as far as is humanly possible.
For the implementation I went back to the drawing board and carefully read through the affected code, so I could be certain I knew what was going on, and this wound of with some very significant implementation changes from a correctness point of view.
On each mount I keep an escape count which is almost but not quite a seqcount that is bumped each time a directory escapes a mount point. This allows marking the mounts that do have directories escape and allows caching of when a path has been verified to have no escapes, so in the common case even a mount that has had a directory escape will see only a single call to d_ancestor during path name resolution the first time .. is encountered.
I have not benchmarked the code but I don't see any reason to expect anything except for rename will see a performance impact, and then only in cases with where a rename potentially affects allows a directory to escape lots of mounts.
Do I have something that is good enough this time, or am I blind and missing something?
These changes are all against v4.2-rc4.
For those who like to see everything in a single tree the code is at:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-testing
Eric W. Biederman (6):
mnt: Track which mounts use a dentry as root.
dcache: Handle escaped paths in prepend_path
dcache: Implement d_common_ancestor
mnt: Track when a directory escapes a bind mount
vfs: Test for and handle paths that are unreachable from their mnt_root
vfs: Cache the results of path_connected
fs/dcache.c | 90 ++++++++++++++++-- fs/mount.h | 25 +++++ fs/namei.c | 59 +++++++++++- fs/namespace.c | 243 ++++++++++++++++++++++++++++++++++++++++++++++++- include/linux/dcache.h | 8 ++ 5 files changed, 409 insertions(+), 16 deletions(-)
A message that is not in the archive (sent off-list or not yet received): 871tfkawu9.fsf_-_-JOvCrm2gF+uungPnsOpG7nhyD016LWXt@public.gmane.org
PATCH 1/6 mnt: Track which mounts use a dentry as root.
This is needed infrastructure for better handling of when files or directories are moved out from under the root of a bind mount.
--- fs/mount.h | 7 +++ fs/namespace.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 7 +++ 3 files changed, 130 insertions(+), 4 deletions(-)
fs/mount.h +7 -0
@@ -27,6 +27,12 @@ struct mountpoint {int m_count; }; +struct mountroot { +struct hlist_node r_hash; +struct dentry *r_dentry; +struct hlist_head r_list; +}; +struct mount {struct hlist_node mnt_hash;struct mount *mnt_parent;
@@ -55,6 +61,7 @@ struct mount {struct mnt_namespace *mnt_ns;/* containing namespace */ struct mountpoint *mnt_mp;/* where is it mounted */ struct hlist_node mnt_mp_list;/* list mounts with the same mountpoint */ +struct hlist_node mnt_mr_list;/* list mounts with the same mountroot */ #ifdef CONFIG_FSNOTIFY struct hlist_head mnt_fsnotify_marks;__u32 mnt_fsnotify_mask;
fs/namespace.c +116 -4
200 lines not on this page · raw message
include/linux/dcache.h +7 -0
@@ -228,6 +228,8 @@ struct dentry_operations {#define DCACHE_FALLTHRU 0x01000000/* Fall through to lower layer */ #define DCACHE_OP_SELECT_INODE 0x02000000/* Unioned entry: dcache op selects inode */ +#define DCACHE_MOUNTROOT 0x04000000/* Root of a vfsmount */ +extern seqlock_t rename_lock;/*
@@ -404,6 +406,11 @@ static inline bool d_mountpoint(const struct dentry *dentry)return dentry->d_flags &DCACHE_MOUNTED ; } +static inline bool d_mountroot (const struct dentry *dentry) +{ +return dentry->d_flags &DCACHE_MOUNTROOT ; +} +/* * Directory cache entry type accessor functions. */
Signature · 1 line
2.2.1
PATCH 2/6 dcache: Handle escaped paths in prepend_path
In reply to a message not in the archiveA rename can result in a dentry that by walking up d_parent will never reach it's mnt_root. For lack of a better term I call this an escaped path.
prepend_path is called by four different functions __d_path, d_absolute_path, d_path, and getcwd.
__d_path only wants to see paths are connected to the root it passes in. So __d_path needs prepend_path to return an error.
d_absolute_path similarly wants to see paths that are connected to some root. Escaped paths are not connected to any mnt_root so d_absolute_path needs prepend_path to return an error greater than 1. So escaped paths will be treated like paths on lazily unmounted mounts.
getcwd needs to prepend "(unreachable)" so getcwd also needs prepend_path to return an error.
d_path is the interesting hold out. d_path just wants to print something, and does not care about the weird cases. Which raises the question what should be printed?
Given that <escaped_path>/<anything> should result in -ENOENT I believe it is desirable for escaped paths to be printed as empty paths. As there are not really any meaninful path components when considered from the perspective of a mount tree.
So tweak prepend_path to return an empty path with an new error code of 3 when it encounters an escaped path.
--- fs/dcache.c | 7 +++++++ 1 file changed, 7 insertions(+)
fs/dcache.c +7 -0
@@ -2926,6 +2926,13 @@ restart:if (dentry == vfsmnt->mnt_root ||IS_ROOT (dentry)) {struct mount *parent =ACCESS_ONCE (mnt->mnt_parent); +/* Escaped? */ +if (dentry != vfsmnt->mnt_root) { + bptr = *buffer; + blen = *buflen; + error =3 ; +break ; + }/* Global root? */ if (mnt != parent) { dentry =ACCESS_ONCE (mnt->mnt_mountpoint);
Signature · 1 line
2.2.1
PATCH 3/6 dcache: Implement d_common_ancestor
In reply to a message not in the archiveIf possible find the common ancestor of two dentries.
This is necessary infrastructure for better handling the case when a dentry is moved out from under the root of a bind mount.
--- fs/dcache.c | 37 +++++++++++++++++++++++++++++++++++++ include/linux/dcache.h | 1 + 2 files changed, 38 insertions(+)
fs/dcache.c +37 -0
@@ -2472,6 +2472,43 @@ void dentry_update_name_case(struct dentry *dentry, struct qstr *name) }EXPORT_SYMBOL (dentry_update_name_case); +static unsigned long d_depth (const struct dentry *dentry) +{ +unsigned long depth =0 ; + +while (!IS_ROOT (dentry)) { + dentry = dentry->d_parent; + depth++; + } +return depth; +} + +const struct dentry *d_common_ancestor (const struct dentry *left, +const struct dentry *right) +{ +unsigned long ldepth =d_depth (left); +unsigned long rdepth =d_depth (right); + +while (ldepth > rdepth) { + left = left->d_parent; + ldepth--; + } + +while (rdepth > ldepth) { + right = right->d_parent; + rdepth--; + } + +while (left != right) { +if (IS_ROOT (left)) +return NULL ; + left = left->d_parent; + right = right->d_parent; + } + +return left; +} +static void swap_names (struct dentry *dentry,struct dentry *target) {if (unlikely (dname_external (target))) {
include/linux/dcache.h +1 -0
@@ -313,6 +313,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *);extern void d_move (struct dentry *,struct dentry *);extern void d_exchange (struct dentry *,struct dentry *);extern struct dentry *d_ancestor (struct dentry *,struct dentry *); +extern const struct dentry *d_common_ancestor (const struct dentry *,const struct dentry *);/* appendix may either be NULL or be used for transname suffixes */ extern struct dentry *d_lookup (const struct dentry *,const struct qstr *);
Signature · 1 line
2.2.1
PATCH 4/6 mnt: Track when a directory escapes a bind mount
In reply to a message not in the archiveWhen bind mounts are in use, and there is another path to the filesystem it is possible to rename files or directories from a path underneath the root of the bind mount to a path that is not underneath the root of the bind mount.
When a directory is moved out from under the root of a bind mount path name lookups that go up the directory tree potentially allow accessing the entire dentry tree of the filesystem. This is not expected, not what is desired and winds up being a secruity problem for userspace.
Augment d_move, d_exchange, and __d_unalias with matching calls to lock_namespace_rename and unlock_namespace_rename, to mark mounts that directories have escaped from.
A few notes on the implementation:
- The escape count on struct mount must be incremented both before the rename and after. If the count is not incremented before the rename it is possible to hit a scenario where the rename happens the code walks up the directory tree to somewhere outside of the bind mount before the count is touched. Similary without a count after the rename it is possible for the code to look at the escape count validate a path is connected before the rename and assume cache the escape count, leading to not retesting the path is ok.
- A lock either namespace_sem or mount_lock needs to be held across the duration of renames where a directory could be escaping to guarantee pairing of the escape_count increments and to ensure that a mount is not added, escaped, and missed during the rename.
- The locking order must be mount_lock outside of rename_lock as prepend_path already takes the locks in this order.
- I have audited all callers of d_move and d_exchange and in every instance it appears safe for d_move and d_exchange to start sleeping. d_splice_alias already sleeps in security_d_instantiate so no audit was needed for it to begin sleeping.
As I can just take mount_lock I don't use that freedom in this change, but it can be relevant to small changes to the locking in this code.
- The largest change is in d_unalias, where the two cases are split apart so they can be handled separately. In the easy case of a rename within the same directory all that is needed is __d_move (escaping a mount is impossible in that case). In the more involved case mutexes need to be acquired, and now the spin locks need to be dropped so that proper lock aquisition order around __d_move can be arranged.
As I read the code inode->i_lock needs to be held until ailas->d_parent->d_parent->i_mutex is taken. The only case I can see that removes an inode from a dentry is d_delete called from a path like vfs_unlink. Those paths all take the parent directories inode mutex. Thus once the parent directories inode mutex is held it becomes unnecessary to hold inode->i_lock to ensure the alias remains an alias.
Similarly the rename_lock does not need to be held once the s_vfs_rename_mutex is taken.
--- fs/dcache.c | 46 +++++++++++++++++---- fs/mount.h | 18 +++++++++ fs/namespace.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 7 deletions(-)
fs/dcache.c +39 -7
@@ -2707,9 +2707,17 @@ static void __d_move(struct dentry *dentry, struct dentry *target,*/ void d_move (struct dentry *dentry,struct dentry *target) { +const struct dentry *unlock; + + unlock =lock_namespace_rename (dentry, target, false); +write_seqlock (&rename_lock);__d_move (dentry, target, false);write_sequnlock (&rename_lock); + +if (unlock) +unlock_namespace_rename (unlock, dentry, target, false); + }EXPORT_SYMBOL (d_move);
@@ -2720,6 +2728,10 @@ EXPORT_SYMBOL(d_move);*/ void d_exchange (struct dentry *dentry1,struct dentry *dentry2) { +const struct dentry *unlock; + + unlock =lock_namespace_rename (dentry1, dentry2, true); +write_seqlock (&rename_lock);WARN_ON (!dentry1->d_inode);
@@ -2730,6 +2742,9 @@ void d_exchange(struct dentry *dentry1, struct dentry *dentry2)__d_move (dentry1, dentry2, true);write_sequnlock (&rename_lock); + +if (unlock) +unlock_namespace_rename (unlock, dentry1, dentry2, true); }/**
@@ -2764,11 +2779,15 @@ static int __d_unalias(struct inode *inode, struct dentry *dentry, struct dentry *alias) { struct mutex *m1 = NULL, *m2 = NULL; -int ret = -ESTALE ; +const struct dentry *unlock;/* If alias and dentry share a parent, then no extra locks required */ -if (alias->d_parent == dentry->d_parent) - goto out_unalias; +if (alias->d_parent == dentry->d_parent) { +__d_move (alias, dentry, false); +spin_unlock (&inode->i_lock); +write_sequnlock (&rename_lock); +return 0 ; + }/* See lock_rename() */ if (!mutex_trylock (&dentry->d_sb->s_vfs_rename_mutex))
@@ -2777,16 +2796,30 @@ static int __d_unalias(struct inode *inode,if (!mutex_trylock (&alias->d_parent->d_inode->i_mutex)) goto out_err; m2 = &alias->d_parent->d_inode->i_mutex; -out_unalias: + +spin_unlock (&inode->i_lock); +write_sequnlock (&rename_lock); + + unlock =lock_namespace_rename (alias, dentry, false); + +write_seqlock (&rename_lock);__d_move (alias, dentry, false); - ret =0 ; +write_sequnlock (&rename_lock); + +if (unlock) +unlock_namespace_rename (unlock, alias, dentry, false); + +mutex_unlock (m2); +mutex_unlock (m1); +return 0 ; out_err:spin_unlock (&inode->i_lock); +write_sequnlock (&rename_lock);if (m2)mutex_unlock (m2);if (m1)mutex_unlock (m1); -return ret; +return -ESTALE ; }/**
@@ -2841,7 +2874,6 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry) inode->i_sb->s_id); }else if (!IS_ROOT (new)) {int err =__d_unalias (inode, dentry, new); -write_sequnlock (&rename_lock);if (err) {dput (new); new =ERR_PTR (err);
fs/mount.h +18 -0
@@ -38,6 +38,7 @@ struct mount {struct mount *mnt_parent;struct dentry *mnt_mountpoint;struct vfsmount mnt; +unsigned mnt_escape_count;union {struct rcu_head mnt_rcu;struct llist_node mnt_llist;
@@ -107,6 +108,23 @@ static inline void detach_mounts(struct dentry *dentry)__detach_mounts (dentry); } +extern const struct dentry *lock_namespace_rename (struct dentry *,struct dentry *,bool ); +extern void unlock_namespace_rename (const struct dentry *,struct dentry *,struct dentry *,bool ); + +static inline unsigned read_mnt_escape_count (struct vfsmount *vfsmount) +{ +struct mount *mnt =real_mount (vfsmount); +unsigned ret =READ_ONCE (mnt->mnt_escape_count); +smp_rmb (); +return ret; +} + +static inline void cache_mnt_escape_count (unsigned *cache,unsigned escape_count) +{ +if (likely (escape_count &1 ) ==0 ) + *cache = escape_count; +} +static inline void get_mnt_ns (struct mnt_namespace *ns) {atomic_inc (&ns->count);
fs/namespace.c +123 -0
129 lines not on this page · raw message
Signature · 1 line
2.2.1
PATCH 5/6 vfs: Test for and handle paths that are unreachable from their mnt_root
In reply to a message not in the archiveIn rare cases a directory can be renamed out from under a bind mount. In those cases without special handling it becomes possible to walk up the directory tree to the root dentry of the filesystem and down from the root dentry to every other file or directory on the filesystem.
Like division by zero .. from an unconnected path can not be given a useful semantic as there is no predicting at which path component the code will realize it is unconnected. We certainly can not match the current behavior as the current behavior is a security hole.
Therefore when encounting .. when following an unconnected path return -ENOENT.
- Add a function path_connected to verify nd->path.dentry is reachable from nd->path.mnt.mnt_root. AKA to validate that rename did not do something nasty to the bind mount.
To avoid races path_connected must be called after following a path component to it's next path component.
--- fs/namei.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-)
fs/namei.c +32 -3
@@ -560,6 +560,27 @@ static int __nd_alloc_stack(struct nameidata *nd)return 0 ; } +/** +* path_connected - Verify that a nd->path.dentry is below nd->path.mnt->mnt.mnt_root +* @nd: nameidate to verify +* +* Rename can sometimes move a file or directory outside of a bind +* mount, path_connected allows those cases to be detected. +*/ +static bool path_connected (struct nameidata *nd) +{ +struct vfsmount *mnt = nd->path.mnt; +unsigned escape_count =read_mnt_escape_count (mnt); + +if (likely (escape_count ==0 )) +return true; + +if (!is_subdir (nd->path.dentry, mnt->mnt_root)) +return false; + +return true; +} +static inline int nd_alloc_stack (struct nameidata *nd) {if (likely (nd->depth !=EMBEDDED_LEVELS ))
@@ -1294,6 +1315,8 @@ static int follow_dotdot_rcu(struct nameidata *nd) seq =read_seqcount_begin (&parent->d_seq);if (unlikely (read_seqcount_retry (&old->d_seq, nd->seq)))return -ECHILD ; +if (unlikely (!path_connected (nd))) +return -ENOENT ; nd->path.dentry = parent; nd->seq = seq;break ;
@@ -1396,7 +1419,7 @@ static void follow_mount(struct path *path) } } -static void follow_dotdot (struct nameidata *nd) +static int follow_dotdot (struct nameidata *nd) {if (!nd->root.mnt)set_root (nd);
@@ -1410,7 +1433,12 @@ static void follow_dotdot(struct nameidata *nd) }if (nd->path.dentry != nd->path.mnt->mnt_root) {/* rare case of legitimate dget_parent()... */ - nd->path.dentry =dget_parent (nd->path.dentry); +struct dentry *parent =dget_parent (nd->path.dentry); +if (unlikely (!path_connected (nd))) { +dput (parent); +return -ENOENT ; + } + nd->path.dentry = parent;dput (old);break ; }
@@ -1419,6 +1447,7 @@ static void follow_dotdot(struct nameidata *nd) }follow_mount (&nd->path); nd->inode = nd->path.dentry->d_inode; +return 0 ; }/*
@@ -1634,7 +1663,7 @@ static inline int handle_dots(struct nameidata *nd, int type)if (nd->flags &LOOKUP_RCU ) {return follow_dotdot_rcu (nd); }else -follow_dotdot (nd); +return follow_dotdot (nd); }return 0 ; }
Signature · 1 line
2.2.1
PATCH 6/6 vfs: Cache the results of path_connected
In reply to a message not in the archiveAdd a new field mnt_escape_count in nameidata, initialize it to 0 and cache the value of read_mnt_escape_count in nd->mnt_escape_count.
This allows a single check in path_connected in the common case where either the mount has had no escapes (mnt_escape_count == 0) or there has been an escape and it has been validated that the current path does not escape.
To keep the cache valid nd->mnt_escape_count must be set to 0 whenever the nd->path.mnt changes or when nd->path.dentry changes such that the connectedness of the previous value of nd->path.dentry does not imply the connected of the new value of nd->path.dentry.
Various locations in fs/namei.c are updated to set nd->mnt_escape_count to 0 as necessary.
--- fs/namei.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-)
fs/namei.c +23 -3
@@ -514,6 +514,7 @@ struct nameidata {struct nameidata *saved;unsigned root_seq;int dfd; +unsigned mnt_escape_count; };static void set_nameidata (struct nameidata *p,int dfd,struct filename *name)
@@ -572,12 +573,13 @@ static bool path_connected(struct nameidata *nd)struct vfsmount *mnt = nd->path.mnt;unsigned escape_count =read_mnt_escape_count (mnt); -if (likely (escape_count ==0 )) +if (likely (escape_count == nd->mnt_escape_count))return true;if (!is_subdir (nd->path.dentry, mnt->mnt_root))return false; +cache_mnt_escape_count (&nd->mnt_escape_count, escape_count);return true; }
@@ -840,6 +842,9 @@ static inline void path_to_nameidata(const struct path *path,if (nd->path.mnt != path->mnt)mntput (nd->path.mnt); } +if (unlikely ((nd->path.mnt != path->mnt) || + (nd->path.dentry != path->dentry->d_parent))) + nd->mnt_escape_count =0 ; nd->path.mnt = path->mnt; nd->path.dentry = path->dentry; }
@@ -856,6 +861,7 @@ void nd_jump_link(struct path *path) nd->path = *path; nd->inode = nd->path.dentry->d_inode; nd->flags |=LOOKUP_JUMPED ; + nd->mnt_escape_count =0 ; }static inline void put_link (struct nameidata *nd)
@@ -1040,6 +1046,7 @@ const char *get_link(struct nameidata *nd) nd->inode = nd->path.dentry->d_inode; } nd->flags |=LOOKUP_JUMPED ; + nd->mnt_escape_count =0 ;while (unlikely (*++res =='/' )) ; }
@@ -1335,6 +1342,7 @@ static int follow_dotdot_rcu(struct nameidata *nd) nd->path.mnt = &mparent->mnt; inode = inode2; nd->seq = seq; + nd->mnt_escape_count =0 ; } }while (unlikely (d_mountpoint (nd->path.dentry))) {
@@ -1348,6 +1356,7 @@ static int follow_dotdot_rcu(struct nameidata *nd) nd->path.dentry = mounted->mnt.mnt_root; inode = nd->path.dentry->d_inode; nd->seq =read_seqcount_begin (&nd->path.dentry->d_seq); + nd->mnt_escape_count =0 ; } nd->inode = inode;return 0 ;
@@ -1406,8 +1415,9 @@ EXPORT_SYMBOL(follow_down);/* * Skip to top of mountpoint pile in refwalk mode for follow_dotdot() */ -static void follow_mount (struct path *path) +static bool follow_mount (struct path *path) { +bool followed = false;while (d_mountpoint (path->dentry)) {struct vfsmount *mounted =lookup_mnt (path);if (!mounted)
@@ -1416,7 +1426,9 @@ static void follow_mount(struct path *path)mntput (path->mnt); path->mnt = mounted; path->dentry =dget (mounted->mnt_root); + followed = true; } +return followed; }static int follow_dotdot (struct nameidata *nd)
@@ -1444,8 +1456,10 @@ static int follow_dotdot(struct nameidata *nd) }if (!follow_up (&nd->path))break ; + nd->mnt_escape_count =0 ; } -follow_mount (&nd->path); +if (follow_mount (&nd->path)) + nd->mnt_escape_count =0 ; nd->inode = nd->path.dentry->d_inode;return 0 ; }
@@ -1997,6 +2011,7 @@ static const char *path_init(struct nameidata *nd, unsigned flags) nd->flags = flags |LOOKUP_JUMPED |LOOKUP_PARENT ; nd->depth =0 ; nd->total_link_count =0 ; + nd->mnt_escape_count =0 ;if (flags &LOOKUP_ROOT ) {struct dentry *root = nd->root.dentry;struct inode *inode = root->d_inode;
@@ -3026,6 +3041,7 @@ static int do_last(struct nameidata *nd,unsigned seq;struct inode *inode;struct path save_parent = { .dentry =NULL , .mnt =NULL }; +unsigned save_parent_escape_count =0 ;struct path path;bool retried = false;int error;
@@ -3155,6 +3171,9 @@ finish_lookup: }else { save_parent.dentry = nd->path.dentry; save_parent.mnt =mntget (path.mnt); + save_parent_escape_count = nd->mnt_escape_count; +if (nd->path.dentry != path.dentry->d_parent) + nd->mnt_escape_count =0 ; nd->path.dentry = path.dentry; }
@@ -3227,6 +3246,7 @@ stale_open:BUG_ON (save_parent.dentry != dir);path_put (&nd->path); + nd->mnt_escape_count = save_parent_escape_count; nd->path = save_parent; nd->inode = dir->d_inode; save_parent.mnt =NULL ;
Signature · 1 line
2.2.1
[PATCH review 7/6] vfs: Make mnt_escape_count 64bit
In reply to a message not in the archiveThe primary way that mnt_escape_count differs from a seqcount is that it's value is cached across operations that sleep. In emperical testing mnt_escape_count can be made to rollover in 64 minutes on an Intel 2.5Ghz Core i5 processor on ramfs. Meanwhile a single pathname lookup on an otherwise idle system has been measured at 2 minutes 9 seconds. Those numbers are entirely too close for comfort, especially given that nfs lookups can take indefinitely long.
Extend mnt_escape_count to 64bit to increase the expected time to rollover from 1 hour to 489,957 years. Even if the efficiency of rename is increased to be able to rename 2^31 entries in 1 second (instead of the 1 hour that I measured) it will still take 136 years before the escape count rolls over, making it essentially never.
On 32bit the low 32bit word of the 64bit count is treated as a sequence count such that if you read the low 32bit value, read the high 32bit value, and then read the low 32bit value again and the low 32bit value remains unchanged the high 32bit value is guaranteed to be stable when the low 32bit value does not equal -1UL. Thankfully in the unlikely event that the low 32bit value is -1UL the code does not care about the high 32bit value so the code does not need to reread the values in that case.
--- fs/mount.h | 32 ++++++++++++++++++++++++++++---- fs/namei.c | 6 +++--- fs/namespace.c | 13 ++++++++++++- 3 files changed, 43 insertions(+), 8 deletions(-)
fs/mount.h +28 -4
@@ -38,7 +38,10 @@ struct mount {struct mount *mnt_parent;struct dentry *mnt_mountpoint;struct vfsmount mnt; -unsigned mnt_escape_count; +unsigned long mnt_escape_count; +#if BITS_PER_LONG <64 +unsigned long mnt_escape_count_high; +#endif union {struct rcu_head mnt_rcu;struct llist_node mnt_llist;
@@ -111,15 +114,36 @@ static inline void detach_mounts(struct dentry *dentry)extern const struct dentry *lock_namespace_rename (struct dentry *,struct dentry *,bool );extern void unlock_namespace_rename (const struct dentry *,struct dentry *,struct dentry *,bool ); -static inline unsigned read_mnt_escape_count (struct vfsmount *vfsmount) +static inline u64 read_mnt_escape_count (struct vfsmount *vfsmount) {struct mount *mnt =real_mount (vfsmount); -unsigned ret =READ_ONCE (mnt->mnt_escape_count); +#if BITS_PER_LONG >=64 +u64 ret =READ_ONCE (mnt->mnt_escape_count); +#else +u64 ret; +unsigned long low0, low, high; +/* In the unlikely event that low0 == low and low == -1 +* mnt_escape_count_high may or not be incremented yet. In +* that event the odd value of low will not match the anything +* cached, will signal that the validity of is_subdir is in +* flux and will not be cached. Therefore when low == -1 the +* value of high does not matter. +*/ + low0 =READ_ONCE (mnt->mnt_escape_count); +do { + low = low0; +smp_rmb (); + high =READ_ONCE (mnt->mnt_escape_count_high); +smp_rmb (); + low0 =READ_ONCE (mnt->mnt_escape_count); + }while (low != low0); + ret = (((u64 )high) <<32 ) | low; +#endif smp_rmb ();return ret; } -static inline void cache_mnt_escape_count (unsigned *cache,unsigned escape_count) +static inline void cache_mnt_escape_count (u64 *cache,u64 escape_count) {if (likely (escape_count &1 ) ==0 ) *cache = escape_count;
fs/namei.c +3 -3
@@ -514,7 +514,7 @@ struct nameidata {struct nameidata *saved;unsigned root_seq;int dfd; -unsigned mnt_escape_count; +u64 mnt_escape_count; };static void set_nameidata (struct nameidata *p,int dfd,struct filename *name)
@@ -571,7 +571,7 @@ static int __nd_alloc_stack(struct nameidata *nd)static bool path_connected (struct nameidata *nd) {struct vfsmount *mnt = nd->path.mnt; -unsigned escape_count =read_mnt_escape_count (mnt); +u64 escape_count =read_mnt_escape_count (mnt);if (likely (escape_count == nd->mnt_escape_count))return true;
@@ -3041,7 +3041,7 @@ static int do_last(struct nameidata *nd,unsigned seq;struct inode *inode;struct path save_parent = { .dentry =NULL , .mnt =NULL }; -unsigned save_parent_escape_count =0 ; +u64 save_parent_escape_count =0 ;struct path path;bool retried = false;int error;
fs/namespace.c +12 -1
@@ -1692,10 +1692,21 @@ static void lock_escaped_mounts_begin(struct dentry *root)*/ hlist_for_each_entry (mnt, &mr->r_list, mnt_mr_list) {/* Don't return to 0 if the couunt wraps */ -if (unlikely (mnt->mnt_escape_count == (0U -2 ))) +#if BITS_PER_LONG >=64 +if (unlikely (mnt->mnt_escape_count == (0UL -2 ))) mnt->mnt_escape_count =1 ;else mnt->mnt_escape_count++; +#else +if (unlikely (mnt->mnt_escape_count == (0UL -2 ))) { +WRITE_ONCE (mnt->mnt_escape_count, (0UL -1 )); +smp_wmb (); + mnt->mnt_escape_count_high++; +smp_wmb (); +WRITE_ONCE (mnt->mnt_escape_count,1 ); + }else + mnt->mnt_escape_count++; +#endif smp_wmb (); } }
Signature · 1 line
2.2.1
PATCH 1/6 mnt: Track which mounts use a dentry as root.
In reply to Eric W. BiedermanThis is needed infrastructure for better handling of when files or directories are moved out from under the root of a bind mount.
--- fs/mount.h | 7 +++ fs/namespace.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 7 +++ 3 files changed, 130 insertions(+), 4 deletions(-)
fs/mount.h +7 -0
@@ -27,6 +27,12 @@ struct mountpoint {int m_count; }; +struct mountroot { +struct hlist_node r_hash; +struct dentry *r_dentry; +struct hlist_head r_list; +}; +struct mount {struct hlist_node mnt_hash;struct mount *mnt_parent;
@@ -55,6 +61,7 @@ struct mount {struct mnt_namespace *mnt_ns;/* containing namespace */ struct mountpoint *mnt_mp;/* where is it mounted */ struct hlist_node mnt_mp_list;/* list mounts with the same mountpoint */ +struct hlist_node mnt_mr_list;/* list mounts with the same mountroot */ #ifdef CONFIG_FSNOTIFY struct hlist_head mnt_fsnotify_marks;__u32 mnt_fsnotify_mask;
fs/namespace.c +116 -4
200 lines not on this page · raw message
include/linux/dcache.h +7 -0
@@ -228,6 +228,8 @@ struct dentry_operations {#define DCACHE_FALLTHRU 0x01000000/* Fall through to lower layer */ #define DCACHE_OP_SELECT_INODE 0x02000000/* Unioned entry: dcache op selects inode */ +#define DCACHE_MOUNTROOT 0x04000000/* Root of a vfsmount */ +extern seqlock_t rename_lock;/*
@@ -404,6 +406,11 @@ static inline bool d_mountpoint(const struct dentry *dentry)return dentry->d_flags &DCACHE_MOUNTED ; } +static inline bool d_mountroot (const struct dentry *dentry) +{ +return dentry->d_flags &DCACHE_MOUNTROOT ; +} +/* * Directory cache entry type accessor functions. */
Signature · 1 line
2.2.1
A message that is not in the archive (sent off-list or not yet received): 87vbcw9i8g.fsf_-_-JOvCrm2gF+uungPnsOpG7nhyD016LWXt@public.gmane.org
Re: [PATCH review 1/6] mnt: Track which mounts use a dentry as root.
Quoted 50 lines · Eric W. Biederman
This is needed infrastructure for better handling of when files or directories are moved out from under the root of a bind mount.
--- fs/mount.h | 7 +++ fs/namespace.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 7 +++ 3 files changed, 130 insertions(+), 4 deletions(-)fs/mount.h +7
-0@@ -27,6 +27,12 @@ struct mountpoint {int m_count; }; +struct mountroot { +struct hlist_node r_hash; +struct dentry *r_dentry; +struct hlist_head r_list; +}; +struct mount {struct hlist_node mnt_hash;struct mount *mnt_parent;@@ -55,6 +61,7 @@ struct mount {struct mnt_namespace *mnt_ns;/* containing namespace */ struct mountpoint *mnt_mp;/* where is it mounted */ struct hlist_node mnt_mp_list;/* list mounts with the same mountpoint */ +struct hlist_node mnt_mr_list;/* list mounts with the same mountroot */ #ifdef CONFIG_FSNOTIFY struct hlist_head mnt_fsnotify_marks;__u32 mnt_fsnotify_mask;fs/namespace.c +2
-0@@ -31,6 +31,8 @@ static unsigned int m_hash_mask __read_mostly;static unsigned int m_hash_shift__read_mostly ;static unsigned int mp_hash_mask__read_mostly ;static unsigned int mp_hash_shift__read_mostly ; +static unsigned int mr_hash_mask__read_mostly ; +static unsigned int mr_hash_shift__read_mostly ;static __initdata unsigned long mhash_entries;static int __init set_mhash_entries (char *str)
fs/namespace.c static int __init set_mphash_entries(char *str) · line 62
Quoted 9 lines
fs/namespace.c +6
-0@@ -52,6 +54,16 @@ static int __init set_mphash_entries(char *str) }__setup ("mphash_entries=" , set_mphash_entries); +static __initdata unsigned long mrhash_entries; +static int __init set_mrhash_entries (char *str) +{ +if (!str) +return 0 ; + mrhash_entries =simple_strtoul (str, &str,0 );
Nit: Any particular reason for using simple_* rather than kstrto* family of functions?
Quoted 219 lines
fs/namespace.c +108
-4+return 1 ; +} +__setup ("mrhash_entries=" , set_mrhash_entries); +static u64 event;static DEFINE_IDA (mnt_id_ida);static DEFINE_IDA (mnt_group_ida);@@ -61,6 +73,7 @@ static int mnt_group_start = 1;static struct hlist_head *mount_hashtable__read_mostly ;static struct hlist_head *mountpoint_hashtable__read_mostly ; +static struct hlist_head *mountroot_hashtable__read_mostly ;static struct kmem_cache *mnt_cache__read_mostly ;static DECLARE_RWSEM (namespace_sem);@@ -93,6 +106,13 @@ static inline struct hlist_head *mp_hash(struct dentry *dentry)return &mountpoint_hashtable[tmp & mp_hash_mask]; } +static inline struct hlist_head *mr_hash (struct dentry *dentry) +{ +unsigned long tmp = ((unsigned long )dentry /L1_CACHE_BYTES ); + tmp = tmp + (tmp >> mr_hash_shift); +return &mountroot_hashtable[tmp & mr_hash_mask]; +} +/* * allocation is serialized by namespace_sem, but we need the spinlock to * serialize with freeing. @@ -234,6 +254,7 @@ static struct mount *alloc_vfsmnt(const char *name)INIT_LIST_HEAD (&mnt->mnt_slave_list);INIT_LIST_HEAD (&mnt->mnt_slave);INIT_HLIST_NODE (&mnt->mnt_mp_list); +INIT_HLIST_NODE (&mnt->mnt_mr_list);#ifdef CONFIG_FSNOTIFY INIT_HLIST_HEAD (&mnt->mnt_fsnotify_marks);#endif @@ -779,6 +800,77 @@ static void put_mountpoint(struct mountpoint *mp) } } +static struct mountroot *lookup_mountroot (struct dentry *dentry) +{ +struct hlist_head *chain =mr_hash (dentry); +struct mountroot *mr; + +hlist_for_each_entry (mr, chain, r_hash) { +if (mr->r_dentry == dentry) +return mr; + } +return NULL ; +} + +static int mnt_set_root (struct mount *mnt,struct dentry *root) +{ +struct mountroot *mr =NULL ; + +read_seqlock_excl (&mount_lock); +if (d_mountroot (root)) + mr =lookup_mountroot (root); +if (!mr) { +struct mountroot *new; +read_sequnlock_excl (&mount_lock); + + new =kmalloc (sizeof (struct mountroot ),GFP_KERNEL ); +if (!new) +return -ENOMEM ; + +read_seqlock_excl (&mount_lock); + mr =lookup_mountroot (root); +if (mr) { +kfree (new); + }else { +struct hlist_head *chain =mr_hash (root); + + mr = new; + mr->r_dentry = root; +INIT_HLIST_HEAD (&mr->r_list); +hlist_add_head (&mr->r_hash, chain); + +spin_lock (&root->d_lock); + root->d_flags |=DCACHE_MOUNTROOT ; +spin_unlock (&root->d_lock); + } + } + mnt->mnt.mnt_root = root; +hlist_add_head (&mnt->mnt_mr_list, &mr->r_list); +read_sequnlock_excl (&mount_lock); + +return 0 ; +} + +static void mnt_put_root (struct mount *mnt) +{ +struct dentry *root = mnt->mnt.mnt_root; +struct mountroot *mr; + +read_seqlock_excl (&mount_lock); + mr =lookup_mountroot (root); +BUG_ON (!mr); +hlist_del (&mnt->mnt_mr_list); +if (hlist_empty (&mr->r_list)) { +hlist_del (&mr->r_hash); +spin_lock (&root->d_lock); + root->d_flags &= ~DCACHE_MOUNTROOT ; +spin_unlock (&root->d_lock); +kfree (mr); + } +read_sequnlock_excl (&mount_lock); +dput (root); +} +static inline int check_mnt (struct mount *mnt) {return mnt->mnt_ns == current->nsproxy->mnt_ns;@@ -934,6 +1026,7 @@ vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void {struct mount *mnt;struct dentry *root; +int err;if (!type)return ERR_PTR (-ENODEV );@@ -952,8 +1045,16 @@ vfs_kern_mount(struct file_system_type *type, int flags, const char *name, voidreturn ERR_CAST (root); } - mnt->mnt.mnt_root = root; mnt->mnt.mnt_sb = root->d_sb; + err =mnt_set_root (mnt, root); +if (err) { +dput (root); +deactivate_super (mnt->mnt.mnt_sb); +mnt_free_id (mnt); +free_vfsmnt (mnt); +return ERR_PTR (err); + } + mnt->mnt_mountpoint = mnt->mnt.mnt_root; mnt->mnt_parent = mnt;lock_mount_hash ();@@ -985,6 +1086,10 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root, goto out_free; } + err =mnt_set_root (mnt, root); +if (err) + goto out_free; + mnt->mnt.mnt_flags = old->mnt.mnt_flags & ~(MNT_WRITE_HOLD |MNT_MARKED );/* Don't allow unprivileged users to change mount flags */ if (flag &CL_UNPRIVILEGED ) {@@ -1010,7 +1115,7 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,atomic_inc (&sb->s_active); mnt->mnt.mnt_sb = sb; - mnt->mnt.mnt_root =dget (root); +dget (root); mnt->mnt_mountpoint = mnt->mnt.mnt_root; mnt->mnt_parent = mnt;lock_mount_hash ();@@ -1063,7 +1168,7 @@ static void cleanup_mnt(struct mount *mnt)if (unlikely (mnt->mnt_pins.first))mnt_pin_kill (mnt);fsnotify_vfsmount_delete (&mnt->mnt); -dput (mnt->mnt.mnt_root); +mnt_put_root (mnt);deactivate_super (mnt->mnt.mnt_sb);mnt_free_id (mnt);call_rcu (&mnt->mnt_rcu, delayed_free_vfsmnt);@@ -3120,14 +3225,21 @@ void __init mnt_init(void) mphash_entries,19 ,0 , &mp_hash_shift, &mp_hash_mask, 0, 0); + mountroot_hashtable =alloc_large_system_hash ("Mountroot-cache" , +sizeof (struct hlist_head ), + mrhash_entries,19 , +0 , + &mr_hash_shift, &mr_hash_mask,0 ,0 ); -if (!mount_hashtable || !mountpoint_hashtable) +if (!mount_hashtable || !mountpoint_hashtable || !mountroot_hashtable)panic ("Failed to allocate mount hash table\n" );for (u =0 ; u <= m_hash_mask; u++)INIT_HLIST_HEAD (&mount_hashtable[u]);for (u =0 ; u <= mp_hash_mask; u++)INIT_HLIST_HEAD (&mountpoint_hashtable[u]); +for (u =0 ; u <= mr_hash_mask; u++) +INIT_HLIST_HEAD (&mountroot_hashtable[u]);kernfs_init ();include/linux/dcache.h +7
-0@@ -228,6 +228,8 @@ struct dentry_operations {#define DCACHE_FALLTHRU 0x01000000/* Fall through to lower layer */ #define DCACHE_OP_SELECT_INODE 0x02000000/* Unioned entry: dcache op selects inode */ +#define DCACHE_MOUNTROOT 0x04000000/* Root of a vfsmount */ +extern seqlock_t rename_lock;/* @@ -404,6 +406,11 @@ static inline bool d_mountpoint(const struct dentry *dentry)return dentry->d_flags &DCACHE_MOUNTED ; } +static inline bool d_mountroot (const struct dentry *dentry) +{ +return dentry->d_flags &DCACHE_MOUNTROOT ; +} +/* * Directory cache entry type accessor functions. */
Re: [PATCH review 1/6] mnt: Track which mounts use a dentry as root.
In reply to Eric W. BiedermanQuoted 50 lines · Eric W. Biederman
This is needed infrastructure for better handling of when files or directories are moved out from under the root of a bind mount.
Signed-off-by "Eric W. Biederman" <[email protected]>--- fs/mount.h | 7 +++ fs/namespace.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 7 +++ 3 files changed, 130 insertions(+), 4 deletions(-)fs/mount.h +7
-0@@ -27,6 +27,12 @@ struct mountpoint {int m_count; }; +struct mountroot { +struct hlist_node r_hash; +struct dentry *r_dentry; +struct hlist_head r_list; +}; +struct mount {struct hlist_node mnt_hash;struct mount *mnt_parent;@@ -55,6 +61,7 @@ struct mount {struct mnt_namespace *mnt_ns;/* containing namespace */ struct mountpoint *mnt_mp;/* where is it mounted */ struct hlist_node mnt_mp_list;/* list mounts with the same mountpoint */ +struct hlist_node mnt_mr_list;/* list mounts with the same mountroot */ #ifdef CONFIG_FSNOTIFY struct hlist_head mnt_fsnotify_marks;__u32 mnt_fsnotify_mask;fs/namespace.c +2
-0@@ -31,6 +31,8 @@ static unsigned int m_hash_mask __read_mostly;static unsigned int m_hash_shift__read_mostly ;static unsigned int mp_hash_mask__read_mostly ;static unsigned int mp_hash_shift__read_mostly ; +static unsigned int mr_hash_mask__read_mostly ; +static unsigned int mr_hash_shift__read_mostly ;static __initdata unsigned long mhash_entries;static int __init set_mhash_entries (char *str)
fs/namespace.c static int __init set_mphash_entries(char *str) · line 62
Quoted 9 lines
fs/namespace.c +6
-0@@ -52,6 +54,16 @@ static int __init set_mphash_entries(char *str) }__setup ("mphash_entries=" , set_mphash_entries); +static __initdata unsigned long mrhash_entries; +static int __init set_mrhash_entries (char *str) +{ +if (!str) +return 0 ; + mrhash_entries =simple_strtoul (str, &str,0 );
Nit: Any particular reason for using simple_* rather than kstrto* family of functions?
Quoted 219 lines
fs/namespace.c +108
-4+return 1 ; +} +__setup ("mrhash_entries=" , set_mrhash_entries); +static u64 event;static DEFINE_IDA (mnt_id_ida);static DEFINE_IDA (mnt_group_ida);@@ -61,6 +73,7 @@ static int mnt_group_start = 1;static struct hlist_head *mount_hashtable__read_mostly ;static struct hlist_head *mountpoint_hashtable__read_mostly ; +static struct hlist_head *mountroot_hashtable__read_mostly ;static struct kmem_cache *mnt_cache__read_mostly ;static DECLARE_RWSEM (namespace_sem);@@ -93,6 +106,13 @@ static inline struct hlist_head *mp_hash(struct dentry *dentry)return &mountpoint_hashtable[tmp & mp_hash_mask]; } +static inline struct hlist_head *mr_hash (struct dentry *dentry) +{ +unsigned long tmp = ((unsigned long )dentry /L1_CACHE_BYTES ); + tmp = tmp + (tmp >> mr_hash_shift); +return &mountroot_hashtable[tmp & mr_hash_mask]; +} +/* * allocation is serialized by namespace_sem, but we need the spinlock to * serialize with freeing. @@ -234,6 +254,7 @@ static struct mount *alloc_vfsmnt(const char *name)INIT_LIST_HEAD (&mnt->mnt_slave_list);INIT_LIST_HEAD (&mnt->mnt_slave);INIT_HLIST_NODE (&mnt->mnt_mp_list); +INIT_HLIST_NODE (&mnt->mnt_mr_list);#ifdef CONFIG_FSNOTIFY INIT_HLIST_HEAD (&mnt->mnt_fsnotify_marks);#endif @@ -779,6 +800,77 @@ static void put_mountpoint(struct mountpoint *mp) } } +static struct mountroot *lookup_mountroot (struct dentry *dentry) +{ +struct hlist_head *chain =mr_hash (dentry); +struct mountroot *mr; + +hlist_for_each_entry (mr, chain, r_hash) { +if (mr->r_dentry == dentry) +return mr; + } +return NULL ; +} + +static int mnt_set_root (struct mount *mnt,struct dentry *root) +{ +struct mountroot *mr =NULL ; + +read_seqlock_excl (&mount_lock); +if (d_mountroot (root)) + mr =lookup_mountroot (root); +if (!mr) { +struct mountroot *new; +read_sequnlock_excl (&mount_lock); + + new =kmalloc (sizeof (struct mountroot ),GFP_KERNEL ); +if (!new) +return -ENOMEM ; + +read_seqlock_excl (&mount_lock); + mr =lookup_mountroot (root); +if (mr) { +kfree (new); + }else { +struct hlist_head *chain =mr_hash (root); + + mr = new; + mr->r_dentry = root; +INIT_HLIST_HEAD (&mr->r_list); +hlist_add_head (&mr->r_hash, chain); + +spin_lock (&root->d_lock); + root->d_flags |=DCACHE_MOUNTROOT ; +spin_unlock (&root->d_lock); + } + } + mnt->mnt.mnt_root = root; +hlist_add_head (&mnt->mnt_mr_list, &mr->r_list); +read_sequnlock_excl (&mount_lock); + +return 0 ; +} + +static void mnt_put_root (struct mount *mnt) +{ +struct dentry *root = mnt->mnt.mnt_root; +struct mountroot *mr; + +read_seqlock_excl (&mount_lock); + mr =lookup_mountroot (root); +BUG_ON (!mr); +hlist_del (&mnt->mnt_mr_list); +if (hlist_empty (&mr->r_list)) { +hlist_del (&mr->r_hash); +spin_lock (&root->d_lock); + root->d_flags &= ~DCACHE_MOUNTROOT ; +spin_unlock (&root->d_lock); +kfree (mr); + } +read_sequnlock_excl (&mount_lock); +dput (root); +} +static inline int check_mnt (struct mount *mnt) {return mnt->mnt_ns == current->nsproxy->mnt_ns;@@ -934,6 +1026,7 @@ vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void {struct mount *mnt;struct dentry *root; +int err;if (!type)return ERR_PTR (-ENODEV );@@ -952,8 +1045,16 @@ vfs_kern_mount(struct file_system_type *type, int flags, const char *name, voidreturn ERR_CAST (root); } - mnt->mnt.mnt_root = root; mnt->mnt.mnt_sb = root->d_sb; + err =mnt_set_root (mnt, root); +if (err) { +dput (root); +deactivate_super (mnt->mnt.mnt_sb); +mnt_free_id (mnt); +free_vfsmnt (mnt); +return ERR_PTR (err); + } + mnt->mnt_mountpoint = mnt->mnt.mnt_root; mnt->mnt_parent = mnt;lock_mount_hash ();@@ -985,6 +1086,10 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root, goto out_free; } + err =mnt_set_root (mnt, root); +if (err) + goto out_free; + mnt->mnt.mnt_flags = old->mnt.mnt_flags & ~(MNT_WRITE_HOLD |MNT_MARKED );/* Don't allow unprivileged users to change mount flags */ if (flag &CL_UNPRIVILEGED ) {@@ -1010,7 +1115,7 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,atomic_inc (&sb->s_active); mnt->mnt.mnt_sb = sb; - mnt->mnt.mnt_root =dget (root); +dget (root); mnt->mnt_mountpoint = mnt->mnt.mnt_root; mnt->mnt_parent = mnt;lock_mount_hash ();@@ -1063,7 +1168,7 @@ static void cleanup_mnt(struct mount *mnt)if (unlikely (mnt->mnt_pins.first))mnt_pin_kill (mnt);fsnotify_vfsmount_delete (&mnt->mnt); -dput (mnt->mnt.mnt_root); +mnt_put_root (mnt);deactivate_super (mnt->mnt.mnt_sb);mnt_free_id (mnt);call_rcu (&mnt->mnt_rcu, delayed_free_vfsmnt);@@ -3120,14 +3225,21 @@ void __init mnt_init(void) mphash_entries,19 ,0 , &mp_hash_shift, &mp_hash_mask, 0, 0); + mountroot_hashtable =alloc_large_system_hash ("Mountroot-cache" , +sizeof (struct hlist_head ), + mrhash_entries,19 , +0 , + &mr_hash_shift, &mr_hash_mask,0 ,0 ); -if (!mount_hashtable || !mountpoint_hashtable) +if (!mount_hashtable || !mountpoint_hashtable || !mountroot_hashtable)panic ("Failed to allocate mount hash table\n" );for (u =0 ; u <= m_hash_mask; u++)INIT_HLIST_HEAD (&mount_hashtable[u]);for (u =0 ; u <= mp_hash_mask; u++)INIT_HLIST_HEAD (&mountpoint_hashtable[u]); +for (u =0 ; u <= mr_hash_mask; u++) +INIT_HLIST_HEAD (&mountroot_hashtable[u]);kernfs_init ();include/linux/dcache.h +7
-0@@ -228,6 +228,8 @@ struct dentry_operations {#define DCACHE_FALLTHRU 0x01000000/* Fall through to lower layer */ #define DCACHE_OP_SELECT_INODE 0x02000000/* Unioned entry: dcache op selects inode */ +#define DCACHE_MOUNTROOT 0x04000000/* Root of a vfsmount */ +extern seqlock_t rename_lock;/* @@ -404,6 +406,11 @@ static inline bool d_mountpoint(const struct dentry *dentry)return dentry->d_flags &DCACHE_MOUNTED ; } +static inline bool d_mountroot (const struct dentry *dentry) +{ +return dentry->d_flags &DCACHE_MOUNTROOT ; +} +/* * Directory cache entry type accessor functions. */
A message that is not in the archive (sent off-list or not yet received): [email protected]
Re: [PATCH review 1/6] mnt: Track which mounts use a dentry as root.
Quoted 17 lines · Nikolay Borisov
Quoted 17 lines
fs/namespace.c +2
-0@@ -31,6 +31,8 @@ static unsigned int m_hash_mask __read_mostly;static unsigned int m_hash_shift__read_mostly ;static unsigned int mp_hash_mask__read_mostly ;static unsigned int mp_hash_shift__read_mostly ; +static unsigned int mr_hash_mask__read_mostly ; +static unsigned int mr_hash_shift__read_mostly ;static __initdata unsigned long mhash_entries;static int __init set_mhash_entries (char *str)@@ -52,6 +54,16 @@ static int __init set_mphash_entries(char *str) }__setup ("mphash_entries=" , set_mphash_entries);
Quoted 9 lines
fs/namespace.c static int __init set_mphash_entries(char *str) · line 62
Quoted 6 lines
fs/namespace.c +6
-0+static __initdata unsigned long mrhash_entries; +static int __init set_mrhash_entries (char *str) +{ +if (!str) +return 0 ; + mrhash_entries =simple_strtoul (str, &str,0 );Nit: Any particular reason for using simple_* rather than kstrto* family of functions?
That is what set_mhash_entries, and set_mphash_entries do, and I maintained the existing style in the code.
It does look like a followup change to add error handling in the pathological cases might be worthwhile.
Although it would probably be even better to convert these hash tables into rcu resizeable hash tables that can automatically grow to the size needed.
Eric
Re: [PATCH review 1/6] mnt: Track which mounts use a dentry as root.
In reply to Nikolay BorisovQuoted 17 lines · Nikolay Borisov
Quoted 17 lines
fs/namespace.c +2
-0@@ -31,6 +31,8 @@ static unsigned int m_hash_mask __read_mostly;static unsigned int m_hash_shift__read_mostly ;static unsigned int mp_hash_mask__read_mostly ;static unsigned int mp_hash_shift__read_mostly ; +static unsigned int mr_hash_mask__read_mostly ; +static unsigned int mr_hash_shift__read_mostly ;static __initdata unsigned long mhash_entries;static int __init set_mhash_entries (char *str)@@ -52,6 +54,16 @@ static int __init set_mphash_entries(char *str) }__setup ("mphash_entries=" , set_mphash_entries);
Quoted 9 lines
fs/namespace.c static int __init set_mphash_entries(char *str) · line 62
Quoted 6 lines
fs/namespace.c +6
-0+static __initdata unsigned long mrhash_entries; +static int __init set_mrhash_entries (char *str) +{ +if (!str) +return 0 ; + mrhash_entries =simple_strtoul (str, &str,0 );Nit: Any particular reason for using simple_* rather than kstrto* family of functions?
That is what set_mhash_entries, and set_mphash_entries do, and I maintained the existing style in the code.
It does look like a followup change to add error handling in the pathological cases might be worthwhile.
Although it would probably be even better to convert these hash tables into rcu resizeable hash tables that can automatically grow to the size needed.
Eric
PATCH 4/6 mnt: Track when a directory escapes a bind mount
In reply to Eric W. BiedermanWhen bind mounts are in use, and there is another path to the filesystem it is possible to rename files or directories from a path underneath the root of the bind mount to a path that is not underneath the root of the bind mount.
When a directory is moved out from under the root of a bind mount path name lookups that go up the directory tree potentially allow accessing the entire dentry tree of the filesystem. This is not expected, not what is desired and winds up being a secruity problem for userspace.
Augment d_move, d_exchange, and __d_unalias with matching calls to lock_namespace_rename and unlock_namespace_rename, to mark mounts that directories have escaped from.
A few notes on the implementation:
- The escape count on struct mount must be incremented both before the rename and after. If the count is not incremented before the rename it is possible to hit a scenario where the rename happens the code walks up the directory tree to somewhere outside of the bind mount before the count is touched. Similary without a count after the rename it is possible for the code to look at the escape count validate a path is connected before the rename and assume cache the escape count, leading to not retesting the path is ok.
- A lock either namespace_sem or mount_lock needs to be held across the duration of renames where a directory could be escaping to guarantee pairing of the escape_count increments and to ensure that a mount is not added, escaped, and missed during the rename.
- The locking order must be mount_lock outside of rename_lock as prepend_path already takes the locks in this order.
- I have audited all callers of d_move and d_exchange and in every instance it appears safe for d_move and d_exchange to start sleeping. d_splice_alias already sleeps in security_d_instantiate so no audit was needed for it to begin sleeping.
As I can just take mount_lock I don't use that freedom in this change, but it can be relevant to small changes to the locking in this code.
- The largest change is in d_unalias, where the two cases are split apart so they can be handled separately. In the easy case of a rename within the same directory all that is needed is __d_move (escaping a mount is impossible in that case). In the more involved case mutexes need to be acquired, and now the spin locks need to be dropped so that proper lock aquisition order around __d_move can be arranged.
As I read the code inode->i_lock needs to be held until ailas->d_parent->d_parent->i_mutex is taken. The only case I can see that removes an inode from a dentry is d_delete called from a path like vfs_unlink. Those paths all take the parent directories inode mutex. Thus once the parent directories inode mutex is held it becomes unnecessary to hold inode->i_lock to ensure the alias remains an alias.
Similarly the rename_lock does not need to be held once the s_vfs_rename_mutex is taken.
--- fs/dcache.c | 46 +++++++++++++++++---- fs/mount.h | 18 +++++++++ fs/namespace.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 7 deletions(-)
fs/dcache.c +39 -7
@@ -2707,9 +2707,17 @@ static void __d_move(struct dentry *dentry, struct dentry *target,*/ void d_move (struct dentry *dentry,struct dentry *target) { +const struct dentry *unlock; + + unlock =lock_namespace_rename (dentry, target, false); +write_seqlock (&rename_lock);__d_move (dentry, target, false);write_sequnlock (&rename_lock); + +if (unlock) +unlock_namespace_rename (unlock, dentry, target, false); + }EXPORT_SYMBOL (d_move);
@@ -2720,6 +2728,10 @@ EXPORT_SYMBOL(d_move);*/ void d_exchange (struct dentry *dentry1,struct dentry *dentry2) { +const struct dentry *unlock; + + unlock =lock_namespace_rename (dentry1, dentry2, true); +write_seqlock (&rename_lock);WARN_ON (!dentry1->d_inode);
@@ -2730,6 +2742,9 @@ void d_exchange(struct dentry *dentry1, struct dentry *dentry2)__d_move (dentry1, dentry2, true);write_sequnlock (&rename_lock); + +if (unlock) +unlock_namespace_rename (unlock, dentry1, dentry2, true); }/**
@@ -2764,11 +2779,15 @@ static int __d_unalias(struct inode *inode, struct dentry *dentry, struct dentry *alias) { struct mutex *m1 = NULL, *m2 = NULL; -int ret = -ESTALE ; +const struct dentry *unlock;/* If alias and dentry share a parent, then no extra locks required */ -if (alias->d_parent == dentry->d_parent) - goto out_unalias; +if (alias->d_parent == dentry->d_parent) { +__d_move (alias, dentry, false); +spin_unlock (&inode->i_lock); +write_sequnlock (&rename_lock); +return 0 ; + }/* See lock_rename() */ if (!mutex_trylock (&dentry->d_sb->s_vfs_rename_mutex))
@@ -2777,16 +2796,30 @@ static int __d_unalias(struct inode *inode,if (!mutex_trylock (&alias->d_parent->d_inode->i_mutex)) goto out_err; m2 = &alias->d_parent->d_inode->i_mutex; -out_unalias: + +spin_unlock (&inode->i_lock); +write_sequnlock (&rename_lock); + + unlock =lock_namespace_rename (alias, dentry, false); + +write_seqlock (&rename_lock);__d_move (alias, dentry, false); - ret =0 ; +write_sequnlock (&rename_lock); + +if (unlock) +unlock_namespace_rename (unlock, alias, dentry, false); + +mutex_unlock (m2); +mutex_unlock (m1); +return 0 ; out_err:spin_unlock (&inode->i_lock); +write_sequnlock (&rename_lock);if (m2)mutex_unlock (m2);if (m1)mutex_unlock (m1); -return ret; +return -ESTALE ; }/**
@@ -2841,7 +2874,6 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry) inode->i_sb->s_id); }else if (!IS_ROOT (new)) {int err =__d_unalias (inode, dentry, new); -write_sequnlock (&rename_lock);if (err) {dput (new); new =ERR_PTR (err);
fs/mount.h +18 -0
@@ -38,6 +38,7 @@ struct mount {struct mount *mnt_parent;struct dentry *mnt_mountpoint;struct vfsmount mnt; +unsigned mnt_escape_count;union {struct rcu_head mnt_rcu;struct llist_node mnt_llist;
@@ -107,6 +108,23 @@ static inline void detach_mounts(struct dentry *dentry)__detach_mounts (dentry); } +extern const struct dentry *lock_namespace_rename (struct dentry *,struct dentry *,bool ); +extern void unlock_namespace_rename (const struct dentry *,struct dentry *,struct dentry *,bool ); + +static inline unsigned read_mnt_escape_count (struct vfsmount *vfsmount) +{ +struct mount *mnt =real_mount (vfsmount); +unsigned ret =READ_ONCE (mnt->mnt_escape_count); +smp_rmb (); +return ret; +} + +static inline void cache_mnt_escape_count (unsigned *cache,unsigned escape_count) +{ +if (likely (escape_count &1 ) ==0 ) + *cache = escape_count; +} +static inline void get_mnt_ns (struct mnt_namespace *ns) {atomic_inc (&ns->count);
fs/namespace.c +123 -0
129 lines not on this page · raw message
Signature · 1 line
2.2.1
A message that is not in the archive (sent off-list or not yet received): 87egjk9i61.fsf_-_-JOvCrm2gF+uungPnsOpG7nhyD016LWXt@public.gmane.org
Re: [PATCH review 4/6] mnt: Track when a directory escapes a bind mount
Quoted 8 lines · Eric W. Biederman
- The escape count on struct mount must be incremented both before the rename and after. If the count is not incremented before the rename it is possible to hit a scenario where the rename happens the code walks up the directory tree to somewhere outside of the bind mount before the count is touched. Similary without a count after the rename it is possible for the code to look at the escape count validate a path is connected before the rename and assume cache the escape count, leading to not retesting the path is ok.
Umm... I wonder if you are overcomplicating the things here. Sure, I understand wanting to reduce the checks on "..", but... It costs you considerable complexity (especially when it comes to 64bit counts), it's really brittle (you need to be very careful about the places where you zero the cached values in fs/namei.c and missing one will lead to really unpleasant effects there) _and_ it's all for the benefit of a very rare case. With check you are optimizing away not being all that costly anyway.
Quoted 7 lines
- The largest change is in
d_unalias, where the two cases are split apart so they can be handled separately. In the easy case of a rename within the same directory all that is needed is__d_move(escaping a mount is impossible in that case). In the more involved case mutexes need to be acquired, and now the spin locks need to be dropped so that proper lock aquisition order around__d_movecan be arranged.
I _really_ hate that part. Could you explain WTF is wrong with simply taking mount_lock in that case of __d_splice_alias() just outside of rename_lock?
fs/dcache.c static void __d_move(struct dentry *dentry, struct dentry *target, · line 2720
Quoted 9 lines
fs/dcache.c +6
-0+ unlock =lock_namespace_rename (dentry, target, false); +write_seqlock (&rename_lock);__d_move (dentry, target, false);write_sequnlock (&rename_lock); + +if (unlock) +unlock_namespace_rename (unlock, dentry, target, false); +
Your unlock_namespace_rename() should've been a static inline. With the check of unlock != NULL done in there. Two such inlines, actually, and to hell with the boolean argument. Same split for the lock counterpart, of course.
A message that is not in the archive (sent off-list or not yet received): 20150810043637.GC14139-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 4/6] mnt: Track when a directory escapes a bind mount
Quoted 4 lines · Al Viro
Your
unlock_namespace_rename()should've been a static inline. With the check of unlock != NULL done in there. Two such inlines, actually, and to hell with the boolean argument. Same split for the lock counterpart, of course.
PS: that thing should be in fs/dcache.c, at least in the part that deals with finding the common ancestor, etc. And __d_move() (and dentry_lock_for_move()) games with d_ancestor() should be redundant now.
Re: [PATCH review 4/6] mnt: Track when a directory escapes a bind mount
In reply to a message not in the archiveQuoted 11 lines · Al Viro
Quoted 8 lines · Eric W. Biederman
- The escape count on struct mount must be incremented both before the rename and after. If the count is not incremented before the rename it is possible to hit a scenario where the rename happens the code walks up the directory tree to somewhere outside of the bind mount before the count is touched. Similary without a count after the rename it is possible for the code to look at the escape count validate a path is connected before the rename and assume cache the escape count, leading to not retesting the path is ok.
Quoted 8 lines
Umm... I wonder if you are overcomplicating the things here. Sure, I understand wanting to reduce the checks on "..", but... It costs you considerable complexity (especially when it comes to 64bit counts), it's really brittle (you need to be very careful about the places where you zero the cached values in
fs/namei.cand missing one will lead to really unpleasant effects there) _and_ it's all for the benefit of a very rare case. With check you are optimizing away not being all that costly anyway.
I had to give this a long hard think. Algorithms going to O(N^2) when it is uncessarry really bother me. I ran some numbers for really deep directory trees, slow memory, etc and I could not come up with a scenario where even in it's worst case d_ancestor would take anywhere near as long as a one disk seek, and most of the d_ancestor would be much quicker.
So it appears to me that in the worst case a pathname lookup consisting of a ridiculous number of .. components starting with a cold cache, on a mount where a directory has escaped is likely to be faster than a similar lookup going down the tree with many disk seeks.
I don't think the 64bit counts and the zeroing the cache values are quite as bad as you make out. There are much trickier things already in path name lookup code. But I do agree that it is easy to get wrong because nothing will show up in testing, and getting it wrong will have really unpleasant effects.
I also can't see a scenario where a directory would escape a subtree that is mounted somewhere without it being a misconfiguration.
So I agree it is not worth it to optimize the code so that there are an absolute minimum number of d_ancestor calls during pathname lookup.
Further replacing mnt_escape_count with a mnt_flag makes the code much simpler. Which I very much appreciate.
Quoted 11 lines
Quoted 7 lines
- The largest change is in
d_unalias, where the two cases are split apart so they can be handled separately. In the easy case of a rename within the same directory all that is needed is__d_move(escaping a mount is impossible in that case). In the more involved case mutexes need to be acquired, and now the spin locks need to be dropped so that proper lock aquisition order around__d_movecan be arranged.I _really_ hate that part. Could you explain WTF is wrong with simply taking
mount_lockin that case of__d_splice_alias()just outside ofrename_lock?
Me too. So upon realizing the that inode->i_lock is held longer than necessary in d_splice_alias I reworked the locking in d_splice_alias.
Updated patches to follow in a little bit.
Quoted 4 lines
PS: that thing should be in
fs/dcache.c, at least in the part that deals with finding the common ancestor, etc. And__d_move()(anddentry_lock_for_move()) games withd_ancestor()should be redundant now.
It does seem reasonable that the BUG_ONs in __d_move that call d_ancestor can be removed, or simplified by passing the common ancestor into __d_move.
I don't know the code in dentry_lock_for_move well enough to say anything except that the d_ancestor call in dentry_lock_for_move looks reasonable.
Doing anything inside of __d_move or dentry_lock_for_move appears to be a detour to the cause of preventing escaping from bind mounts. So while I have no problems with the with the kinds of changes I hear you suggesting, but unless I encounter something that makes changing __d_move or dentry_lock_for_more relevant to the work of preventing escaping from bind mounts I don't plan on touching them while that is my focus.
Eric
Re: [PATCH review 4/6] mnt: Track when a directory escapes a bind mount
In reply to Al ViroQuoted 11 lines · Al Viro
Quoted 8 lines · Eric W. Biederman
- The escape count on struct mount must be incremented both before the rename and after. If the count is not incremented before the rename it is possible to hit a scenario where the rename happens the code walks up the directory tree to somewhere outside of the bind mount before the count is touched. Similary without a count after the rename it is possible for the code to look at the escape count validate a path is connected before the rename and assume cache the escape count, leading to not retesting the path is ok.
Quoted 8 lines
Umm... I wonder if you are overcomplicating the things here. Sure, I understand wanting to reduce the checks on "..", but... It costs you considerable complexity (especially when it comes to 64bit counts), it's really brittle (you need to be very careful about the places where you zero the cached values in
fs/namei.cand missing one will lead to really unpleasant effects there) _and_ it's all for the benefit of a very rare case. With check you are optimizing away not being all that costly anyway.
I had to give this a long hard think. Algorithms going to O(N^2) when it is uncessarry really bother me. I ran some numbers for really deep directory trees, slow memory, etc and I could not come up with a scenario where even in it's worst case d_ancestor would take anywhere near as long as a one disk seek, and most of the d_ancestor would be much quicker.
So it appears to me that in the worst case a pathname lookup consisting of a ridiculous number of .. components starting with a cold cache, on a mount where a directory has escaped is likely to be faster than a similar lookup going down the tree with many disk seeks.
I don't think the 64bit counts and the zeroing the cache values are quite as bad as you make out. There are much trickier things already in path name lookup code. But I do agree that it is easy to get wrong because nothing will show up in testing, and getting it wrong will have really unpleasant effects.
I also can't see a scenario where a directory would escape a subtree that is mounted somewhere without it being a misconfiguration.
So I agree it is not worth it to optimize the code so that there are an absolute minimum number of d_ancestor calls during pathname lookup.
Further replacing mnt_escape_count with a mnt_flag makes the code much simpler. Which I very much appreciate.
Quoted 11 lines
Quoted 7 lines
- The largest change is in
d_unalias, where the two cases are split apart so they can be handled separately. In the easy case of a rename within the same directory all that is needed is__d_move(escaping a mount is impossible in that case). In the more involved case mutexes need to be acquired, and now the spin locks need to be dropped so that proper lock aquisition order around__d_movecan be arranged.I _really_ hate that part. Could you explain WTF is wrong with simply taking
mount_lockin that case of__d_splice_alias()just outside ofrename_lock?
Me too. So upon realizing the that inode->i_lock is held longer than necessary in d_splice_alias I reworked the locking in d_splice_alias.
Updated patches to follow in a little bit.
Quoted 4 lines
PS: that thing should be in
fs/dcache.c, at least in the part that deals with finding the common ancestor, etc. And__d_move()(anddentry_lock_for_move()) games withd_ancestor()should be redundant now.
It does seem reasonable that the BUG_ONs in __d_move that call d_ancestor can be removed, or simplified by passing the common ancestor into __d_move.
I don't know the code in dentry_lock_for_move well enough to say anything except that the d_ancestor call in dentry_lock_for_move looks reasonable.
Doing anything inside of __d_move or dentry_lock_for_move appears to be a detour to the cause of preventing escaping from bind mounts. So while I have no problems with the with the kinds of changes I hear you suggesting, but unless I encounter something that makes changing __d_move or dentry_lock_for_more relevant to the work of preventing escaping from bind mounts I don't plan on touching them while that is my focus.
Eric
A message that is not in the archive (sent off-list or not yet received): [email protected]
cover Bind mount escape fixes
It is possible in some situations to rename a file or directory through one mount point such that it can start out inside of a bind mount and after the rename wind up outside of the bind mount. Unfortunately with user namespaces these conditions can be trivially created by creating a bind mount under an existing bind mount.
I have identified four situations in which this may be a problem.
- __d_path and d_absolute_path need to error on disconnected paths that can not reach some root directory or lsm path based security checks can incorrectly succeed.
- Normal path name resolution following .. can lead to a directory that is outside of the original loopback mount.
- file handle reconsititution aka exportfs_decode_fh can yield a dentry from which d_parent can be followed up to mnt->sb->s_root, but d_parent can not be followed up to mnt->mnt_root.
- Mounts on a path that has been renamed outside of a loopback mount become unreachable, as there is no possible path that can be passed to umount to unmount them.
My strategy:
o File handle reconsitituion problems can be prevented by enabling the nfsd subtree checks for nfs exports, and open_by_handle_at requires capable(CAP_DAC_READ_SEARCH) so is only usable by the global root. This makes any problems difficult if not impossible to exploit in practice so I have not yet written code to address that issue.
o The functions __d_path and d_absolute_path are agumented so that the security modules will not be fed a problematic path to work with.
o Following of .. has been agumented to test that after d_parent has been resolved the original directory is connected, and if not an error of -ENOENT is returned.
o I do not worry about mounts that are disconnected from their bind mount as these mounts can always be freed by either umount -l on the bind mount they have escaped from, or by freeing the mount namespace. So I do not believe there is an actual problem.
Pathname resolution is a common fast path and most of the code in this patchset to support keeping .. from becoming expensive in the common case.
After hearing the Al's feedback and running some numbers I have given up attempting to keeping the number of d_ancestor calls during pathname resolution to an absolute minimum. It appears that simply preventing calls d_ancestor unless a directory has escaped is good enough. This change in approach has significantly simplified the code.
The big implementation change to note is that I have rewritten d_splice_alias and made some significant progress in cleaning up how the locks are dealt with. The only limitation now is that dentry->d_parent->d_inode->i_mutex is taken in lookup held whend_splice_alias is called. If that ever goes away my newd_splice_alias can easily take all of the locks it needs to rename a directory in the proper order.
Does anyone see anything significant that I have missed?
These changes are all against v4.2-rc1.
For those who like to see everything in a single tree the code is at:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-testing
Eric W. Biederman (8):
dcache: Handle escaped paths in prepend_path
dcache: Reduce the scope of i_lock in d_splice_alias
dcache: Clearly separate the two directory rename cases in d_splice_alias
mnt: Track which mounts use a dentry as root.
dcache: Implement d_common_ancestor
dcache: Only read d_flags once is d_is_dir
mnt: Track when a directory escapes a bind mount
vfs: Test for and handle paths that are unreachable from their mnt_root
fs/dcache.c | 193 +++++++++++++++++++++++++++++++++++++------------ fs/mount.h | 9 +++ fs/namei.c | 26 ++++++- fs/namespace.c | 152 +++++++++++++++++++++++++++++++++++++- include/linux/dcache.h | 11 ++- include/linux/mount.h | 1 + 6 files changed, 338 insertions(+), 54 deletions(-)
A message that is not in the archive (sent off-list or not yet received): 87wpwyjxwc.fsf_-_-JOvCrm2gF+uungPnsOpG7nhyD016LWXt@public.gmane.org
PATCH 1/8 dcache: Handle escaped paths in prepend_path
A rename can result in a dentry that by walking up d_parent will never reach it's mnt_root. For lack of a better term I call this an escaped path.
prepend_path is called by four different functions __d_path, d_absolute_path, d_path, and getcwd.
__d_path only wants to see paths are connected to the root it passes in. So __d_path needs prepend_path to return an error.
d_absolute_path similarly wants to see paths that are connected to some root. Escaped paths are not connected to any mnt_root so d_absolute_path needs prepend_path to return an error greater than 1. So escaped paths will be treated like paths on lazily unmounted mounts.
getcwd needs to prepend "(unreachable)" so getcwd also needs prepend_path to return an error.
d_path is the interesting hold out. d_path just wants to print something, and does not care about the weird cases. Which raises the question what should be printed?
Given that <escaped_path>/<anything> should result in -ENOENT I believe it is desirable for escaped paths to be printed as empty paths. As there are not really any meaninful path components when considered from the perspective of a mount tree.
So tweak prepend_path to return an empty path with an new error code of 3 when it encounters an escaped path.
--- fs/dcache.c | 7 +++++++ 1 file changed, 7 insertions(+)
fs/dcache.c +7 -0
@@ -2923,6 +2923,13 @@ restart:if (dentry == vfsmnt->mnt_root ||IS_ROOT (dentry)) {struct mount *parent =ACCESS_ONCE (mnt->mnt_parent); +/* Escaped? */ +if (dentry != vfsmnt->mnt_root) { + bptr = *buffer; + blen = *buflen; + error =3 ; +break ; + }/* Global root? */ if (mnt != parent) { dentry =ACCESS_ONCE (mnt->mnt_mountpoint);
Signature · 1 line
2.2.1
PATCH 2/8 dcache: Reduce the scope of i_lock in d_splice_alias
In reply to a message not in the archivei_lock is only needed until __d_find_any_alias calls dget on the alias dentry. After that the reference to new ensures that dentry_kill and d_delete will not remove the inode from the dentry, and remove the dentry from the inode->d_entry list.
The inode i_lock came to be held over the the __d_move calls in d_splice_alias through a series of introduction of locks with increasing smaller scope. First it was the dcache_lock, then it was the dcache_inode_lock, and finally inode->i_lock.
Furthermore inode->i_lock is not held over any other calls to d_move or __d_move so it can not provide any meaningful rename protection.
--- fs/dcache.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
fs/dcache.c +3 -4
@@ -2715,7 +2715,7 @@ struct dentry *d_ancestor(struct dentry *p1, struct dentry *p2)* This helper attempts to cope with remotely renamed directories * * It assumes that the caller is already holding -* dentry->d_parent->d_inode->i_mutex, inode->i_lock and rename_lock +* dentry->d_parent->d_inode->i_mutex, and rename_lock * * Note: If ever the locking in lock_rename() changes, then please * remember to update this too...
@@ -2741,7 +2741,6 @@ out_unalias:__d_move (alias, dentry, false); ret =0 ; out_err: -spin_unlock (&inode->i_lock);if (m2)mutex_unlock (m2);if (m1)
@@ -2787,10 +2786,11 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)if (S_ISDIR (inode->i_mode)) {struct dentry *new =__d_find_any_alias (inode);if (unlikely (new)) { +/* The reference to new ensures it remains an alias */ +spin_unlock (&inode->i_lock);write_seqlock (&rename_lock);if (unlikely (d_ancestor (new, dentry))) {write_sequnlock (&rename_lock); -spin_unlock (&inode->i_lock);dput (new); new =ERR_PTR (-ELOOP ); pr_warn_ratelimited(
@@ -2809,7 +2809,6 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry) }else {__d_move (new, dentry, false);write_sequnlock (&rename_lock); -spin_unlock (&inode->i_lock);security_d_instantiate (new, inode); }iput (inode);
Signature · 1 line
2.2.1
PATCH 3/8 dcache: Clearly separate the two directory rename cases in d_splice_alias
In reply to a message not in the archiveThere are two scenarios that can result in an alias being found in d_splice_alias. The first scenario is a disconnected dentry being looked up and reconnected (common with nfs file handles, and open_by_handle_at). The second scenario is a lookup on a directory lazily discovering that it was renamed on the remote filesystem.
The locking challenge for handling these two scenarios is that the instance of lookup calling d_splice_alias has already taken the dentry->d_parent->d_inode->i_mutex.
If the mutex was not held the locking we could just do:
rename_mutex = &inode->i_sb->s_vfs_rename_mutex;
mutex_lock(rename_mutex);
if (d_ancestor(alias, dentry)) {
mutex_unlock(rename_mutex);
pr_warn_ratelimited(...);
return -ELOOP;
}
m1 = &dentry->d_parent->d_inode->i_mutex;
mutex_lock_nested(m1, I_MUTEX_PARENT);
if (!IS_ROOT(alias) && (alias->d_parent != dentry->d_parent)) {
m2 = &alias->d_parent->d_inode->i_mutex;
mutex_lock_nested(m2, I_MUTEX_PARENT2);
}
d_move(alias, dentry);
if (m2)
mutex_unlock(m2);
mutex_unlock(m1);
mutex_unlock(rename_mutex);Which is essentially lock_rename and unlock_reaname with added handling of the IS_ROOT case.
The problem is that as the lookup locking stands today grabbing the s_vfs_rename_mutex must use mutex_trylock which can fail, so for reliability reasons we need to avoid using the rename_mutex as much as possible.
For the case where a disconnected dentry is being connected (aka the IS_ROOT case) the d_ancestor test can be placed under the rename_lock removing any chance that taking locks will cause a failure.
For the lazy rename case things are trickier because when dentry->d_parent and alias->d_parent are not equal the code need to take the s_vfs_rename_mutex, or risk the d_ancestor call in lock_rename being inaccurate. As s_vfs_rename_mutex is take first there are no locking ordering issues with taking alias->d_parent->d_inode->i_mutex. Furthermore as games with rename_lock are unnecessary d_move instead of __d_move can be called.
Sleeping in d_splice_alias is not something new as security_d_instantiate is a sleeping function.
Compared to the current implementation this change introduces a function for each case __d_rename_alias and __d_connect_alias and moves the acquisition of rename_lock down into those functions.
In the case of __d_rename_alias which was __d_unalias this allows the existing lock ordering rules to be followed much more closely removing the need for a trylock when acquiring alias->d_parent->d_inode->i_mutex, and allowing the use of d_move.
A common helper that prints the warning message when a loop is detected is factored out into d_alias_is_ancestor so that code does not need to be duplicated in both cases.
--- fs/dcache.c | 111 ++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 67 insertions(+), 44 deletions(-)
fs/dcache.c +67 -44
@@ -2711,41 +2711,77 @@ struct dentry *d_ancestor(struct dentry *p1, struct dentry *p2)return NULL ; } +static struct dentry *d_alias_is_ancestor (struct dentry *alias, +struct dentry *dentry) +{ +struct dentry *ancestor =d_ancestor (alias, dentry); +if (ancestor) { +pr_warn_ratelimited ( +"VFS: Lookup of '%s' in %s %s would have caused loop\n" , + dentry->d_name.name, + dentry->d_sb->s_type->name, + dentry->d_sb->s_id); + } +return ancestor; +} +/* * This helper attempts to cope with remotely renamed directories * * It assumes that the caller is already holding -* dentry->d_parent->d_inode->i_mutex, and rename_lock +* dentry->d_parent->d_inode->i_mutex * * Note: If ever the locking in lock_rename() changes, then please * remember to update this too... */ -static int __d_unalias (struct inode *inode, -struct dentry *dentry,struct dentry *alias) +static int __d_rename_alias (struct dentry *alias,struct dentry *dentry) { -struct mutex *m1 =NULL , *m2 =NULL ; -int ret = -ESTALE ; +struct mutex *rename_mutex =NULL , *parent_mutex =NULL ; -/* If alias and dentry share a parent, then no extra locks required */ -if (alias->d_parent == dentry->d_parent) - goto out_unalias; +/* Holding dentry->d_parent->d_inode->i_mutex guarantees this +* that the equality or inequality alias->d_parent and +* dentry->d_parent remains stable. +*/ +if (alias->d_parent != dentry->d_parent) { +/* See lock_rename() */ + rename_mutex = &dentry->d_sb->s_vfs_rename_mutex; +if (!mutex_trylock (rename_mutex)) +return -ESTALE ; + +if (d_alias_is_ancestor (alias, dentry)) { +mutex_unlock (rename_mutex); +return -ELOOP ; + } + parent_mutex = &alias->d_parent->d_inode->i_mutex; +mutex_lock_nested (parent_mutex,I_MUTEX_PARENT2 ); + } +d_move (alias, dentry); +if (parent_mutex) { +mutex_unlock (parent_mutex); +mutex_unlock (rename_mutex); + } +return 0 ; +} -/* See lock_rename() */ -if (!mutex_trylock (&dentry->d_sb->s_vfs_rename_mutex)) - goto out_err; - m1 = &dentry->d_sb->s_vfs_rename_mutex; -if (!mutex_trylock (&alias->d_parent->d_inode->i_mutex)) - goto out_err; - m2 = &alias->d_parent->d_inode->i_mutex; -out_unalias: +/* +* This helper connects disconnected dentries. +* +* It assumes that the caller is already holding +* dentry->d_parent->d_inode->i_mutex +* +*/ +static int __d_connect_alias (struct inode *inode, +struct dentry *alias,struct dentry *dentry) +{ +write_seqlock (&rename_lock); +if (unlikely (d_alias_is_ancestor (alias, dentry))) { +write_sequnlock (&rename_lock); +return -ELOOP ; + }__d_move (alias, dentry, false); - ret =0 ; -out_err: -if (m2) -mutex_unlock (m2); -if (m1) -mutex_unlock (m1); -return ret; +write_sequnlock (&rename_lock); +security_d_instantiate (alias, inode); +return 0 ; }/**
@@ -2786,30 +2822,17 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)if (S_ISDIR (inode->i_mode)) {struct dentry *new =__d_find_any_alias (inode);if (unlikely (new)) { +int err;/* The reference to new ensures it remains an alias */ spin_unlock (&inode->i_lock); -write_seqlock (&rename_lock); -if (unlikely (d_ancestor (new, dentry))) { -write_sequnlock (&rename_lock); + +if (!IS_ROOT (new)) + err =__d_rename_alias (new, dentry); +else + err =__d_connect_alias (inode, new, dentry); +if (err) {dput (new); - new =ERR_PTR (-ELOOP ); -pr_warn_ratelimited ( -"VFS: Lookup of '%s' in %s %s" -" would have caused loop\n" , - dentry->d_name.name, - inode->i_sb->s_type->name, - inode->i_sb->s_id); - }else if (!IS_ROOT (new)) { -int err =__d_unalias (inode, dentry, new); -write_sequnlock (&rename_lock); -if (err) { -dput (new); - new =ERR_PTR (err); - } - }else { -__d_move (new, dentry, false); -write_sequnlock (&rename_lock); -security_d_instantiate (new, inode); + new =ERR_PTR (err); }iput (inode);return new;
Signature · 1 line
2.2.1
PATCH 4/8 mnt: Track which mounts use a dentry as root.
In reply to a message not in the archiveThis is needed infrastructure for better handling of when files or directories are moved out from under the root of a bind mount.
--- fs/mount.h | 7 +++ fs/namespace.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 7 +++ 3 files changed, 130 insertions(+), 4 deletions(-)
fs/mount.h +7 -0
@@ -27,6 +27,12 @@ struct mountpoint {int m_count; }; +struct mountroot { +struct hlist_node r_hash; +struct dentry *r_dentry; +struct hlist_head r_list; +}; +struct mount {struct hlist_node mnt_hash;struct mount *mnt_parent;
@@ -55,6 +61,7 @@ struct mount {struct mnt_namespace *mnt_ns;/* containing namespace */ struct mountpoint *mnt_mp;/* where is it mounted */ struct hlist_node mnt_mp_list;/* list mounts with the same mountpoint */ +struct hlist_node mnt_mr_list;/* list mounts with the same mountroot */ #ifdef CONFIG_FSNOTIFY struct hlist_head mnt_fsnotify_marks;__u32 mnt_fsnotify_mask;
fs/namespace.c +116 -4
200 lines not on this page · raw message
include/linux/dcache.h +7 -0
@@ -228,6 +228,8 @@ struct dentry_operations {#define DCACHE_FALLTHRU 0x01000000/* Fall through to lower layer */ #define DCACHE_OP_SELECT_INODE 0x02000000/* Unioned entry: dcache op selects inode */ +#define DCACHE_MOUNTROOT 0x04000000/* Root of a vfsmount */ +extern seqlock_t rename_lock;/*
@@ -403,6 +405,11 @@ static inline bool d_mountpoint(const struct dentry *dentry)return dentry->d_flags &DCACHE_MOUNTED ; } +static inline bool d_mountroot (const struct dentry *dentry) +{ +return dentry->d_flags &DCACHE_MOUNTROOT ; +} +/* * Directory cache entry type accessor functions. */
Signature · 1 line
2.2.1
PATCH 5/8 dcache: Implement d_common_ancestor
In reply to a message not in the archiveIf possible find the common ancestor of two dentries.
This is necessary infrastructure for better handling the case when a dentry is moved out from under the root of a bind mount.
--- fs/dcache.c | 37 +++++++++++++++++++++++++++++++++++++ include/linux/dcache.h | 1 + 2 files changed, 38 insertions(+)
fs/dcache.c +37 -0
@@ -2469,6 +2469,43 @@ void dentry_update_name_case(struct dentry *dentry, struct qstr *name) }EXPORT_SYMBOL (dentry_update_name_case); +static unsigned long d_depth (const struct dentry *dentry) +{ +unsigned long depth =0 ; + +while (!IS_ROOT (dentry)) { + dentry = dentry->d_parent; + depth++; + } +return depth; +} + +const struct dentry *d_common_ancestor (const struct dentry *left, +const struct dentry *right) +{ +unsigned long ldepth =d_depth (left); +unsigned long rdepth =d_depth (right); + +while (ldepth > rdepth) { + left = left->d_parent; + ldepth--; + } + +while (rdepth > ldepth) { + right = right->d_parent; + rdepth--; + } + +while (left != right) { +if (IS_ROOT (left)) +return NULL ; + left = left->d_parent; + right = right->d_parent; + } + +return left; +} +static void swap_names (struct dentry *dentry,struct dentry *target) {if (unlikely (dname_external (target))) {
include/linux/dcache.h +1 -0
@@ -313,6 +313,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *);extern void d_move (struct dentry *,struct dentry *);extern void d_exchange (struct dentry *,struct dentry *);extern struct dentry *d_ancestor (struct dentry *,struct dentry *); +extern const struct dentry *d_common_ancestor (const struct dentry *,const struct dentry *);/* appendix may either be NULL or be used for transname suffixes */ extern struct dentry *d_lookup (const struct dentry *,const struct qstr *);
Signature · 1 line
2.2.1
PATCH 6/8 dcache: Only read d_flags once is d_is_dir
In reply to a message not in the archiveCache the value of __d_entry_type in d_is_dir and test if it equal to DCACHE_DIRECTORY_TYPE or DCACHE_AUTODIR_TYPE.
The generated assembly goes from:
movl (%rdi), %eax # MEM[(volatile __u32 *)dentry_3(D)], tmp73 andl $7340032, %eax #, tmp73 cmpl $2097152, %eax #, tmp73 je .L1091 #, movl (%rdi), %eax # MEM[(volatile __u32 *)dentry_3(D)], tmp74 andl $7340032, %eax #, tmp74 cmpl $3145728, %eax #, tmp74 je .L1091 #, to: movl (%rdi), %eax # MEM[(volatile __u32 *)dentry_3(D)], tmp71 andl $6291456, %eax #, tmp71 cmpl $2097152, %eax #, tmp71 jne .L1091 #,
Which with only one read of d_flags, one comparison and one jump is dramatically better code.
As __d_entry_type is not written to allow the compiler to optimize away anything that it does, when it is possible and reasonable to optimize things away the optimization needs to be performend manually.
--- include/linux/dcache.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
include/linux/dcache.h +2 -1
@@ -443,7 +443,8 @@ static inline bool d_is_autodir(const struct dentry *dentry)static inline bool d_is_dir (const struct dentry *dentry) { -return d_can_lookup (dentry) ||d_is_autodir (dentry); +unsigned type =__d_entry_type (dentry); +return (type ==DCACHE_DIRECTORY_TYPE ) || (type ==DCACHE_AUTODIR_TYPE ); }static inline bool d_is_symlink (const struct dentry *dentry)
Signature · 1 line
2.2.1
PATCH 7/8 mnt: Track when a directory escapes a bind mount
In reply to a message not in the archiveWhen bind mounts are in use, and there is another path to the filesystem it is possible to rename files or directories from a path underneath the root of the bind mount to a path that is not underneath the root of the bind mount.
When a directory is moved out from under the root of a bind mount path name lookups that go up the directory tree potentially allow accessing the entire dentry tree of the filesystem. This is not expected, not what is desired and winds up being a secruity problem for userspace.
Augment d_move, d_exchange to call d_common_ancestor and handle_possible_mount_escapes to mark any mount points that directories escape from.
A few notes on the implementation:
- d_splice_alias does not need to be touched as the only case that can result in a directory escaping calls d_move.
- Only directory escapes are recorded as only those are relevant to new pathname lookup. Escaped files are handled in prepend_path.
- A lock either namespace_sem or mount_lock needs to be held across the duration of renames where a directory could be escaping to ensure that a mount is not added, escaped, and missed during the rename.
- The mount_lock is used as it does not sleep. I have audited all of thecallers of d_move and d_exchange and in every instance it appears safe for d_move and d_exchange to start sleeping. But there is no point in adding sleeping behavior if that is unncessary.
- The locking order must be mount_lock outside of rename_lock as prepend_path already takes the locks in this order.
--- fs/dcache.c | 33 +++++++++++++++++++++++++++++++++ fs/mount.h | 2 ++ fs/namespace.c | 32 ++++++++++++++++++++++++++++++++ include/linux/mount.h | 1 + 4 files changed, 68 insertions(+)
fs/dcache.c +33 -0
@@ -2704,9 +2704,23 @@ static void __d_move(struct dentry *dentry, struct dentry *target,*/ void d_move (struct dentry *dentry,struct dentry *target) { +bool unlock = false; + +if (d_is_dir (dentry) && (dentry->d_parent != target->d_parent)) { +const struct dentry *ancestor; + + ancestor =d_common_ancestor (dentry, target); +read_seqlock_excl (&mount_lock); + unlock = true; +handle_possible_mount_escapee (ancestor, dentry); + } +write_seqlock (&rename_lock);__d_move (dentry, target, false);write_sequnlock (&rename_lock); +if (unlock) +read_sequnlock_excl (&mount_lock); + }EXPORT_SYMBOL (d_move);
@@ -2717,6 +2731,23 @@ EXPORT_SYMBOL(d_move);*/ void d_exchange (struct dentry *dentry1,struct dentry *dentry2) { +bool d1_is_dir =d_is_dir (dentry1); +bool d2_is_dir =d_is_dir (dentry2); +bool unlock = false; + +if ((d1_is_dir || d2_is_dir) && + (dentry1->d_parent != dentry2->d_parent)) { +const struct dentry *ancestor; + + ancestor =d_common_ancestor (dentry1, dentry2); +read_seqlock_excl (&mount_lock); + unlock = true; +if (d1_is_dir) +handle_possible_mount_escapee (ancestor, dentry1); +if (d2_is_dir) +handle_possible_mount_escapee (ancestor, dentry2); + } +write_seqlock (&rename_lock);WARN_ON (!dentry1->d_inode);
@@ -2727,6 +2758,8 @@ void d_exchange(struct dentry *dentry1, struct dentry *dentry2)__d_move (dentry1, dentry2, true);write_sequnlock (&rename_lock); +if (unlock) +read_sequnlock_excl (&mount_lock); }/**
fs/mount.h +2 -0
@@ -107,6 +107,8 @@ static inline void detach_mounts(struct dentry *dentry)__detach_mounts (dentry); } +extern void handle_possible_mount_escapee (const struct dentry *,struct dentry *); +static inline void get_mnt_ns (struct mnt_namespace *ns) {atomic_inc (&ns->count);
fs/namespace.c +32 -0
@@ -1657,6 +1657,38 @@ out_unlock:namespace_unlock (); } +static void mark_escaped_mounts (struct dentry *root) +{ +/* Must be called with mount_lock held */ +struct mountroot *mr; +struct mount *mnt; + + mr =lookup_mountroot (root); +if (mr) { +/* Mark each mount from which a directory is escaping. +*/ +hlist_for_each_entry (mnt, &mr->r_list, mnt_mr_list) + mnt->mnt.mnt_flags |=MNT_DIR_ESCAPED ; + } +} + +void handle_possible_mount_escapee (const struct dentry *ancestor, +struct dentry *escapee) +{ +struct dentry *dentry; + +for (dentry = escapee->d_parent; dentry != ancestor; + dentry = dentry->d_parent) { + +if (d_mountroot (dentry)) +mark_escaped_mounts (dentry); + +/* In case there is no common ancestor */ +if (IS_ROOT (dentry)) +break ; + } +} +/* * Is the caller allowed to modify his namespace? */
include/linux/mount.h +1 -0
@@ -62,6 +62,7 @@ struct mnt_namespace;#define MNT_SYNC_UMOUNT 0x2000000#define MNT_MARKED 0x4000000#define MNT_UMOUNT 0x8000000 +#define MNT_DIR_ESCAPED 0x10000000struct vfsmount {struct dentry *mnt_root;/* root of the mounted tree */
Signature · 1 line
2.2.1
PATCH 8/8 vfs: Test for and handle paths that are unreachable from their mnt_root
In reply to a message not in the archiveIn rare cases a directory can be renamed out from under a bind mount. In those cases without special handling it becomes possible to walk up the directory tree to the root dentry of the filesystem and down from the root dentry to every other file or directory on the filesystem.
Like division by zero .. from an unconnected path can not be given a useful semantic as there is no predicting at which path component the code will realize it is unconnected. We certainly can not match the current behavior as the current behavior is a security hole.
Therefore when encounting .. when following an unconnected path return -ENOENT.
- Add a function path_connected to verify nd->path.dentry is reachable from nd->path.mnt.mnt_root. AKA to validate that rename did not do something nasty to the bind mount.
To avoid races path_connected must be called after following a path component to it's next path component.
--- fs/namei.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-)
fs/namei.c +24 -2
@@ -560,6 +560,23 @@ static int __nd_alloc_stack(struct nameidata *nd)return 0 ; } +/** +* path_connected - Verify that a nd->path.dentry is below nd->path.mnt->mnt.mnt_root +* @nd: nameidate to verify +* +* Rename can sometimes move a file or directory outside of a bind +* mount, path_connected allows those cases to be detected. +*/ +static bool path_connected (const struct path *path) +{ +struct vfsmount *mnt = path->mnt; + +if (likely (!(mnt->mnt_flags &MNT_DIR_ESCAPED ))) +return true; + +return is_subdir (path->dentry, mnt->mnt_root); +} +static inline int nd_alloc_stack (struct nameidata *nd) {if (likely (nd->depth !=EMBEDDED_LEVELS ))
@@ -1296,6 +1313,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)return -ECHILD ; nd->path.dentry = parent; nd->seq = seq; +if (unlikely (!path_connected (&nd->path))) +return -ENOENT ;break ; }else {struct mount *mnt =real_mount (nd->path.mnt);
@@ -1396,7 +1415,7 @@ static void follow_mount(struct path *path) } } -static void follow_dotdot (struct nameidata *nd) +static int follow_dotdot (struct nameidata *nd) {if (!nd->root.mnt)set_root (nd);
@@ -1412,6 +1431,8 @@ static void follow_dotdot(struct nameidata *nd)/* rare case of legitimate dget_parent()... */ nd->path.dentry =dget_parent (nd->path.dentry);dput (old); +if (unlikely (!path_connected (&nd->path))) +return -ENOENT ;break ; }if (!follow_up (&nd->path))
@@ -1419,6 +1440,7 @@ static void follow_dotdot(struct nameidata *nd) }follow_mount (&nd->path); nd->inode = nd->path.dentry->d_inode; +return 0 ; }/*
@@ -1634,7 +1656,7 @@ static inline int handle_dots(struct nameidata *nd, int type)if (nd->flags &LOOKUP_RCU ) {return follow_dotdot_rcu (nd); }else -follow_dotdot (nd); +return follow_dotdot (nd); }return 0 ; }
Signature · 1 line
2.2.1
PATCH 1/8 dcache: Handle escaped paths in prepend_path
In reply to Eric W. BiedermanA rename can result in a dentry that by walking up d_parent will never reach it's mnt_root. For lack of a better term I call this an escaped path.
prepend_path is called by four different functions __d_path, d_absolute_path, d_path, and getcwd.
__d_path only wants to see paths are connected to the root it passes in. So __d_path needs prepend_path to return an error.
d_absolute_path similarly wants to see paths that are connected to some root. Escaped paths are not connected to any mnt_root so d_absolute_path needs prepend_path to return an error greater than 1. So escaped paths will be treated like paths on lazily unmounted mounts.
getcwd needs to prepend "(unreachable)" so getcwd also needs prepend_path to return an error.
d_path is the interesting hold out. d_path just wants to print something, and does not care about the weird cases. Which raises the question what should be printed?
Given that <escaped_path>/<anything> should result in -ENOENT I believe it is desirable for escaped paths to be printed as empty paths. As there are not really any meaninful path components when considered from the perspective of a mount tree.
So tweak prepend_path to return an empty path with an new error code of 3 when it encounters an escaped path.
--- fs/dcache.c | 7 +++++++ 1 file changed, 7 insertions(+)
fs/dcache.c +7 -0
@@ -2923,6 +2923,13 @@ restart:if (dentry == vfsmnt->mnt_root ||IS_ROOT (dentry)) {struct mount *parent =ACCESS_ONCE (mnt->mnt_parent); +/* Escaped? */ +if (dentry != vfsmnt->mnt_root) { + bptr = *buffer; + blen = *buflen; + error =3 ; +break ; + }/* Global root? */ if (mnt != parent) { dentry =ACCESS_ONCE (mnt->mnt_mountpoint);
Signature · 1 line
2.2.1
PATCH 2/8 dcache: Reduce the scope of i_lock in d_splice_alias
In reply to Eric W. Biedermani_lock is only needed until __d_find_any_alias calls dget on the alias dentry. After that the reference to new ensures that dentry_kill and d_delete will not remove the inode from the dentry, and remove the dentry from the inode->d_entry list.
The inode i_lock came to be held over the the __d_move calls in d_splice_alias through a series of introduction of locks with increasing smaller scope. First it was the dcache_lock, then it was the dcache_inode_lock, and finally inode->i_lock.
Furthermore inode->i_lock is not held over any other calls to d_move or __d_move so it can not provide any meaningful rename protection.
--- fs/dcache.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
fs/dcache.c +3 -4
@@ -2715,7 +2715,7 @@ struct dentry *d_ancestor(struct dentry *p1, struct dentry *p2)* This helper attempts to cope with remotely renamed directories * * It assumes that the caller is already holding -* dentry->d_parent->d_inode->i_mutex, inode->i_lock and rename_lock +* dentry->d_parent->d_inode->i_mutex, and rename_lock * * Note: If ever the locking in lock_rename() changes, then please * remember to update this too...
@@ -2741,7 +2741,6 @@ out_unalias:__d_move (alias, dentry, false); ret =0 ; out_err: -spin_unlock (&inode->i_lock);if (m2)mutex_unlock (m2);if (m1)
@@ -2787,10 +2786,11 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)if (S_ISDIR (inode->i_mode)) {struct dentry *new =__d_find_any_alias (inode);if (unlikely (new)) { +/* The reference to new ensures it remains an alias */ +spin_unlock (&inode->i_lock);write_seqlock (&rename_lock);if (unlikely (d_ancestor (new, dentry))) {write_sequnlock (&rename_lock); -spin_unlock (&inode->i_lock);dput (new); new =ERR_PTR (-ELOOP ); pr_warn_ratelimited(
@@ -2809,7 +2809,6 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry) }else {__d_move (new, dentry, false);write_sequnlock (&rename_lock); -spin_unlock (&inode->i_lock);security_d_instantiate (new, inode); }iput (inode);
Signature · 1 line
2.2.1
PATCH 3/8 dcache: Clearly separate the two directory rename cases in d_splice_alias
In reply to Eric W. BiedermanThere are two scenarios that can result in an alias being found in d_splice_alias. The first scenario is a disconnected dentry being looked up and reconnected (common with nfs file handles, and open_by_handle_at). The second scenario is a lookup on a directory lazily discovering that it was renamed on the remote filesystem.
The locking challenge for handling these two scenarios is that the instance of lookup calling d_splice_alias has already taken the dentry->d_parent->d_inode->i_mutex.
If the mutex was not held the locking we could just do:
rename_mutex = &inode->i_sb->s_vfs_rename_mutex;
mutex_lock(rename_mutex);
if (d_ancestor(alias, dentry)) {
mutex_unlock(rename_mutex);
pr_warn_ratelimited(...);
return -ELOOP;
}
m1 = &dentry->d_parent->d_inode->i_mutex;
mutex_lock_nested(m1, I_MUTEX_PARENT);
if (!IS_ROOT(alias) && (alias->d_parent != dentry->d_parent)) {
m2 = &alias->d_parent->d_inode->i_mutex;
mutex_lock_nested(m2, I_MUTEX_PARENT2);
}
d_move(alias, dentry);
if (m2)
mutex_unlock(m2);
mutex_unlock(m1);
mutex_unlock(rename_mutex);Which is essentially lock_rename and unlock_reaname with added handling of the IS_ROOT case.
The problem is that as the lookup locking stands today grabbing the s_vfs_rename_mutex must use mutex_trylock which can fail, so for reliability reasons we need to avoid using the rename_mutex as much as possible.
For the case where a disconnected dentry is being connected (aka the IS_ROOT case) the d_ancestor test can be placed under the rename_lock removing any chance that taking locks will cause a failure.
For the lazy rename case things are trickier because when dentry->d_parent and alias->d_parent are not equal the code need to take the s_vfs_rename_mutex, or risk the d_ancestor call in lock_rename being inaccurate. As s_vfs_rename_mutex is take first there are no locking ordering issues with taking alias->d_parent->d_inode->i_mutex. Furthermore as games with rename_lock are unnecessary d_move instead of __d_move can be called.
Sleeping in d_splice_alias is not something new as security_d_instantiate is a sleeping function.
Compared to the current implementation this change introduces a function for each case __d_rename_alias and __d_connect_alias and moves the acquisition of rename_lock down into those functions.
In the case of __d_rename_alias which was __d_unalias this allows the existing lock ordering rules to be followed much more closely removing the need for a trylock when acquiring alias->d_parent->d_inode->i_mutex, and allowing the use of d_move.
A common helper that prints the warning message when a loop is detected is factored out into d_alias_is_ancestor so that code does not need to be duplicated in both cases.
--- fs/dcache.c | 111 ++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 67 insertions(+), 44 deletions(-)
fs/dcache.c +67 -44
@@ -2711,41 +2711,77 @@ struct dentry *d_ancestor(struct dentry *p1, struct dentry *p2)return NULL ; } +static struct dentry *d_alias_is_ancestor (struct dentry *alias, +struct dentry *dentry) +{ +struct dentry *ancestor =d_ancestor (alias, dentry); +if (ancestor) { +pr_warn_ratelimited ( +"VFS: Lookup of '%s' in %s %s would have caused loop\n" , + dentry->d_name.name, + dentry->d_sb->s_type->name, + dentry->d_sb->s_id); + } +return ancestor; +} +/* * This helper attempts to cope with remotely renamed directories * * It assumes that the caller is already holding -* dentry->d_parent->d_inode->i_mutex, and rename_lock +* dentry->d_parent->d_inode->i_mutex * * Note: If ever the locking in lock_rename() changes, then please * remember to update this too... */ -static int __d_unalias (struct inode *inode, -struct dentry *dentry,struct dentry *alias) +static int __d_rename_alias (struct dentry *alias,struct dentry *dentry) { -struct mutex *m1 =NULL , *m2 =NULL ; -int ret = -ESTALE ; +struct mutex *rename_mutex =NULL , *parent_mutex =NULL ; -/* If alias and dentry share a parent, then no extra locks required */ -if (alias->d_parent == dentry->d_parent) - goto out_unalias; +/* Holding dentry->d_parent->d_inode->i_mutex guarantees this +* that the equality or inequality alias->d_parent and +* dentry->d_parent remains stable. +*/ +if (alias->d_parent != dentry->d_parent) { +/* See lock_rename() */ + rename_mutex = &dentry->d_sb->s_vfs_rename_mutex; +if (!mutex_trylock (rename_mutex)) +return -ESTALE ; + +if (d_alias_is_ancestor (alias, dentry)) { +mutex_unlock (rename_mutex); +return -ELOOP ; + } + parent_mutex = &alias->d_parent->d_inode->i_mutex; +mutex_lock_nested (parent_mutex,I_MUTEX_PARENT2 ); + } +d_move (alias, dentry); +if (parent_mutex) { +mutex_unlock (parent_mutex); +mutex_unlock (rename_mutex); + } +return 0 ; +} -/* See lock_rename() */ -if (!mutex_trylock (&dentry->d_sb->s_vfs_rename_mutex)) - goto out_err; - m1 = &dentry->d_sb->s_vfs_rename_mutex; -if (!mutex_trylock (&alias->d_parent->d_inode->i_mutex)) - goto out_err; - m2 = &alias->d_parent->d_inode->i_mutex; -out_unalias: +/* +* This helper connects disconnected dentries. +* +* It assumes that the caller is already holding +* dentry->d_parent->d_inode->i_mutex +* +*/ +static int __d_connect_alias (struct inode *inode, +struct dentry *alias,struct dentry *dentry) +{ +write_seqlock (&rename_lock); +if (unlikely (d_alias_is_ancestor (alias, dentry))) { +write_sequnlock (&rename_lock); +return -ELOOP ; + }__d_move (alias, dentry, false); - ret =0 ; -out_err: -if (m2) -mutex_unlock (m2); -if (m1) -mutex_unlock (m1); -return ret; +write_sequnlock (&rename_lock); +security_d_instantiate (alias, inode); +return 0 ; }/**
@@ -2786,30 +2822,17 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)if (S_ISDIR (inode->i_mode)) {struct dentry *new =__d_find_any_alias (inode);if (unlikely (new)) { +int err;/* The reference to new ensures it remains an alias */ spin_unlock (&inode->i_lock); -write_seqlock (&rename_lock); -if (unlikely (d_ancestor (new, dentry))) { -write_sequnlock (&rename_lock); + +if (!IS_ROOT (new)) + err =__d_rename_alias (new, dentry); +else + err =__d_connect_alias (inode, new, dentry); +if (err) {dput (new); - new =ERR_PTR (-ELOOP ); -pr_warn_ratelimited ( -"VFS: Lookup of '%s' in %s %s" -" would have caused loop\n" , - dentry->d_name.name, - inode->i_sb->s_type->name, - inode->i_sb->s_id); - }else if (!IS_ROOT (new)) { -int err =__d_unalias (inode, dentry, new); -write_sequnlock (&rename_lock); -if (err) { -dput (new); - new =ERR_PTR (err); - } - }else { -__d_move (new, dentry, false); -write_sequnlock (&rename_lock); -security_d_instantiate (new, inode); + new =ERR_PTR (err); }iput (inode);return new;
Signature · 1 line
2.2.1
A message that is not in the archive (sent off-list or not yet received): 87fv3mjxsc.fsf_-_-JOvCrm2gF+uungPnsOpG7nhyD016LWXt@public.gmane.org
Re: [PATCH review 3/8] dcache: Clearly separate the two directory rename cases in d_splice_alias
Quoted 3 lines · Eric W. Biederman
The problem is that as the lookup locking stands today grabbing the
s_vfs_rename_mutexmust usemutex_trylockwhich can fail, so for reliability reasons we need to avoid using therename_mutexas much as possible.
I really don't like it. For one thing, you *still* are taking it - have to, really. So this argument is moot anyway. ESTALE can happen here. For another, I'm not convinced that this "we don't need no stinkin' extra locks for attaching a detached subtree" is correct. E.g. what's to protect that IS_ROOT(new) from changing right under you?
Consider a corrupted filesystem (or a bogus server, or a sufficiently unpleasant race with another client, etc.)
You have a detached subtree with lookups from *TWO* directories trying to attach its root. Now what? ->i_mutex on either prospective parent won't give a damn thing - different inodes. The current tree would've serialized those on rename_lock and treated that as attach a detached followed by relocate a misplaced. With your change we get a race in there.
This place is subtle and nasty, and we had rather nasty races there. Repeatedly. Any non-trivial locking changes in that area should go separately from everything else and only with an accurate analysis of those changes. It's one of the easiest places to fuck up in. Been there, done that, and so had many other people.
I'm not saying that it wouldn't benefit from cleaner locking - it sure as hell would. But it's in a really incestous relationship with a lot of other pieces, both in fs/dcache.c and elsewhere. Let's not mix that into anything else - driveby cleanups in that place are very likely to cause serious trouble.
A message that is not in the archive (sent off-list or not yet received): 20150815061617.GG14139-3bDd1+5oDREiFSDQTTA3OLVCufUGDwFn@public.gmane.org
Re: [PATCH review 3/8] dcache: Clearly separate the two directory rename cases in d_splice_alias
Quoted 8 lines · Al Viro
Quoted 3 lines · Eric W. Biederman
The problem is that as the lookup locking stands today grabbing the
s_vfs_rename_mutexmust usemutex_trylockwhich can fail, so for reliability reasons we need to avoid using therename_mutexas much as possible.I really don't like it. For one thing, you *still* are taking it - have to, really. So this argument is moot anyway.
Not moot. We don't take s_vfs_rename_mutex when we are connecting a disconnected dentry alias. If d_splice_alias could sleep and take s_vfs_rename_mutex then we could have a single path through the code.
Unfortunately the code can't sleep when taking s_vfs_rename_mutex so attempting to take s_vfs_rename_mutex for both paths will introduce unnecessary -ESTALE failures into d_splice_alias.
Quoted 4 lines
ESTALE can happen here. For
another, I'm not convinced that this "we don't need no stinkin' extra locks for attaching a detached subtree" is correct. E.g. what's to protect thatIS_ROOT(new)from changing right under you?
You are quite correct that I missed that nothing protects the result ofIS_ROOT(new). So my change does introduce a case where we don't
hold the appropriate inode mutexes when renaming a dentry and that introduces races elsewhere in the code so it is not acceptable.
But it is true that we only take rename_lock and don't take any additional mutex when connecting a disconnected dentry. Aka "we don't need no stinkin' extra locks". We clearly can not take the new->d_parent->d_inode->i_mutex when IS_ROOT(new) as that is meaningless. Further I do not see a point in taking s_vfs_rename_mutex in that case.
Not for this round but if you can see any reason why our not taking s_vfs_rename_mutex when connecting disconnected dentries is wrong and we need to take it and risk -ESTALE. I would love to know because I would love to clean up that mess.
Quoted 5 lines
I'm not saying that it wouldn't benefit from cleaner locking - it sure as hell would. But it's in a really incestous relationship with a lot of other pieces, both in
fs/dcache.cand elsewhere. Let's not mix that into anything else - driveby cleanups in that place are very likely to cause serious trouble.
Fair enough.
I do have to touch d_splice_alias and change the locking, so unfortunately I have to do some of the nasty locking analysis anyway.
I am keeping the i_lock cleanup because it is trivial and removes the need to figure out if there is any existing ordering between i_lock and mount_lock, and if taking mount_lock could induce a deadlock. That audit would not be trivial.
Eric
A message that is not in the archive (sent off-list or not yet received): [email protected]
cover Bind mount escape fixes
It is possible in some situations to rename a file or directory through one mount point such that it can start out inside of a bind mount and after the rename wind up outside of the bind mount. Unfortunately with user namespaces these conditions can be trivially created by creating a bind mount under an existing bind mount.
I have identified four situations in which this may be a problem.
- __d_path and d_absolute_path need to error on disconnected paths that can not reach some root directory or lsm path based security checks can incorrectly succeed.
- Normal path name resolution following .. can lead to a directory that is outside of the original loopback mount.
- file handle reconsititution aka exportfs_decode_fh can yield a dentry from which d_parent can be followed up to mnt->sb->s_root, but d_parent can not be followed up to mnt->mnt_root.
- Mounts on a path that has been renamed outside of a loopback mount become unreachable, as there is no possible path that can be passed to umount to unmount them.
My strategy:
o File handle reconsitituion problems can be prevented by enabling the nfsd subtree checks for nfs exports, and open_by_handle_at requires capable(CAP_DAC_READ_SEARCH) so is only usable by the global root. This makes any problems difficult if not impossible to exploit in practice so I have not yet written code to address that issue.
o The functions __d_path and d_absolute_path are agumented so that the security modules will not be fed a problematic path to work with.
o Following of .. has been agumented to test that after d_parent has been resolved the original directory is connected, and if not an error of -ENOENT is returned.
o I do not worry about mounts that are disconnected from their bind mount as these mounts can always be freed by either umount -l on the bind mount they have escaped from, or by freeing the mount namespace. So I do not believe there is an actual problem.
Pathname resolution is a common fast path and most of the code in this patchset to support keeping .. from becoming expensive in the common case.
After hearing the Al's feedback and running some numbers I have given up attempting to keeping the number of d_ancestor calls during pathname resolution to an absolute minimum. It appears that simply preventing calls d_ancestor unless a directory has escaped is good enough. This change in approach has significantly simplified the code.
The implementation change this round is I have dropped my patch cleaning up d_splice_alias. Al Viro found a race that makes the technique I was using fundamentally racy. I now have d_splice_alias taking mount_lock around rename_lock. Since I don't have to sleep in d_splice_alias change is minimal and sufficient for this purpose.
Barring some other idiocy I think this will be the final version of this patchset.
These changes are all against v4.2-rc1.
For those who like to see everything in a single tree the code is at:
git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace.git for-testing
Eric W. Biederman (7):
dcache: Handle escaped paths in prepend_path
dcache: Reduce the scope of i_lock in d_splice_alias
mnt: Track which mounts use a dentry as root.
dcache: Implement d_common_ancestor
dcache: Only read d_flags once in d_is_dir
mnt: Track when a directory escapes a bind mount
vfs: Test for and handle paths that are unreachable from their mnt_root
fs/dcache.c | 91 +++++++++++++++++++++++++++-- fs/mount.h | 9 +++ fs/namei.c | 26 ++++++++- fs/namespace.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 11 +++- include/linux/mount.h | 1 + 6 files changed, 279 insertions(+), 11 deletions(-)
A message that is not in the archive (sent off-list or not yet received): 87a8ts763c.fsf_-_-JOvCrm2gF+uungPnsOpG7nhyD016LWXt@public.gmane.org
PATCH 1/7 dcache: Handle escaped paths in prepend_path
A rename can result in a dentry that by walking up d_parent will never reach it's mnt_root. For lack of a better term I call this an escaped path.
prepend_path is called by four different functions __d_path, d_absolute_path, d_path, and getcwd.
__d_path only wants to see paths are connected to the root it passes in. So __d_path needs prepend_path to return an error.
d_absolute_path similarly wants to see paths that are connected to some root. Escaped paths are not connected to any mnt_root so d_absolute_path needs prepend_path to return an error greater than 1. So escaped paths will be treated like paths on lazily unmounted mounts.
getcwd needs to prepend "(unreachable)" so getcwd also needs prepend_path to return an error.
d_path is the interesting hold out. d_path just wants to print something, and does not care about the weird cases. Which raises the question what should be printed?
Given that <escaped_path>/<anything> should result in -ENOENT I believe it is desirable for escaped paths to be printed as empty paths. As there are not really any meaninful path components when considered from the perspective of a mount tree.
So tweak prepend_path to return an empty path with an new error code of 3 when it encounters an escaped path.
--- fs/dcache.c | 7 +++++++ 1 file changed, 7 insertions(+)
fs/dcache.c +7 -0
@@ -2923,6 +2923,13 @@ restart:if (dentry == vfsmnt->mnt_root ||IS_ROOT (dentry)) {struct mount *parent =ACCESS_ONCE (mnt->mnt_parent); +/* Escaped? */ +if (dentry != vfsmnt->mnt_root) { + bptr = *buffer; + blen = *buflen; + error =3 ; +break ; + }/* Global root? */ if (mnt != parent) { dentry =ACCESS_ONCE (mnt->mnt_mountpoint);
Signature · 1 line
2.2.1
PATCH 2/7 dcache: Reduce the scope of i_lock in d_splice_alias
In reply to a message not in the archivei_lock is only needed until __d_find_any_alias calls dget on the alias dentry. After that the reference to new ensures that dentry_kill and d_delete will not remove the inode from the dentry, and remove the dentry from the inode->d_entry list.
The inode i_lock came to be held over the the __d_move calls in d_splice_alias through a series of introduction of locks with increasing smaller scope. First it was the dcache_lock, then it was the dcache_inode_lock, and finally inode->i_lock.
Furthermore inode->i_lock is not held over any other calls to d_move or __d_move so it can not provide any meaningful rename protection.
--- fs/dcache.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
fs/dcache.c +3 -4
@@ -2715,7 +2715,7 @@ struct dentry *d_ancestor(struct dentry *p1, struct dentry *p2)* This helper attempts to cope with remotely renamed directories * * It assumes that the caller is already holding -* dentry->d_parent->d_inode->i_mutex, inode->i_lock and rename_lock +* dentry->d_parent->d_inode->i_mutex, and rename_lock * * Note: If ever the locking in lock_rename() changes, then please * remember to update this too...
@@ -2741,7 +2741,6 @@ out_unalias:__d_move (alias, dentry, false); ret =0 ; out_err: -spin_unlock (&inode->i_lock);if (m2)mutex_unlock (m2);if (m1)
@@ -2787,10 +2786,11 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)if (S_ISDIR (inode->i_mode)) {struct dentry *new =__d_find_any_alias (inode);if (unlikely (new)) { +/* The reference to new ensures it remains an alias */ +spin_unlock (&inode->i_lock);write_seqlock (&rename_lock);if (unlikely (d_ancestor (new, dentry))) {write_sequnlock (&rename_lock); -spin_unlock (&inode->i_lock);dput (new); new =ERR_PTR (-ELOOP ); pr_warn_ratelimited(
@@ -2809,7 +2809,6 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry) }else {__d_move (new, dentry, false);write_sequnlock (&rename_lock); -spin_unlock (&inode->i_lock);security_d_instantiate (new, inode); }iput (inode);
Signature · 1 line
2.2.1
PATCH 3/7 mnt: Track which mounts use a dentry as root.
In reply to a message not in the archiveThis is needed infrastructure for better handling of when files or directories are moved out from under the root of a bind mount.
--- fs/mount.h | 7 +++ fs/namespace.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 7 +++ 3 files changed, 130 insertions(+), 4 deletions(-)
fs/mount.h +7 -0
@@ -27,6 +27,12 @@ struct mountpoint {int m_count; }; +struct mountroot { +struct hlist_node r_hash; +struct dentry *r_dentry; +struct hlist_head r_list; +}; +struct mount {struct hlist_node mnt_hash;struct mount *mnt_parent;
@@ -55,6 +61,7 @@ struct mount {struct mnt_namespace *mnt_ns;/* containing namespace */ struct mountpoint *mnt_mp;/* where is it mounted */ struct hlist_node mnt_mp_list;/* list mounts with the same mountpoint */ +struct hlist_node mnt_mr_list;/* list mounts with the same mountroot */ #ifdef CONFIG_FSNOTIFY struct hlist_head mnt_fsnotify_marks;__u32 mnt_fsnotify_mask;
fs/namespace.c +116 -4
200 lines not on this page · raw message
include/linux/dcache.h +7 -0
@@ -228,6 +228,8 @@ struct dentry_operations {#define DCACHE_FALLTHRU 0x01000000/* Fall through to lower layer */ #define DCACHE_OP_SELECT_INODE 0x02000000/* Unioned entry: dcache op selects inode */ +#define DCACHE_MOUNTROOT 0x04000000/* Root of a vfsmount */ +extern seqlock_t rename_lock;/*
@@ -403,6 +405,11 @@ static inline bool d_mountpoint(const struct dentry *dentry)return dentry->d_flags &DCACHE_MOUNTED ; } +static inline bool d_mountroot (const struct dentry *dentry) +{ +return dentry->d_flags &DCACHE_MOUNTROOT ; +} +/* * Directory cache entry type accessor functions. */
Signature · 1 line
2.2.1
PATCH 4/7 dcache: Implement d_common_ancestor
In reply to a message not in the archiveIf possible find the common ancestor of two dentries.
This is necessary infrastructure for better handling the case when a dentry is moved out from under the root of a bind mount.
--- fs/dcache.c | 37 +++++++++++++++++++++++++++++++++++++ include/linux/dcache.h | 1 + 2 files changed, 38 insertions(+)
fs/dcache.c +37 -0
@@ -2469,6 +2469,43 @@ void dentry_update_name_case(struct dentry *dentry, struct qstr *name) }EXPORT_SYMBOL (dentry_update_name_case); +static unsigned long d_depth (const struct dentry *dentry) +{ +unsigned long depth =0 ; + +while (!IS_ROOT (dentry)) { + dentry = dentry->d_parent; + depth++; + } +return depth; +} + +const struct dentry *d_common_ancestor (const struct dentry *left, +const struct dentry *right) +{ +unsigned long ldepth =d_depth (left); +unsigned long rdepth =d_depth (right); + +while (ldepth > rdepth) { + left = left->d_parent; + ldepth--; + } + +while (rdepth > ldepth) { + right = right->d_parent; + rdepth--; + } + +while (left != right) { +if (IS_ROOT (left)) +return NULL ; + left = left->d_parent; + right = right->d_parent; + } + +return left; +} +static void swap_names (struct dentry *dentry,struct dentry *target) {if (unlikely (dname_external (target))) {
include/linux/dcache.h +1 -0
@@ -313,6 +313,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *);extern void d_move (struct dentry *,struct dentry *);extern void d_exchange (struct dentry *,struct dentry *);extern struct dentry *d_ancestor (struct dentry *,struct dentry *); +extern const struct dentry *d_common_ancestor (const struct dentry *,const struct dentry *);/* appendix may either be NULL or be used for transname suffixes */ extern struct dentry *d_lookup (const struct dentry *,const struct qstr *);
Signature · 1 line
2.2.1