Skip to content

Commit 2db154b

Browse files
dhowellsAl Viro
authored andcommitted
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 <[email protected]> cc: [email protected] Signed-off-by: Al Viro <[email protected]>
1 parent a07b200 commit 2db154b

File tree

8 files changed

+130
-32
lines changed

8 files changed

+130
-32
lines changed

arch/x86/entry/syscalls/syscall_32.tbl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,8 @@
399399
385 i386 io_pgetevents sys_io_pgetevents_time32 __ia32_compat_sys_io_pgetevents
400400
386 i386 rseq sys_rseq __ia32_sys_rseq
401401
387 i386 open_tree sys_open_tree __ia32_sys_open_tree
402-
# don't use numbers 388 through 392, add new calls at the end
402+
388 i386 move_mount sys_move_mount __ia32_sys_move_mount
403+
# don't use numbers 389 through 392, add new calls at the end
403404
393 i386 semget sys_semget __ia32_sys_semget
404405
394 i386 semctl sys_semctl __ia32_compat_sys_semctl
405406
395 i386 shmget sys_shmget __ia32_sys_shmget

arch/x86/entry/syscalls/syscall_64.tbl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@
344344
333 common io_pgetevents __x64_sys_io_pgetevents
345345
334 common rseq __x64_sys_rseq
346346
335 common open_tree __x64_sys_open_tree
347+
336 common move_mount __x64_sys_move_mount
347348
# don't use numbers 387 through 423, add new calls after the last
348349
# 'common' entry
349350
424 common pidfd_send_signal __x64_sys_pidfd_send_signal

fs/namespace.c

Lines changed: 95 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2539,72 +2539,81 @@ static inline int tree_contains_unbindable(struct mount *mnt)
25392539
return 0;
25402540
}
25412541

2542-
static int do_move_mount(struct path *path, const char *old_name)
2542+
static int do_move_mount(struct path *old_path, struct path *new_path)
25432543
{
2544-
struct path old_path, parent_path;
2544+
struct path parent_path = {.mnt = NULL, .dentry = NULL};
25452545
struct mount *p;
25462546
struct mount *old;
25472547
struct mountpoint *mp;
25482548
int err;
2549-
if (!old_name || !*old_name)
2550-
return -EINVAL;
2551-
err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
2552-
if (err)
2553-
return err;
25542549

2555-
mp = lock_mount(path);
2556-
err = PTR_ERR(mp);
2550+
mp = lock_mount(new_path);
25572551
if (IS_ERR(mp))
2558-
goto out;
2552+
return PTR_ERR(mp);
25592553

2560-
old = real_mount(old_path.mnt);
2561-
p = real_mount(path->mnt);
2554+
old = real_mount(old_path->mnt);
2555+
p = real_mount(new_path->mnt);
25622556

25632557
err = -EINVAL;
25642558
if (!check_mnt(p) || !check_mnt(old))
2565-
goto out1;
2559+
goto out;
25662560

2567-
if (old->mnt.mnt_flags & MNT_LOCKED)
2568-
goto out1;
2561+
if (!mnt_has_parent(old))
2562+
goto out;
25692563

2570-
err = -EINVAL;
2571-
if (old_path.dentry != old_path.mnt->mnt_root)
2572-
goto out1;
2564+
if (old->mnt.mnt_flags & MNT_LOCKED)
2565+
goto out;
25732566

2574-
if (!mnt_has_parent(old))
2575-
goto out1;
2567+
if (old_path->dentry != old_path->mnt->mnt_root)
2568+
goto out;
25762569

2577-
if (d_is_dir(path->dentry) !=
2578-
d_is_dir(old_path.dentry))
2579-
goto out1;
2570+
if (d_is_dir(new_path->dentry) !=
2571+
d_is_dir(old_path->dentry))
2572+
goto out;
25802573
/*
25812574
* Don't move a mount residing in a shared parent.
25822575
*/
25832576
if (IS_MNT_SHARED(old->mnt_parent))
2584-
goto out1;
2577+
goto out;
25852578
/*
25862579
* Don't move a mount tree containing unbindable mounts to a destination
25872580
* mount which is shared.
25882581
*/
25892582
if (IS_MNT_SHARED(p) && tree_contains_unbindable(old))
2590-
goto out1;
2583+
goto out;
25912584
err = -ELOOP;
25922585
for (; mnt_has_parent(p); p = p->mnt_parent)
25932586
if (p == old)
2594-
goto out1;
2587+
goto out;
25952588

2596-
err = attach_recursive_mnt(old, real_mount(path->mnt), mp, &parent_path);
2589+
err = attach_recursive_mnt(old, real_mount(new_path->mnt), mp,
2590+
&parent_path);
25972591
if (err)
2598-
goto out1;
2592+
goto out;
25992593

26002594
/* if the mount is moved, it should no longer be expire
26012595
* automatically */
26022596
list_del_init(&old->mnt_expire);
2603-
out1:
2604-
unlock_mount(mp);
26052597
out:
2598+
unlock_mount(mp);
26062599
if (!err)
26072600
path_put(&parent_path);
2601+
return err;
2602+
}
2603+
2604+
static int do_move_mount_old(struct path *path, const char *old_name)
2605+
{
2606+
struct path old_path;
2607+
int err;
2608+
2609+
if (!old_name || !*old_name)
2610+
return -EINVAL;
2611+
2612+
err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
2613+
if (err)
2614+
return err;
2615+
2616+
err = do_move_mount(&old_path, path);
26082617
path_put(&old_path);
26092618
return err;
26102619
}
@@ -3050,7 +3059,7 @@ long do_mount(const char *dev_name, const char __user *dir_name,
30503059
else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
30513060
retval = do_change_type(&path, flags);
30523061
else if (flags & MS_MOVE)
3053-
retval = do_move_mount(&path, dev_name);
3062+
retval = do_move_mount_old(&path, dev_name);
30543063
else
30553064
retval = do_new_mount(&path, type_page, sb_flags, mnt_flags,
30563065
dev_name, data_page);
@@ -3278,6 +3287,61 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
32783287
return ksys_mount(dev_name, dir_name, type, flags, data);
32793288
}
32803289

3290+
/*
3291+
* Move a mount from one place to another.
3292+
*
3293+
* Note the flags value is a combination of MOVE_MOUNT_* flags.
3294+
*/
3295+
SYSCALL_DEFINE5(move_mount,
3296+
int, from_dfd, const char *, from_pathname,
3297+
int, to_dfd, const char *, to_pathname,
3298+
unsigned int, flags)
3299+
{
3300+
struct path from_path, to_path;
3301+
unsigned int lflags;
3302+
int ret = 0;
3303+
3304+
if (!may_mount())
3305+
return -EPERM;
3306+
3307+
if (flags & ~MOVE_MOUNT__MASK)
3308+
return -EINVAL;
3309+
3310+
/* If someone gives a pathname, they aren't permitted to move
3311+
* from an fd that requires unmount as we can't get at the flag
3312+
* to clear it afterwards.
3313+
*/
3314+
lflags = 0;
3315+
if (flags & MOVE_MOUNT_F_SYMLINKS) lflags |= LOOKUP_FOLLOW;
3316+
if (flags & MOVE_MOUNT_F_AUTOMOUNTS) lflags |= LOOKUP_AUTOMOUNT;
3317+
if (flags & MOVE_MOUNT_F_EMPTY_PATH) lflags |= LOOKUP_EMPTY;
3318+
3319+
ret = user_path_at(from_dfd, from_pathname, lflags, &from_path);
3320+
if (ret < 0)
3321+
return ret;
3322+
3323+
lflags = 0;
3324+
if (flags & MOVE_MOUNT_T_SYMLINKS) lflags |= LOOKUP_FOLLOW;
3325+
if (flags & MOVE_MOUNT_T_AUTOMOUNTS) lflags |= LOOKUP_AUTOMOUNT;
3326+
if (flags & MOVE_MOUNT_T_EMPTY_PATH) lflags |= LOOKUP_EMPTY;
3327+
3328+
ret = user_path_at(to_dfd, to_pathname, lflags, &to_path);
3329+
if (ret < 0)
3330+
goto out_from;
3331+
3332+
ret = security_move_mount(&from_path, &to_path);
3333+
if (ret < 0)
3334+
goto out_to;
3335+
3336+
ret = do_move_mount(&from_path, &to_path);
3337+
3338+
out_to:
3339+
path_put(&to_path);
3340+
out_from:
3341+
path_put(&from_path);
3342+
return ret;
3343+
}
3344+
32813345
/*
32823346
* Return true if path is reachable from root
32833347
*

include/linux/lsm_hooks.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@
160160
* Parse a string of security data filling in the opts structure
161161
* @options string containing all mount options known by the LSM
162162
* @opts binary data structure usable by the LSM
163+
* @move_mount:
164+
* Check permission before a mount is moved.
165+
* @from_path indicates the mount that is going to be moved.
166+
* @to_path indicates the mountpoint that will be mounted upon.
163167
* @dentry_init_security:
164168
* Compute a context for a dentry as the inode is not yet available
165169
* since NFSv4 has no label backed by an EA anyway.
@@ -1501,6 +1505,7 @@ union security_list_options {
15011505
unsigned long *set_kern_flags);
15021506
int (*sb_add_mnt_opt)(const char *option, const char *val, int len,
15031507
void **mnt_opts);
1508+
int (*move_mount)(const struct path *from_path, const struct path *to_path);
15041509
int (*dentry_init_security)(struct dentry *dentry, int mode,
15051510
const struct qstr *name, void **ctx,
15061511
u32 *ctxlen);
@@ -1835,6 +1840,7 @@ struct security_hook_heads {
18351840
struct hlist_head sb_set_mnt_opts;
18361841
struct hlist_head sb_clone_mnt_opts;
18371842
struct hlist_head sb_add_mnt_opt;
1843+
struct hlist_head move_mount;
18381844
struct hlist_head dentry_init_security;
18391845
struct hlist_head dentry_create_files_as;
18401846
#ifdef CONFIG_SECURITY_PATH

include/linux/security.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
250250
unsigned long *set_kern_flags);
251251
int security_add_mnt_opt(const char *option, const char *val,
252252
int len, void **mnt_opts);
253+
int security_move_mount(const struct path *from_path, const struct path *to_path);
253254
int security_dentry_init_security(struct dentry *dentry, int mode,
254255
const struct qstr *name, void **ctx,
255256
u32 *ctxlen);
@@ -611,6 +612,12 @@ static inline int security_add_mnt_opt(const char *option, const char *val,
611612
return 0;
612613
}
613614

615+
static inline int security_move_mount(const struct path *from_path,
616+
const struct path *to_path)
617+
{
618+
return 0;
619+
}
620+
614621
static inline int security_inode_alloc(struct inode *inode)
615622
{
616623
return 0;

include/linux/syscalls.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,9 @@ asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
986986
asmlinkage long sys_rseq(struct rseq __user *rseq, uint32_t rseq_len,
987987
int flags, uint32_t sig);
988988
asmlinkage long sys_open_tree(int dfd, const char __user *path, unsigned flags);
989+
asmlinkage long sys_move_mount(int from_dfd, const char __user *from_path,
990+
int to_dfd, const char __user *to_path,
991+
unsigned int ms_flags);
989992
asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,
990993
siginfo_t __user *info,
991994
unsigned int flags);

include/uapi/linux/mount.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,15 @@
6161
#define OPEN_TREE_CLONE 1 /* Clone the target tree and attach the clone */
6262
#define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */
6363

64+
/*
65+
* move_mount() flags.
66+
*/
67+
#define MOVE_MOUNT_F_SYMLINKS 0x00000001 /* Follow symlinks on from path */
68+
#define MOVE_MOUNT_F_AUTOMOUNTS 0x00000002 /* Follow automounts on from path */
69+
#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
70+
#define MOVE_MOUNT_T_SYMLINKS 0x00000010 /* Follow symlinks on to path */
71+
#define MOVE_MOUNT_T_AUTOMOUNTS 0x00000020 /* Follow automounts on to path */
72+
#define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 /* Empty to path permitted */
73+
#define MOVE_MOUNT__MASK 0x00000077
74+
6475
#endif /* _UAPI_LINUX_MOUNT_H */

security/security.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,11 @@ int security_add_mnt_opt(const char *option, const char *val, int len,
866866
}
867867
EXPORT_SYMBOL(security_add_mnt_opt);
868868

869+
int security_move_mount(const struct path *from_path, const struct path *to_path)
870+
{
871+
return call_int_hook(move_mount, 0, from_path, to_path);
872+
}
873+
869874
int security_inode_alloc(struct inode *inode)
870875
{
871876
int rc = lsm_inode_alloc(inode);

0 commit comments

Comments
 (0)