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: David Howells <dhowells@redhat.com>
cc: linux-api@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
David Howells 2018-11-05 17:40:30 +00:00 committed by Al Viro
parent a07b200047
commit 2db154b3ea
8 changed files with 131 additions and 33 deletions

View File

@ -399,7 +399,8 @@
385 i386 io_pgetevents sys_io_pgetevents_time32 __ia32_compat_sys_io_pgetevents 385 i386 io_pgetevents sys_io_pgetevents_time32 __ia32_compat_sys_io_pgetevents
386 i386 rseq sys_rseq __ia32_sys_rseq 386 i386 rseq sys_rseq __ia32_sys_rseq
387 i386 open_tree sys_open_tree __ia32_sys_open_tree 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 393 i386 semget sys_semget __ia32_sys_semget
394 i386 semctl sys_semctl __ia32_compat_sys_semctl 394 i386 semctl sys_semctl __ia32_compat_sys_semctl
395 i386 shmget sys_shmget __ia32_sys_shmget 395 i386 shmget sys_shmget __ia32_sys_shmget

View File

@ -344,6 +344,7 @@
333 common io_pgetevents __x64_sys_io_pgetevents 333 common io_pgetevents __x64_sys_io_pgetevents
334 common rseq __x64_sys_rseq 334 common rseq __x64_sys_rseq
335 common open_tree __x64_sys_open_tree 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 # don't use numbers 387 through 423, add new calls after the last
# 'common' entry # 'common' entry
424 common pidfd_send_signal __x64_sys_pidfd_send_signal 424 common pidfd_send_signal __x64_sys_pidfd_send_signal

View File

@ -2539,72 +2539,81 @@ static inline int tree_contains_unbindable(struct mount *mnt)
return 0; 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 *p;
struct mount *old; struct mount *old;
struct mountpoint *mp; struct mountpoint *mp;
int err; 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); mp = lock_mount(new_path);
err = PTR_ERR(mp);
if (IS_ERR(mp)) if (IS_ERR(mp))
goto out; return PTR_ERR(mp);
old = real_mount(old_path.mnt); old = real_mount(old_path->mnt);
p = real_mount(path->mnt); p = real_mount(new_path->mnt);
err = -EINVAL; err = -EINVAL;
if (!check_mnt(p) || !check_mnt(old)) if (!check_mnt(p) || !check_mnt(old))
goto out1; goto out;
if (old->mnt.mnt_flags & MNT_LOCKED)
goto out1;
err = -EINVAL;
if (old_path.dentry != old_path.mnt->mnt_root)
goto out1;
if (!mnt_has_parent(old)) if (!mnt_has_parent(old))
goto out1; goto out;
if (d_is_dir(path->dentry) != if (old->mnt.mnt_flags & MNT_LOCKED)
d_is_dir(old_path.dentry)) goto out;
goto out1;
if (old_path->dentry != old_path->mnt->mnt_root)
goto out;
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. * Don't move a mount residing in a shared parent.
*/ */
if (IS_MNT_SHARED(old->mnt_parent)) if (IS_MNT_SHARED(old->mnt_parent))
goto out1; goto out;
/* /*
* Don't move a mount tree containing unbindable mounts to a destination * Don't move a mount tree containing unbindable mounts to a destination
* mount which is shared. * mount which is shared.
*/ */
if (IS_MNT_SHARED(p) && tree_contains_unbindable(old)) if (IS_MNT_SHARED(p) && tree_contains_unbindable(old))
goto out1; goto out;
err = -ELOOP; err = -ELOOP;
for (; mnt_has_parent(p); p = p->mnt_parent) for (; mnt_has_parent(p); p = p->mnt_parent)
if (p == old) 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) if (err)
goto out1; goto out;
/* if the mount is moved, it should no longer be expire /* if the mount is moved, it should no longer be expire
* automatically */ * automatically */
list_del_init(&old->mnt_expire); list_del_init(&old->mnt_expire);
out1:
unlock_mount(mp);
out: out:
unlock_mount(mp);
if (!err) if (!err)
path_put(&parent_path); 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); path_put(&old_path);
return err; 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)) else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
retval = do_change_type(&path, flags); retval = do_change_type(&path, flags);
else if (flags & MS_MOVE) else if (flags & MS_MOVE)
retval = do_move_mount(&path, dev_name); retval = do_move_mount_old(&path, dev_name);
else else
retval = do_new_mount(&path, type_page, sb_flags, mnt_flags, retval = do_new_mount(&path, type_page, sb_flags, mnt_flags,
dev_name, data_page); 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); 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 * Return true if path is reachable from root
* *

View File

@ -160,6 +160,10 @@
* Parse a string of security data filling in the opts structure * Parse a string of security data filling in the opts structure
* @options string containing all mount options known by the LSM * @options string containing all mount options known by the LSM
* @opts binary data structure usable 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: * @dentry_init_security:
* Compute a context for a dentry as the inode is not yet available * Compute a context for a dentry as the inode is not yet available
* since NFSv4 has no label backed by an EA anyway. * since NFSv4 has no label backed by an EA anyway.
@ -1501,6 +1505,7 @@ union security_list_options {
unsigned long *set_kern_flags); unsigned long *set_kern_flags);
int (*sb_add_mnt_opt)(const char *option, const char *val, int len, int (*sb_add_mnt_opt)(const char *option, const char *val, int len,
void **mnt_opts); 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, int (*dentry_init_security)(struct dentry *dentry, int mode,
const struct qstr *name, void **ctx, const struct qstr *name, void **ctx,
u32 *ctxlen); u32 *ctxlen);
@ -1835,6 +1840,7 @@ struct security_hook_heads {
struct hlist_head sb_set_mnt_opts; struct hlist_head sb_set_mnt_opts;
struct hlist_head sb_clone_mnt_opts; struct hlist_head sb_clone_mnt_opts;
struct hlist_head sb_add_mnt_opt; struct hlist_head sb_add_mnt_opt;
struct hlist_head move_mount;
struct hlist_head dentry_init_security; struct hlist_head dentry_init_security;
struct hlist_head dentry_create_files_as; struct hlist_head dentry_create_files_as;
#ifdef CONFIG_SECURITY_PATH #ifdef CONFIG_SECURITY_PATH

View File

@ -250,6 +250,7 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
unsigned long *set_kern_flags); unsigned long *set_kern_flags);
int security_add_mnt_opt(const char *option, const char *val, int security_add_mnt_opt(const char *option, const char *val,
int len, void **mnt_opts); 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, int security_dentry_init_security(struct dentry *dentry, int mode,
const struct qstr *name, void **ctx, const struct qstr *name, void **ctx,
u32 *ctxlen); u32 *ctxlen);
@ -611,6 +612,12 @@ static inline int security_add_mnt_opt(const char *option, const char *val,
return 0; 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) static inline int security_inode_alloc(struct inode *inode)
{ {
return 0; return 0;

View File

@ -986,6 +986,9 @@ asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
asmlinkage long sys_rseq(struct rseq __user *rseq, uint32_t rseq_len, asmlinkage long sys_rseq(struct rseq __user *rseq, uint32_t rseq_len,
int flags, uint32_t sig); int flags, uint32_t sig);
asmlinkage long sys_open_tree(int dfd, const char __user *path, unsigned flags); asmlinkage long sys_open_tree(int dfd, const char __user *path, unsigned flags);
asmlinkage long sys_move_mount(int from_dfd, const char __user *from_path,
int to_dfd, const char __user *to_path,
unsigned int ms_flags);
asmlinkage long sys_pidfd_send_signal(int pidfd, int sig, asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,
siginfo_t __user *info, siginfo_t __user *info,
unsigned int flags); unsigned int flags);

View File

@ -61,4 +61,15 @@
#define OPEN_TREE_CLONE 1 /* Clone the target tree and attach the clone */ #define OPEN_TREE_CLONE 1 /* Clone the target tree and attach the clone */
#define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */ #define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */
/*
* move_mount() flags.
*/
#define MOVE_MOUNT_F_SYMLINKS 0x00000001 /* Follow symlinks on from path */
#define MOVE_MOUNT_F_AUTOMOUNTS 0x00000002 /* Follow automounts on from path */
#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
#define MOVE_MOUNT_T_SYMLINKS 0x00000010 /* Follow symlinks on to path */
#define MOVE_MOUNT_T_AUTOMOUNTS 0x00000020 /* Follow automounts on to path */
#define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 /* Empty to path permitted */
#define MOVE_MOUNT__MASK 0x00000077
#endif /* _UAPI_LINUX_MOUNT_H */ #endif /* _UAPI_LINUX_MOUNT_H */

View File

@ -866,6 +866,11 @@ int security_add_mnt_opt(const char *option, const char *val, int len,
} }
EXPORT_SYMBOL(security_add_mnt_opt); EXPORT_SYMBOL(security_add_mnt_opt);
int security_move_mount(const struct path *from_path, const struct path *to_path)
{
return call_int_hook(move_mount, 0, from_path, to_path);
}
int security_inode_alloc(struct inode *inode) int security_inode_alloc(struct inode *inode)
{ {
int rc = lsm_inode_alloc(inode); int rc = lsm_inode_alloc(inode);