diff options
Diffstat (limited to 'fs/namespace.c')
| -rw-r--r-- | fs/namespace.c | 96 | 
1 files changed, 65 insertions, 31 deletions
diff --git a/fs/namespace.c b/fs/namespace.c index 85b5f7bea82e..a28e4db075ed 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1669,7 +1669,7 @@ int ksys_umount(char __user *name, int flags)  	struct path path;  	struct mount *mnt;  	int retval; -	int lookup_flags = 0; +	int lookup_flags = LOOKUP_MOUNTPOINT;  	if (flags & ~(MNT_FORCE | MNT_DETACH | MNT_EXPIRE | UMOUNT_NOFOLLOW))  		return -EINVAL; @@ -1680,7 +1680,7 @@ int ksys_umount(char __user *name, int flags)  	if (!(flags & UMOUNT_NOFOLLOW))  		lookup_flags |= LOOKUP_FOLLOW; -	retval = user_path_mountpoint_at(AT_FDCWD, name, lookup_flags, &path); +	retval = user_path_at(AT_FDCWD, name, lookup_flags, &path);  	if (retval)  		goto out;  	mnt = real_mount(path.mnt); @@ -2697,45 +2697,32 @@ static int do_move_mount_old(struct path *path, const char *old_name)  /*   * add a mount into a namespace's mount tree   */ -static int do_add_mount(struct mount *newmnt, struct path *path, int mnt_flags) +static int do_add_mount(struct mount *newmnt, struct mountpoint *mp, +			struct path *path, int mnt_flags)  { -	struct mountpoint *mp; -	struct mount *parent; -	int err; +	struct mount *parent = real_mount(path->mnt);  	mnt_flags &= ~MNT_INTERNAL_FLAGS; -	mp = lock_mount(path); -	if (IS_ERR(mp)) -		return PTR_ERR(mp); - -	parent = real_mount(path->mnt); -	err = -EINVAL;  	if (unlikely(!check_mnt(parent))) {  		/* that's acceptable only for automounts done in private ns */  		if (!(mnt_flags & MNT_SHRINKABLE)) -			goto unlock; +			return -EINVAL;  		/* ... and for those we'd better have mountpoint still alive */  		if (!parent->mnt_ns) -			goto unlock; +			return -EINVAL;  	}  	/* Refuse the same filesystem on the same mount point */ -	err = -EBUSY;  	if (path->mnt->mnt_sb == newmnt->mnt.mnt_sb &&  	    path->mnt->mnt_root == path->dentry) -		goto unlock; +		return -EBUSY; -	err = -EINVAL;  	if (d_is_symlink(newmnt->mnt.mnt_root)) -		goto unlock; +		return -EINVAL;  	newmnt->mnt.mnt_flags = mnt_flags; -	err = graft_tree(newmnt, parent, mp); - -unlock: -	unlock_mount(mp); -	return err; +	return graft_tree(newmnt, parent, mp);  }  static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags); @@ -2748,6 +2735,7 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,  			   unsigned int mnt_flags)  {  	struct vfsmount *mnt; +	struct mountpoint *mp;  	struct super_block *sb = fc->root->d_sb;  	int error; @@ -2768,7 +2756,13 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,  	mnt_warn_timestamp_expiry(mountpoint, mnt); -	error = do_add_mount(real_mount(mnt), mountpoint, mnt_flags); +	mp = lock_mount(mountpoint); +	if (IS_ERR(mp)) { +		mntput(mnt); +		return PTR_ERR(mp); +	} +	error = do_add_mount(real_mount(mnt), mp, mountpoint, mnt_flags); +	unlock_mount(mp);  	if (error < 0)  		mntput(mnt);  	return error; @@ -2829,23 +2823,63 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,  int finish_automount(struct vfsmount *m, struct path *path)  { -	struct mount *mnt = real_mount(m); +	struct dentry *dentry = path->dentry; +	struct mountpoint *mp; +	struct mount *mnt;  	int err; + +	if (!m) +		return 0; +	if (IS_ERR(m)) +		return PTR_ERR(m); + +	mnt = real_mount(m);  	/* The new mount record should have at least 2 refs to prevent it being  	 * expired before we get a chance to add it  	 */  	BUG_ON(mnt_get_count(mnt) < 2);  	if (m->mnt_sb == path->mnt->mnt_sb && -	    m->mnt_root == path->dentry) { +	    m->mnt_root == dentry) {  		err = -ELOOP; -		goto fail; +		goto discard;  	} -	err = do_add_mount(mnt, path, path->mnt->mnt_flags | MNT_SHRINKABLE); -	if (!err) -		return 0; -fail: +	/* +	 * we don't want to use lock_mount() - in this case finding something +	 * that overmounts our mountpoint to be means "quitely drop what we've +	 * got", not "try to mount it on top". +	 */ +	inode_lock(dentry->d_inode); +	namespace_lock(); +	if (unlikely(cant_mount(dentry))) { +		err = -ENOENT; +		goto discard_locked; +	} +	rcu_read_lock(); +	if (unlikely(__lookup_mnt(path->mnt, dentry))) { +		rcu_read_unlock(); +		err = 0; +		goto discard_locked; +	} +	rcu_read_unlock(); +	mp = get_mountpoint(dentry); +	if (IS_ERR(mp)) { +		err = PTR_ERR(mp); +		goto discard_locked; +	} + +	err = do_add_mount(mnt, mp, path, path->mnt->mnt_flags | MNT_SHRINKABLE); +	unlock_mount(mp); +	if (unlikely(err)) +		goto discard; +	mntput(m); +	return 0; + +discard_locked: +	namespace_unlock(); +	inode_unlock(dentry->d_inode); +discard:  	/* remove m from any expiration list it may be on */  	if (!list_empty(&mnt->mnt_expire)) {  		namespace_lock();  |