| /* Filesystem access-by-fd. |
| * |
| * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved. |
| * Written by David Howells (dhowells@redhat.com) |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public Licence |
| * as published by the Free Software Foundation; either version |
| * 2 of the Licence, or (at your option) any later version. |
| */ |
| |
| #include <linux/fs_context.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/syscalls.h> |
| #include <linux/security.h> |
| #include <linux/anon_inodes.h> |
| #include <linux/namei.h> |
| #include "mount.h" |
| |
| /* |
| * Userspace writes configuration data and commands to the fd and we parse it |
| * here. For the moment, we assume a single option or command per write. Each |
| * line written is of the form |
| * |
| * <command_type><space><stuff...> |
| * |
| * s /dev/sda1 -- Source device |
| * o noatime -- Option without value |
| * o cell=grand.central.org -- Option with value |
| * x create -- Create a superblock |
| * x reconfigure -- Reconfigure a superblock |
| */ |
| static ssize_t fscontext_write(struct file *file, |
| const char __user *_buf, size_t len, loff_t *pos) |
| { |
| struct fs_context *fc = file->private_data; |
| char opt[2], *data; |
| ssize_t ret; |
| |
| if (len < 3 || len > 4095) |
| return -EINVAL; |
| |
| if (copy_from_user(opt, _buf, 2) != 0) |
| return -EFAULT; |
| switch (opt[0]) { |
| case 's': |
| case 'o': |
| case 'x': |
| break; |
| default: |
| return -EINVAL; |
| } |
| if (opt[1] != ' ') |
| return -EINVAL; |
| |
| data = memdup_user_nul(_buf + 2, len - 2); |
| if (IS_ERR(data)) |
| return PTR_ERR(data); |
| |
| /* From this point onwards we need to lock the fd against someone |
| * trying to mount it. |
| */ |
| ret = mutex_lock_interruptible(&fc->uapi_mutex); |
| if (ret < 0) |
| goto err_free; |
| |
| if (fc->phase == FS_CONTEXT_AWAITING_RECONF) { |
| if (fc->fs_type->init_fs_context) { |
| ret = fc->fs_type->init_fs_context(fc, fc->root); |
| if (ret < 0) { |
| fc->phase = FS_CONTEXT_FAILED; |
| goto err_unlock; |
| } |
| } else { |
| /* Leave legacy context ops in place */ |
| } |
| |
| /* Do the security check last because ->init_fs_context may |
| * change the namespace subscriptions. |
| */ |
| ret = security_fs_context_alloc(fc, fc->root); |
| if (ret < 0) { |
| fc->phase = FS_CONTEXT_FAILED; |
| goto err_unlock; |
| } |
| |
| fc->phase = FS_CONTEXT_RECONF_PARAMS; |
| } |
| |
| ret = -EINVAL; |
| switch (opt[0]) { |
| case 's': |
| if (fc->phase != FS_CONTEXT_CREATE_PARAMS && |
| fc->phase != FS_CONTEXT_RECONF_PARAMS) |
| goto wrong_phase; |
| ret = vfs_set_fs_source(fc, data, len - 2); |
| if (ret < 0) |
| goto err_unlock; |
| break; |
| |
| case 'o': |
| if (fc->phase != FS_CONTEXT_CREATE_PARAMS && |
| fc->phase != FS_CONTEXT_RECONF_PARAMS) |
| goto wrong_phase; |
| ret = vfs_parse_fs_option(fc, data, len - 2); |
| if (ret < 0) |
| goto err_unlock; |
| break; |
| |
| case 'x': |
| if (strcmp(data, "create") == 0) { |
| if (fc->phase != FS_CONTEXT_CREATE_PARAMS) |
| goto wrong_phase; |
| fc->phase = FS_CONTEXT_CREATING; |
| ret = vfs_get_tree(fc); |
| if (ret == 0) |
| fc->phase = FS_CONTEXT_AWAITING_MOUNT; |
| else |
| fc->phase = FS_CONTEXT_FAILED; |
| } else { |
| ret = -EOPNOTSUPP; |
| } |
| if (ret < 0) |
| goto err_unlock; |
| break; |
| |
| default: |
| goto err_unlock; |
| } |
| |
| ret = len; |
| err_unlock: |
| mutex_unlock(&fc->uapi_mutex); |
| err_free: |
| kfree(data); |
| return ret; |
| |
| wrong_phase: |
| ret = -EBUSY; |
| goto err_unlock; |
| } |
| |
| /* |
| * Allow the user to read back any error, warning or informational messages. |
| */ |
| static ssize_t fscontext_read(struct file *file, |
| char __user *_buf, size_t len, loff_t *pos) |
| { |
| struct fs_context *fc = file->private_data; |
| struct fc_log *log = fc->log; |
| unsigned int logsize = ARRAY_SIZE(log->buffer); |
| ssize_t ret; |
| char *p; |
| bool need_free; |
| int index, n; |
| |
| ret = mutex_lock_interruptible(&fc->uapi_mutex); |
| if (ret < 0) |
| return ret; |
| |
| if (log->head == log->tail) { |
| mutex_unlock(&fc->uapi_mutex); |
| return -ENODATA; |
| } |
| |
| index = log->tail & (logsize - 1); |
| p = log->buffer[index]; |
| need_free = log->need_free & (1 << index); |
| log->buffer[index] = NULL; |
| log->need_free &= ~(1 << index); |
| log->tail++; |
| mutex_unlock(&fc->uapi_mutex); |
| |
| ret = -EMSGSIZE; |
| n = strlen(p); |
| if (n > len) |
| goto err_free; |
| ret = -EFAULT; |
| if (copy_to_user(_buf, p, n) != 0) |
| goto err_free; |
| ret = n; |
| |
| err_free: |
| if (need_free) |
| kfree(p); |
| return ret; |
| } |
| |
| static int fscontext_release(struct inode *inode, struct file *file) |
| { |
| struct fs_context *fc = file->private_data; |
| |
| if (fc) { |
| file->private_data = NULL; |
| put_fs_context(fc); |
| } |
| return 0; |
| } |
| |
| const struct file_operations fscontext_fs_fops = { |
| .read = fscontext_read, |
| .write = fscontext_write, |
| .release = fscontext_release, |
| .llseek = no_llseek, |
| }; |
| |
| /* |
| * Attach a filesystem context to a file and an fd. |
| */ |
| static int fscontext_create_fd(struct fs_context *fc, unsigned int o_flags) |
| { |
| int fd; |
| |
| fd = anon_inode_getfd("fscontext", &fscontext_fs_fops, fc, |
| O_RDWR | o_flags); |
| if (fd < 0) |
| put_fs_context(fc); |
| return fd; |
| } |
| |
| static int fscontext_alloc_log(struct fs_context *fc) |
| { |
| fc->log = kzalloc(sizeof(*fc->log), GFP_KERNEL); |
| if (!fc->log) |
| return -ENOMEM; |
| refcount_set(&fc->log->usage, 1); |
| fc->log->owner = fc->fs_type->owner; |
| return 0; |
| } |
| |
| /* |
| * Open a filesystem by name so that it can be configured for mounting. |
| * |
| * We are allowed to specify a container in which the filesystem will be |
| * opened, thereby indicating which namespaces will be used (notably, which |
| * network namespace will be used for network filesystems). |
| */ |
| SYSCALL_DEFINE2(fsopen, const char __user *, _fs_name, unsigned int, flags) |
| { |
| struct file_system_type *fs_type; |
| struct fs_context *fc; |
| const char *fs_name; |
| int ret; |
| |
| if (!ns_capable(current->nsproxy->mnt_ns->user_ns, CAP_SYS_ADMIN)) |
| return -EPERM; |
| |
| if (flags & ~FSOPEN_CLOEXEC) |
| return -EINVAL; |
| |
| fs_name = strndup_user(_fs_name, PAGE_SIZE); |
| if (IS_ERR(fs_name)) |
| return PTR_ERR(fs_name); |
| |
| fs_type = get_fs_type(fs_name); |
| kfree(fs_name); |
| if (!fs_type) |
| return -ENODEV; |
| |
| fc = vfs_new_fs_context(fs_type, NULL, 0, FS_CONTEXT_FOR_USER_MOUNT); |
| put_filesystem(fs_type); |
| if (IS_ERR(fc)) |
| return PTR_ERR(fc); |
| |
| fc->phase = FS_CONTEXT_CREATE_PARAMS; |
| |
| ret = fscontext_alloc_log(fc); |
| if (ret < 0) |
| goto err_fc; |
| |
| return fscontext_create_fd(fc, flags & FSOPEN_CLOEXEC ? O_CLOEXEC : 0); |
| |
| err_fc: |
| put_fs_context(fc); |
| return ret; |
| } |
| |
| /* |
| * Pick a superblock into a context for reconfiguration. |
| */ |
| SYSCALL_DEFINE3(fspick, int, dfd, const char __user *, path, unsigned int, flags) |
| { |
| struct fs_context *fc; |
| struct path target; |
| unsigned int lookup_flags; |
| int ret; |
| |
| if (!ns_capable(current->nsproxy->mnt_ns->user_ns, CAP_SYS_ADMIN)) |
| return -EPERM; |
| |
| if ((flags & ~(FSPICK_CLOEXEC | |
| FSPICK_SYMLINK_NOFOLLOW | |
| FSPICK_NO_AUTOMOUNT | |
| FSPICK_EMPTY_PATH)) != 0) |
| return -EINVAL; |
| |
| lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT; |
| if (flags & FSPICK_SYMLINK_NOFOLLOW) |
| lookup_flags &= ~LOOKUP_FOLLOW; |
| if (flags & FSPICK_NO_AUTOMOUNT) |
| lookup_flags &= ~LOOKUP_AUTOMOUNT; |
| if (flags & FSPICK_EMPTY_PATH) |
| lookup_flags |= LOOKUP_EMPTY; |
| ret = user_path_at(dfd, path, lookup_flags, &target); |
| if (ret < 0) |
| goto err; |
| |
| ret = -EOPNOTSUPP; |
| if (!target.dentry->d_sb->s_op->reconfigure) |
| goto err_path; |
| |
| fc = vfs_new_fs_context(target.dentry->d_sb->s_type, target.dentry, |
| 0, FS_CONTEXT_FOR_RECONFIGURE); |
| if (IS_ERR(fc)) { |
| ret = PTR_ERR(fc); |
| goto err_path; |
| } |
| |
| fc->phase = FS_CONTEXT_RECONF_PARAMS; |
| |
| ret = fscontext_alloc_log(fc); |
| if (ret < 0) |
| goto err_fc; |
| |
| path_put(&target); |
| return fscontext_create_fd(fc, flags & FSPICK_CLOEXEC ? O_CLOEXEC : 0); |
| |
| err_fc: |
| put_fs_context(fc); |
| err_path: |
| path_put(&target); |
| err: |
| return ret; |
| } |