aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric W. Biederman <ebiederm@xmission.com>2013-10-04 22:15:13 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2014-10-09 02:38:54 -0400
commit7af1364ffa64db61e386628594836e13d2ef04b5 (patch)
tree45f295668d231d1c1a7a063d16195035cac467cf
parentbafc9b754f752ea798c39f9b099a228fd56604e0 (diff)
vfs: Don't allow overwriting mounts in the current mount namespace
In preparation for allowing mountpoints to be renamed and unlinked in remote filesystems and in other mount namespaces test if on a dentry there is a mount in the local mount namespace before allowing it to be renamed or unlinked. The primary motivation here are old versions of fusermount unmount which is not safe if the a path can be renamed or unlinked while it is verifying the mount is safe to unmount. More recent versions are simpler and safer by simply using UMOUNT_NOFOLLOW when unmounting a mount in a directory owned by an arbitrary user. Miklos Szeredi <miklos@szeredi.hu> reports this is approach is good enough to remove concerns about new kernels mixed with old versions of fusermount. A secondary motivation for restrictions here is that it removing empty directories that have non-empty mount points on them appears to violate the rule that rmdir can not remove empty directories. As Linus Torvalds pointed out this is useful for programs (like git) that test if a directory is empty with rmdir. Therefore this patch arranges to enforce the existing mount point semantics for local mount namespace. v2: Rewrote the test to be a drop in replacement for d_mountpoint v3: Use bool instead of int as the return type of is_local_mountpoint Reviewed-by: Miklos Szeredi <miklos@szeredi.hu> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
-rw-r--r--fs/mount.h9
-rw-r--r--fs/namei.c6
-rw-r--r--fs/namespace.c35
3 files changed, 49 insertions, 1 deletions
diff --git a/fs/mount.h b/fs/mount.h
index 8f2a14ae38a2..8c6a2a651254 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -115,3 +115,12 @@ struct proc_mounts {
115#define proc_mounts(p) (container_of((p), struct proc_mounts, m)) 115#define proc_mounts(p) (container_of((p), struct proc_mounts, m))
116 116
117extern const struct seq_operations mounts_op; 117extern const struct seq_operations mounts_op;
118
119extern bool __is_local_mountpoint(struct dentry *dentry);
120static inline bool is_local_mountpoint(struct dentry *dentry)
121{
122 if (!d_mountpoint(dentry))
123 return false;
124
125 return __is_local_mountpoint(dentry);
126}
diff --git a/fs/namei.c b/fs/namei.c
index a7b05bf82d31..a3a14b033b0d 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3565,6 +3565,8 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry)
3565 mutex_lock(&dentry->d_inode->i_mutex); 3565 mutex_lock(&dentry->d_inode->i_mutex);
3566 3566
3567 error = -EBUSY; 3567 error = -EBUSY;
3568 if (is_local_mountpoint(dentry))
3569 goto out;
3568 if (d_mountpoint(dentry)) 3570 if (d_mountpoint(dentry))
3569 goto out; 3571 goto out;
3570 3572
@@ -3681,7 +3683,7 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegate
3681 return -EPERM; 3683 return -EPERM;
3682 3684
3683 mutex_lock(&target->i_mutex); 3685 mutex_lock(&target->i_mutex);
3684 if (d_mountpoint(dentry)) 3686 if (is_local_mountpoint(dentry) || d_mountpoint(dentry))
3685 error = -EBUSY; 3687 error = -EBUSY;
3686 else { 3688 else {
3687 error = security_inode_unlink(dir, dentry); 3689 error = security_inode_unlink(dir, dentry);
@@ -4126,6 +4128,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
4126 mutex_lock(&target->i_mutex); 4128 mutex_lock(&target->i_mutex);
4127 4129
4128 error = -EBUSY; 4130 error = -EBUSY;
4131 if (is_local_mountpoint(old_dentry) || is_local_mountpoint(new_dentry))
4132 goto out;
4129 if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry)) 4133 if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry))
4130 goto out; 4134 goto out;
4131 4135
diff --git a/fs/namespace.c b/fs/namespace.c
index 044134315f93..77ffdb82f63f 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -667,6 +667,41 @@ struct vfsmount *lookup_mnt(struct path *path)
667 return m; 667 return m;
668} 668}
669 669
670/*
671 * __is_local_mountpoint - Test to see if dentry is a mountpoint in the
672 * current mount namespace.
673 *
674 * The common case is dentries are not mountpoints at all and that
675 * test is handled inline. For the slow case when we are actually
676 * dealing with a mountpoint of some kind, walk through all of the
677 * mounts in the current mount namespace and test to see if the dentry
678 * is a mountpoint.
679 *
680 * The mount_hashtable is not usable in the context because we
681 * need to identify all mounts that may be in the current mount
682 * namespace not just a mount that happens to have some specified
683 * parent mount.
684 */
685bool __is_local_mountpoint(struct dentry *dentry)
686{
687 struct mnt_namespace *ns = current->nsproxy->mnt_ns;
688 struct mount *mnt;
689 bool is_covered = false;
690
691 if (!d_mountpoint(dentry))
692 goto out;
693
694 down_read(&namespace_sem);
695 list_for_each_entry(mnt, &ns->list, mnt_list) {
696 is_covered = (mnt->mnt_mountpoint == dentry);
697 if (is_covered)
698 break;
699 }
700 up_read(&namespace_sem);
701out:
702 return is_covered;
703}
704
670static struct mountpoint *new_mountpoint(struct dentry *dentry) 705static struct mountpoint *new_mountpoint(struct dentry *dentry)
671{ 706{
672 struct hlist_head *chain = mp_hash(dentry); 707 struct hlist_head *chain = mp_hash(dentry);