Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2db154b3 authored by David Howells's avatar David Howells Committed by Al Viro
Browse files

vfs: syscall: Add move_mount(2) to move mounts around



Add a move_mount() system call that will move a mount from one place to
another and, in the next commit, allow to attach an unattached mount tree.

The new system call looks like the following:

	int move_mount(int from_dfd, const char *from_path,
		       int to_dfd, const char *to_path,
		       unsigned int flags);

Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
cc: linux-api@vger.kernel.org
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent a07b2000
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -399,7 +399,8 @@
385	i386	io_pgetevents		sys_io_pgetevents_time32	__ia32_compat_sys_io_pgetevents
386	i386	rseq			sys_rseq			__ia32_sys_rseq
387	i386	open_tree		sys_open_tree			__ia32_sys_open_tree
# don't use numbers 388 through 392, add new calls at the end
388	i386	move_mount		sys_move_mount			__ia32_sys_move_mount
# don't use numbers 389 through 392, add new calls at the end
393	i386	semget			sys_semget    			__ia32_sys_semget
394	i386	semctl			sys_semctl    			__ia32_compat_sys_semctl
395	i386	shmget			sys_shmget    			__ia32_sys_shmget
+1 −0
Original line number Diff line number Diff line
@@ -344,6 +344,7 @@
333	common	io_pgetevents		__x64_sys_io_pgetevents
334	common	rseq			__x64_sys_rseq
335	common	open_tree		__x64_sys_open_tree
336	common	move_mount		__x64_sys_move_mount
# don't use numbers 387 through 423, add new calls after the last
# 'common' entry
424	common	pidfd_send_signal	__x64_sys_pidfd_send_signal
+95 −31
Original line number Diff line number Diff line
@@ -2539,72 +2539,81 @@ static inline int tree_contains_unbindable(struct mount *mnt)
	return 0;
}

static int do_move_mount(struct path *path, const char *old_name)
static int do_move_mount(struct path *old_path, struct path *new_path)
{
	struct path old_path, parent_path;
	struct path parent_path = {.mnt = NULL, .dentry = NULL};
	struct mount *p;
	struct mount *old;
	struct mountpoint *mp;
	int err;
	if (!old_name || !*old_name)
		return -EINVAL;
	err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
	if (err)
		return err;

	mp = lock_mount(path);
	err = PTR_ERR(mp);
	mp = lock_mount(new_path);
	if (IS_ERR(mp))
		goto out;
		return PTR_ERR(mp);

	old = real_mount(old_path.mnt);
	p = real_mount(path->mnt);
	old = real_mount(old_path->mnt);
	p = real_mount(new_path->mnt);

	err = -EINVAL;
	if (!check_mnt(p) || !check_mnt(old))
		goto out1;
		goto out;

	if (old->mnt.mnt_flags & MNT_LOCKED)
		goto out1;
	if (!mnt_has_parent(old))
		goto out;

	err = -EINVAL;
	if (old_path.dentry != old_path.mnt->mnt_root)
		goto out1;
	if (old->mnt.mnt_flags & MNT_LOCKED)
		goto out;

	if (!mnt_has_parent(old))
		goto out1;
	if (old_path->dentry != old_path->mnt->mnt_root)
		goto out;

	if (d_is_dir(path->dentry) !=
	      d_is_dir(old_path.dentry))
		goto out1;
	if (d_is_dir(new_path->dentry) !=
	    d_is_dir(old_path->dentry))
		goto out;
	/*
	 * Don't move a mount residing in a shared parent.
	 */
	if (IS_MNT_SHARED(old->mnt_parent))
		goto out1;
		goto out;
	/*
	 * Don't move a mount tree containing unbindable mounts to a destination
	 * mount which is shared.
	 */
	if (IS_MNT_SHARED(p) && tree_contains_unbindable(old))
		goto out1;
		goto out;
	err = -ELOOP;
	for (; mnt_has_parent(p); p = p->mnt_parent)
		if (p == old)
			goto out1;
			goto out;

	err = attach_recursive_mnt(old, real_mount(path->mnt), mp, &parent_path);
	err = attach_recursive_mnt(old, real_mount(new_path->mnt), mp,
				   &parent_path);
	if (err)
		goto out1;
		goto out;

	/* if the mount is moved, it should no longer be expire
	 * automatically */
	list_del_init(&old->mnt_expire);
out1:
	unlock_mount(mp);
out:
	unlock_mount(mp);
	if (!err)
		path_put(&parent_path);
	return err;
}

static int do_move_mount_old(struct path *path, const char *old_name)
{
	struct path old_path;
	int err;

	if (!old_name || !*old_name)
		return -EINVAL;

	err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
	if (err)
		return err;

	err = do_move_mount(&old_path, path);
	path_put(&old_path);
	return err;
}
@@ -3050,7 +3059,7 @@ long do_mount(const char *dev_name, const char __user *dir_name,
	else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
		retval = do_change_type(&path, flags);
	else if (flags & MS_MOVE)
		retval = do_move_mount(&path, dev_name);
		retval = do_move_mount_old(&path, dev_name);
	else
		retval = do_new_mount(&path, type_page, sb_flags, mnt_flags,
				      dev_name, data_page);
@@ -3278,6 +3287,61 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
	return ksys_mount(dev_name, dir_name, type, flags, data);
}

/*
 * Move a mount from one place to another.
 *
 * Note the flags value is a combination of MOVE_MOUNT_* flags.
 */
SYSCALL_DEFINE5(move_mount,
		int, from_dfd, const char *, from_pathname,
		int, to_dfd, const char *, to_pathname,
		unsigned int, flags)
{
	struct path from_path, to_path;
	unsigned int lflags;
	int ret = 0;

	if (!may_mount())
		return -EPERM;

	if (flags & ~MOVE_MOUNT__MASK)
		return -EINVAL;

	/* If someone gives a pathname, they aren't permitted to move
	 * from an fd that requires unmount as we can't get at the flag
	 * to clear it afterwards.
	 */
	lflags = 0;
	if (flags & MOVE_MOUNT_F_SYMLINKS)	lflags |= LOOKUP_FOLLOW;
	if (flags & MOVE_MOUNT_F_AUTOMOUNTS)	lflags |= LOOKUP_AUTOMOUNT;
	if (flags & MOVE_MOUNT_F_EMPTY_PATH)	lflags |= LOOKUP_EMPTY;

	ret = user_path_at(from_dfd, from_pathname, lflags, &from_path);
	if (ret < 0)
		return ret;

	lflags = 0;
	if (flags & MOVE_MOUNT_T_SYMLINKS)	lflags |= LOOKUP_FOLLOW;
	if (flags & MOVE_MOUNT_T_AUTOMOUNTS)	lflags |= LOOKUP_AUTOMOUNT;
	if (flags & MOVE_MOUNT_T_EMPTY_PATH)	lflags |= LOOKUP_EMPTY;

	ret = user_path_at(to_dfd, to_pathname, lflags, &to_path);
	if (ret < 0)
		goto out_from;

	ret = security_move_mount(&from_path, &to_path);
	if (ret < 0)
		goto out_to;

	ret = do_move_mount(&from_path, &to_path);

out_to:
	path_put(&to_path);
out_from:
	path_put(&from_path);
	return ret;
}

/*
 * Return true if path is reachable from root
 *
+6 −0
Original line number Diff line number Diff line
@@ -160,6 +160,10 @@
 *	Parse a string of security data filling in the opts structure
 *	@options string containing all mount options known by the LSM
 *	@opts binary data structure usable by the LSM
 * @move_mount:
 *	Check permission before a mount is moved.
 *	@from_path indicates the mount that is going to be moved.
 *	@to_path indicates the mountpoint that will be mounted upon.
 * @dentry_init_security:
 *	Compute a context for a dentry as the inode is not yet available
 *	since NFSv4 has no label backed by an EA anyway.
@@ -1501,6 +1505,7 @@ union security_list_options {
					unsigned long *set_kern_flags);
	int (*sb_add_mnt_opt)(const char *option, const char *val, int len,
			      void **mnt_opts);
	int (*move_mount)(const struct path *from_path, const struct path *to_path);
	int (*dentry_init_security)(struct dentry *dentry, int mode,
					const struct qstr *name, void **ctx,
					u32 *ctxlen);
@@ -1835,6 +1840,7 @@ struct security_hook_heads {
	struct hlist_head sb_set_mnt_opts;
	struct hlist_head sb_clone_mnt_opts;
	struct hlist_head sb_add_mnt_opt;
	struct hlist_head move_mount;
	struct hlist_head dentry_init_security;
	struct hlist_head dentry_create_files_as;
#ifdef CONFIG_SECURITY_PATH
+7 −0
Original line number Diff line number Diff line
@@ -250,6 +250,7 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
				unsigned long *set_kern_flags);
int security_add_mnt_opt(const char *option, const char *val,
				int len, void **mnt_opts);
int security_move_mount(const struct path *from_path, const struct path *to_path);
int security_dentry_init_security(struct dentry *dentry, int mode,
					const struct qstr *name, void **ctx,
					u32 *ctxlen);
@@ -611,6 +612,12 @@ static inline int security_add_mnt_opt(const char *option, const char *val,
	return 0;
}

static inline int security_move_mount(const struct path *from_path,
				      const struct path *to_path)
{
	return 0;
}

static inline int security_inode_alloc(struct inode *inode)
{
	return 0;
Loading