[Devel] [PATCH RH7 3/4] vfs: syscall: Add move_mount(2) to move mounts around
Pavel Tikhomirov
ptikhomirov at virtuozzo.com
Tue Aug 25 19:14:07 MSK 2020
From: David Howells <dhowells at redhat.com>
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 at redhat.com>
cc: linux-api at vger.kernel.org
Signed-off-by: Al Viro <viro at zeniv.linux.org.uk>
vfs: syscall: Add move_mount(2) to move mounts around
(cherry-picked from commit 2db154b3ea8e14b04fee23e3fdfd5e9d17fbc6ae)
uapi, x86: Fix the syscall numbering of the mount API syscalls [ver #2]
(cherry-picked from commit 9c8ad7a2ff0bfe58f019ec0abc1fb965114dde7d)
selinux: fix regression introduced by move_mount(2) syscall
(cherry-picked from commit 98aa00345de54b8340dc2ddcd87f446d33387b5e)
https://jira.sw.ru/browse/PSBM-107263
Signed-off-by: Pavel Tikhomirov <ptikhomirov at virtuozzo.com>
---
arch/x86/syscalls/syscall_32.tbl | 1 +
arch/x86/syscalls/syscall_64.tbl | 1 +
fs/namespace.c | 126 +++++++++++++++++++++++--------
include/linux/security.h | 12 +++
include/linux/syscalls.h | 3 +
include/uapi/linux/fs.h | 11 +++
security/capability.c | 6 ++
security/security.c | 5 ++
security/selinux/hooks.c | 9 +++
9 files changed, 143 insertions(+), 31 deletions(-)
diff --git a/arch/x86/syscalls/syscall_32.tbl b/arch/x86/syscalls/syscall_32.tbl
index 7b32f7c6d5cc..978f07cb0ea1 100644
--- a/arch/x86/syscalls/syscall_32.tbl
+++ b/arch/x86/syscalls/syscall_32.tbl
@@ -372,6 +372,7 @@
382 i386 pkey_free sys_pkey_free
428 i386 open_tree sys_open_tree
+429 i386 move_mount sys_move_mount
510 i386 getluid sys_getluid
511 i386 setluid sys_setluid
diff --git a/arch/x86/syscalls/syscall_64.tbl b/arch/x86/syscalls/syscall_64.tbl
index 6b3a1d110480..3c86abac9a50 100644
--- a/arch/x86/syscalls/syscall_64.tbl
+++ b/arch/x86/syscalls/syscall_64.tbl
@@ -337,6 +337,7 @@
331 common pkey_free sys_pkey_free
428 common open_tree sys_open_tree
+429 common move_mount sys_move_mount
500 64 getluid sys_getluid
501 64 setluid sys_setluid
diff --git a/fs/namespace.c b/fs/namespace.c
index 694e3d6ac361..0820db1a4a5f 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -2852,72 +2852,81 @@ static int do_set_group(struct path *path, const char *sibling_name)
return err;
}
-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 (S_ISDIR(path->dentry->d_inode->i_mode) !=
- S_ISDIR(old_path.dentry->d_inode->i_mode))
- goto out1;
+ if (S_ISDIR(new_path->dentry->d_inode->i_mode) !=
+ S_ISDIR(old_path->dentry->d_inode->i_mode))
+ 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;
}
@@ -3334,7 +3343,7 @@ long do_mount(const char *dev_name, const char __user *dir_name,
else if (cmd & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
retval = do_change_type(&path, flags);
else if (cmd & MS_MOVE)
- retval = do_move_mount(&path, dev_name);
+ retval = do_move_mount_old(&path, dev_name);
else if (cmd & MS_SET_GROUP)
retval = do_set_group(&path, dev_name);
else
@@ -3567,6 +3576,61 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
return ret;
}
+/*
+ * 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
*
diff --git a/include/linux/security.h b/include/linux/security.h
index 47aed522dc72..2fe05c246ac0 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -323,6 +323,10 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
* 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.
@@ -1559,6 +1563,7 @@ struct security_operations {
unsigned long kern_flags,
unsigned long *set_kern_flags);
int (*sb_parse_opts_str) (char *options, struct security_mnt_opts *opts);
+ int (*move_mount)(const struct path *from_path, const struct path *to_path);
int (*dentry_init_security) (struct dentry *dentry, int mode,
struct qstr *name, void **ctx,
u32 *ctxlen);
@@ -1880,6 +1885,7 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
unsigned long kern_flags,
unsigned long *set_kern_flags);
int security_sb_parse_opts_str(char *options, struct security_mnt_opts *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,
struct qstr *name, void **ctx,
u32 *ctxlen);
@@ -2209,6 +2215,12 @@ static inline int security_sb_parse_opts_str(char *options, struct security_mnt_
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;
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 0e30297318e5..dce905f149f3 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -892,5 +892,8 @@ asmlinkage long sys_membarrier(int cmd, int flags);
asmlinkage long sys_mlock2(unsigned long start, size_t len, int 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);
#endif
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 0fc42f1c1628..8c9e6a255341 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -131,6 +131,17 @@ struct inodes_stat_t {
#define OPEN_TREE_CLONE 1 /* Clone the target tree and attach the clone */
#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
+
/* the read-only stuff doesn't really belong here, but any other place is
probably as bad and I don't want to create yet another include file. */
diff --git a/security/capability.c b/security/capability.c
index aff9bb812bc2..47cd79832b75 100644
--- a/security/capability.c
+++ b/security/capability.c
@@ -114,6 +114,11 @@ static int cap_sb_parse_opts_str(char *options, struct security_mnt_opts *opts)
return 0;
}
+static int cap_move_mount(const struct path *from_path, const struct path *to_path)
+{
+ return 0;
+}
+
static int cap_dentry_init_security(struct dentry *dentry, int mode,
struct qstr *name, void **ctx,
u32 *ctxlen)
@@ -1019,6 +1024,7 @@ void __init security_fixup_ops(struct security_operations *ops)
set_to_cap_if_null(ops, sb_set_mnt_opts);
set_to_cap_if_null(ops, sb_clone_mnt_opts);
set_to_cap_if_null(ops, sb_parse_opts_str);
+ set_to_cap_if_null(ops, move_mount);
set_to_cap_if_null(ops, dentry_init_security);
set_to_cap_if_null(ops, dentry_create_files_as);
set_to_cap_if_null(ops, inode_alloc_security);
diff --git a/security/security.c b/security/security.c
index 44aaadf04778..b6ba158efe48 100644
--- a/security/security.c
+++ b/security/security.c
@@ -345,6 +345,11 @@ int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts)
}
EXPORT_SYMBOL(security_sb_parse_opts_str);
+int security_move_mount(const struct path *from_path, const struct path *to_path)
+{
+ return security_ops->move_mount(from_path, to_path);
+}
+
int security_inode_alloc(struct inode *inode)
{
inode->i_security = NULL;
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 84a6f1afa10a..aa7bb4b41560 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2815,6 +2815,14 @@ static int selinux_mount(const char *dev_name,
return path_has_perm(cred, path, FILE__MOUNTON);
}
+static int selinux_move_mount(const struct path *from_path,
+ const struct path *to_path)
+{
+ const struct cred *cred = current_cred();
+
+ return path_has_perm(cred, to_path, FILE__MOUNTON);
+}
+
static int selinux_umount(struct vfsmount *mnt, int flags)
{
const struct cred *cred = current_cred();
@@ -6316,6 +6324,7 @@ static struct security_operations selinux_ops = {
.sb_set_mnt_opts = selinux_set_mnt_opts,
.sb_clone_mnt_opts = selinux_sb_clone_mnt_opts,
.sb_parse_opts_str = selinux_parse_opts_str,
+ .move_mount = selinux_move_mount,
.dentry_init_security = selinux_dentry_init_security,
.dentry_create_files_as = selinux_dentry_create_files_as,
--
2.24.1
More information about the Devel
mailing list