diff options
| -rw-r--r-- | arch/x86/entry/syscalls/syscall_32.tbl | 3 | ||||
| -rw-r--r-- | arch/x86/entry/syscalls/syscall_64.tbl | 1 | ||||
| -rw-r--r-- | fs/namespace.c | 126 | ||||
| -rw-r--r-- | include/linux/lsm_hooks.h | 6 | ||||
| -rw-r--r-- | include/linux/security.h | 7 | ||||
| -rw-r--r-- | include/linux/syscalls.h | 3 | ||||
| -rw-r--r-- | include/uapi/linux/mount.h | 11 | ||||
| -rw-r--r-- | security/security.c | 5 |
8 files changed, 130 insertions, 32 deletions
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl index ae2294d07ecb..0db9effb18d9 100644 --- a/arch/x86/entry/syscalls/syscall_32.tbl +++ b/arch/x86/entry/syscalls/syscall_32.tbl | |||
| @@ -399,7 +399,8 @@ | |||
| 399 | 385 i386 io_pgetevents sys_io_pgetevents_time32 __ia32_compat_sys_io_pgetevents | 399 | 385 i386 io_pgetevents sys_io_pgetevents_time32 __ia32_compat_sys_io_pgetevents |
| 400 | 386 i386 rseq sys_rseq __ia32_sys_rseq | 400 | 386 i386 rseq sys_rseq __ia32_sys_rseq |
| 401 | 387 i386 open_tree sys_open_tree __ia32_sys_open_tree | 401 | 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 | ||
| 403 | 393 i386 semget sys_semget __ia32_sys_semget | 404 | 393 i386 semget sys_semget __ia32_sys_semget |
| 404 | 394 i386 semctl sys_semctl __ia32_compat_sys_semctl | 405 | 394 i386 semctl sys_semctl __ia32_compat_sys_semctl |
| 405 | 395 i386 shmget sys_shmget __ia32_sys_shmget | 406 | 395 i386 shmget sys_shmget __ia32_sys_shmget |
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl index a6e06c35b5b1..0440f0eefa02 100644 --- a/arch/x86/entry/syscalls/syscall_64.tbl +++ b/arch/x86/entry/syscalls/syscall_64.tbl | |||
| @@ -344,6 +344,7 @@ | |||
| 344 | 333 common io_pgetevents __x64_sys_io_pgetevents | 344 | 333 common io_pgetevents __x64_sys_io_pgetevents |
| 345 | 334 common rseq __x64_sys_rseq | 345 | 334 common rseq __x64_sys_rseq |
| 346 | 335 common open_tree __x64_sys_open_tree | 346 | 335 common open_tree __x64_sys_open_tree |
| 347 | 336 common move_mount __x64_sys_move_mount | ||
| 347 | # don't use numbers 387 through 423, add new calls after the last | 348 | # don't use numbers 387 through 423, add new calls after the last |
| 348 | # 'common' entry | 349 | # 'common' entry |
| 349 | 424 common pidfd_send_signal __x64_sys_pidfd_send_signal | 350 | 424 common pidfd_send_signal __x64_sys_pidfd_send_signal |
diff --git a/fs/namespace.c b/fs/namespace.c index b804a1a497ee..dc600f53de9d 100644 --- a/fs/namespace.c +++ b/fs/namespace.c | |||
| @@ -2539,72 +2539,81 @@ static inline int tree_contains_unbindable(struct mount *mnt) | |||
| 2539 | return 0; | 2539 | return 0; |
| 2540 | } | 2540 | } |
| 2541 | 2541 | ||
| 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) |
| 2543 | { | 2543 | { |
| 2544 | struct path old_path, parent_path; | 2544 | struct path parent_path = {.mnt = NULL, .dentry = NULL}; |
| 2545 | struct mount *p; | 2545 | struct mount *p; |
| 2546 | struct mount *old; | 2546 | struct mount *old; |
| 2547 | struct mountpoint *mp; | 2547 | struct mountpoint *mp; |
| 2548 | int err; | 2548 | 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; | ||
| 2554 | 2549 | ||
| 2555 | mp = lock_mount(path); | 2550 | mp = lock_mount(new_path); |
| 2556 | err = PTR_ERR(mp); | ||
| 2557 | if (IS_ERR(mp)) | 2551 | if (IS_ERR(mp)) |
| 2558 | goto out; | 2552 | return PTR_ERR(mp); |
| 2559 | 2553 | ||
| 2560 | old = real_mount(old_path.mnt); | 2554 | old = real_mount(old_path->mnt); |
| 2561 | p = real_mount(path->mnt); | 2555 | p = real_mount(new_path->mnt); |
| 2562 | 2556 | ||
| 2563 | err = -EINVAL; | 2557 | err = -EINVAL; |
| 2564 | if (!check_mnt(p) || !check_mnt(old)) | 2558 | if (!check_mnt(p) || !check_mnt(old)) |
| 2565 | goto out1; | 2559 | goto out; |
| 2566 | 2560 | ||
| 2567 | if (old->mnt.mnt_flags & MNT_LOCKED) | 2561 | if (!mnt_has_parent(old)) |
| 2568 | goto out1; | 2562 | goto out; |
| 2569 | 2563 | ||
| 2570 | err = -EINVAL; | 2564 | if (old->mnt.mnt_flags & MNT_LOCKED) |
| 2571 | if (old_path.dentry != old_path.mnt->mnt_root) | 2565 | goto out; |
| 2572 | goto out1; | ||
| 2573 | 2566 | ||
| 2574 | if (!mnt_has_parent(old)) | 2567 | if (old_path->dentry != old_path->mnt->mnt_root) |
| 2575 | goto out1; | 2568 | goto out; |
| 2576 | 2569 | ||
| 2577 | if (d_is_dir(path->dentry) != | 2570 | if (d_is_dir(new_path->dentry) != |
| 2578 | d_is_dir(old_path.dentry)) | 2571 | d_is_dir(old_path->dentry)) |
| 2579 | goto out1; | 2572 | goto out; |
| 2580 | /* | 2573 | /* |
| 2581 | * Don't move a mount residing in a shared parent. | 2574 | * Don't move a mount residing in a shared parent. |
| 2582 | */ | 2575 | */ |
| 2583 | if (IS_MNT_SHARED(old->mnt_parent)) | 2576 | if (IS_MNT_SHARED(old->mnt_parent)) |
| 2584 | goto out1; | 2577 | goto out; |
| 2585 | /* | 2578 | /* |
| 2586 | * Don't move a mount tree containing unbindable mounts to a destination | 2579 | * Don't move a mount tree containing unbindable mounts to a destination |
| 2587 | * mount which is shared. | 2580 | * mount which is shared. |
| 2588 | */ | 2581 | */ |
| 2589 | if (IS_MNT_SHARED(p) && tree_contains_unbindable(old)) | 2582 | if (IS_MNT_SHARED(p) && tree_contains_unbindable(old)) |
| 2590 | goto out1; | 2583 | goto out; |
| 2591 | err = -ELOOP; | 2584 | err = -ELOOP; |
| 2592 | for (; mnt_has_parent(p); p = p->mnt_parent) | 2585 | for (; mnt_has_parent(p); p = p->mnt_parent) |
| 2593 | if (p == old) | 2586 | if (p == old) |
| 2594 | goto out1; | 2587 | goto out; |
| 2595 | 2588 | ||
| 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); | ||
| 2597 | if (err) | 2591 | if (err) |
| 2598 | goto out1; | 2592 | goto out; |
| 2599 | 2593 | ||
| 2600 | /* if the mount is moved, it should no longer be expire | 2594 | /* if the mount is moved, it should no longer be expire |
| 2601 | * automatically */ | 2595 | * automatically */ |
| 2602 | list_del_init(&old->mnt_expire); | 2596 | list_del_init(&old->mnt_expire); |
| 2603 | out1: | ||
| 2604 | unlock_mount(mp); | ||
| 2605 | out: | 2597 | out: |
| 2598 | unlock_mount(mp); | ||
| 2606 | if (!err) | 2599 | if (!err) |
| 2607 | path_put(&parent_path); | 2600 | 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); | ||
| 2608 | path_put(&old_path); | 2617 | path_put(&old_path); |
| 2609 | return err; | 2618 | return err; |
| 2610 | } | 2619 | } |
| @@ -3050,7 +3059,7 @@ long do_mount(const char *dev_name, const char __user *dir_name, | |||
| 3050 | else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE)) | 3059 | else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE)) |
| 3051 | retval = do_change_type(&path, flags); | 3060 | retval = do_change_type(&path, flags); |
| 3052 | else if (flags & MS_MOVE) | 3061 | else if (flags & MS_MOVE) |
| 3053 | retval = do_move_mount(&path, dev_name); | 3062 | retval = do_move_mount_old(&path, dev_name); |
| 3054 | else | 3063 | else |
| 3055 | retval = do_new_mount(&path, type_page, sb_flags, mnt_flags, | 3064 | retval = do_new_mount(&path, type_page, sb_flags, mnt_flags, |
| 3056 | dev_name, data_page); | 3065 | dev_name, data_page); |
| @@ -3279,6 +3288,61 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, | |||
| 3279 | } | 3288 | } |
| 3280 | 3289 | ||
| 3281 | /* | 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 | |||
| 3345 | /* | ||
| 3282 | * Return true if path is reachable from root | 3346 | * Return true if path is reachable from root |
| 3283 | * | 3347 | * |
| 3284 | * namespace_sem or mount_lock is held | 3348 | * namespace_sem or mount_lock is held |
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index a9b8ff578b6b..cb33f81cf5a1 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h | |||
| @@ -160,6 +160,10 @@ | |||
| 160 | * Parse a string of security data filling in the opts structure | 160 | * Parse a string of security data filling in the opts structure |
| 161 | * @options string containing all mount options known by the LSM | 161 | * @options string containing all mount options known by the LSM |
| 162 | * @opts binary data structure usable by the LSM | 162 | * @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. | ||
| 163 | * @dentry_init_security: | 167 | * @dentry_init_security: |
| 164 | * Compute a context for a dentry as the inode is not yet available | 168 | * Compute a context for a dentry as the inode is not yet available |
| 165 | * since NFSv4 has no label backed by an EA anyway. | 169 | * since NFSv4 has no label backed by an EA anyway. |
| @@ -1501,6 +1505,7 @@ union security_list_options { | |||
| 1501 | unsigned long *set_kern_flags); | 1505 | unsigned long *set_kern_flags); |
| 1502 | int (*sb_add_mnt_opt)(const char *option, const char *val, int len, | 1506 | int (*sb_add_mnt_opt)(const char *option, const char *val, int len, |
| 1503 | void **mnt_opts); | 1507 | void **mnt_opts); |
| 1508 | int (*move_mount)(const struct path *from_path, const struct path *to_path); | ||
| 1504 | int (*dentry_init_security)(struct dentry *dentry, int mode, | 1509 | int (*dentry_init_security)(struct dentry *dentry, int mode, |
| 1505 | const struct qstr *name, void **ctx, | 1510 | const struct qstr *name, void **ctx, |
| 1506 | u32 *ctxlen); | 1511 | u32 *ctxlen); |
| @@ -1835,6 +1840,7 @@ struct security_hook_heads { | |||
| 1835 | struct hlist_head sb_set_mnt_opts; | 1840 | struct hlist_head sb_set_mnt_opts; |
| 1836 | struct hlist_head sb_clone_mnt_opts; | 1841 | struct hlist_head sb_clone_mnt_opts; |
| 1837 | struct hlist_head sb_add_mnt_opt; | 1842 | struct hlist_head sb_add_mnt_opt; |
| 1843 | struct hlist_head move_mount; | ||
| 1838 | struct hlist_head dentry_init_security; | 1844 | struct hlist_head dentry_init_security; |
| 1839 | struct hlist_head dentry_create_files_as; | 1845 | struct hlist_head dentry_create_files_as; |
| 1840 | #ifdef CONFIG_SECURITY_PATH | 1846 | #ifdef CONFIG_SECURITY_PATH |
diff --git a/include/linux/security.h b/include/linux/security.h index 49f2685324b0..1f2e06afc28f 100644 --- a/include/linux/security.h +++ b/include/linux/security.h | |||
| @@ -250,6 +250,7 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb, | |||
| 250 | unsigned long *set_kern_flags); | 250 | unsigned long *set_kern_flags); |
| 251 | int security_add_mnt_opt(const char *option, const char *val, | 251 | int security_add_mnt_opt(const char *option, const char *val, |
| 252 | int len, void **mnt_opts); | 252 | int len, void **mnt_opts); |
| 253 | int security_move_mount(const struct path *from_path, const struct path *to_path); | ||
| 253 | int security_dentry_init_security(struct dentry *dentry, int mode, | 254 | int security_dentry_init_security(struct dentry *dentry, int mode, |
| 254 | const struct qstr *name, void **ctx, | 255 | const struct qstr *name, void **ctx, |
| 255 | u32 *ctxlen); | 256 | u32 *ctxlen); |
| @@ -611,6 +612,12 @@ static inline int security_add_mnt_opt(const char *option, const char *val, | |||
| 611 | return 0; | 612 | return 0; |
| 612 | } | 613 | } |
| 613 | 614 | ||
| 615 | static inline int security_move_mount(const struct path *from_path, | ||
| 616 | const struct path *to_path) | ||
| 617 | { | ||
| 618 | return 0; | ||
| 619 | } | ||
| 620 | |||
| 614 | static inline int security_inode_alloc(struct inode *inode) | 621 | static inline int security_inode_alloc(struct inode *inode) |
| 615 | { | 622 | { |
| 616 | return 0; | 623 | return 0; |
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index 6c29d586e66b..84347fc0a1a7 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h | |||
| @@ -986,6 +986,9 @@ asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags, | |||
| 986 | asmlinkage long sys_rseq(struct rseq __user *rseq, uint32_t rseq_len, | 986 | asmlinkage long sys_rseq(struct rseq __user *rseq, uint32_t rseq_len, |
| 987 | int flags, uint32_t sig); | 987 | int flags, uint32_t sig); |
| 988 | asmlinkage long sys_open_tree(int dfd, const char __user *path, unsigned flags); | 988 | 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); | ||
| 989 | asmlinkage long sys_pidfd_send_signal(int pidfd, int sig, | 992 | asmlinkage long sys_pidfd_send_signal(int pidfd, int sig, |
| 990 | siginfo_t __user *info, | 993 | siginfo_t __user *info, |
| 991 | unsigned int flags); | 994 | unsigned int flags); |
diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h index fd7ae2e7eccf..3634e065836c 100644 --- a/include/uapi/linux/mount.h +++ b/include/uapi/linux/mount.h | |||
| @@ -61,4 +61,15 @@ | |||
| 61 | #define OPEN_TREE_CLONE 1 /* Clone the target tree and attach the clone */ | 61 | #define OPEN_TREE_CLONE 1 /* Clone the target tree and attach the clone */ |
| 62 | #define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */ | 62 | #define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */ |
| 63 | 63 | ||
| 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 | |||
| 64 | #endif /* _UAPI_LINUX_MOUNT_H */ | 75 | #endif /* _UAPI_LINUX_MOUNT_H */ |
diff --git a/security/security.c b/security/security.c index 23cbb1a295a3..5b3d23e427b3 100644 --- a/security/security.c +++ b/security/security.c | |||
| @@ -866,6 +866,11 @@ int security_add_mnt_opt(const char *option, const char *val, int len, | |||
| 866 | } | 866 | } |
| 867 | EXPORT_SYMBOL(security_add_mnt_opt); | 867 | EXPORT_SYMBOL(security_add_mnt_opt); |
| 868 | 868 | ||
| 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 | |||
| 869 | int security_inode_alloc(struct inode *inode) | 874 | int security_inode_alloc(struct inode *inode) |
| 870 | { | 875 | { |
| 871 | int rc = lsm_inode_alloc(inode); | 876 | int rc = lsm_inode_alloc(inode); |
