[PATCH 02/10] vfs: syscall: Add move_mount(2) to move mounts around

Alan Jenkins alan.christopher.jenkins at gmail.com
Wed Feb 20 12:41:51 UTC 2019


On 20/02/2019 12:32, Alan Jenkins wrote:
> On 19/02/2019 17:08, David Howells wrote:
>> 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>
>> ---
>>
>>   arch/x86/entry/syscalls/syscall_32.tbl |    1
>>   arch/x86/entry/syscalls/syscall_64.tbl |    1
>>   fs/namespace.c                         |  126 
>> ++++++++++++++++++++++++--------
>>   include/linux/lsm_hooks.h              |    6 ++
>>   include/linux/security.h               |    7 ++
>>   include/linux/syscalls.h               |    3 +
>>   include/uapi/linux/mount.h             |   11 +++
>>   security/security.c                    |    5 +
>>   8 files changed, 129 insertions(+), 31 deletions(-)
>
>> diff --git a/fs/namespace.c b/fs/namespace.c
>> index 112d46f26fc3..f10122028a11 100644
>> --- a/fs/namespace.c
>> +++ b/fs/namespace.c
>> @@ -2537,72 +2537,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.
>> +     */
>
> Comment is incorrect.
>
> * FMODE_NEED_UNMOUNT is never cleared.
>
> * Technically I don't see anything preventing them giving a pathname, 
> but it needs to be "." or equivalent.  Otherwise it will fail the 
> "!attached" check in the next patch.

>
> * The only argument I remember for preventing this, was that it might 
> confuse users (not the kernel).  If you are allowed to move from a 
> sub-mount, then in certain programming styles - like my shell script 
> test cases - you might accidentally close the original file too 
> early.  Then you won't be able to do move_mount() from the tree, 
> because the tree was unmounted ("dissolved") when you closed it.
>
> I think the description in the previous patch, for open_tree(), makes 
> it clear though. "The detached tree will be dissolved on the final 
> close of obtained file".
>
> If there is a good reason, I expect we can simply remove the 
> "!attached" part of the check.  If the constraint is generating more 
> confusion than the added flexibility, I think that would be a good 
> reason :-).

Sorry, I see it.  Although you are not clearing a flag, you have to free 
the old value of old->mnt_ns.  And that is not being reference-counted, 
it has a single owner, the file which has FMODE_NEED_UNMOUNT.  So it is 
not possible to simply remove the "!attached" check.

I still find the comment confusing, i.e. describing this as clearing a flag.

Alan



More information about the Linux-security-module-archive mailing list