This can do the equivalent of open_tree and also do bind mount reconfiguration from ro to rw and vice versa. To get the equvalent of open tree you need to do mnt = open("/path/to/tree", O_PATH); fd = configfs_open(bind, O_CLOEXEC); configfs_action(fd, CONFIGFD_SET_FD, "pathfd", NULL, mnt); configfs_action(fd, CONFIGFD_SET_FLAG, "detached", NULL, 0); configfs_action(fd, CONFIGFD_SET_FLAG, "recursive", NULL, 0); configfs_action(fd, CONFIGFD_CMD_CREATE, NULL, NULL, 0); configfs_action(fd, CONFIGFD_GET_FD, "bindfd", &bfd, NULL, O_CLOEXEC); And bfd will now contain the file descriptor to pass to move_tree. There is a deficiency over the original implementation in that the open system call has no way of clearing the LOOKUP_AUTOMOUNT path, but that's fixable. To do a mount reconfigure to change the bind mount to readonly do mnt = open("/path/to/tree", O_PATH); fd = configfs_open(bind, O_CLOEXEC); configfs_action(fd, CONFIGFD_SET_FD, "pathfd", NULL, mnt); configfs_action(fd, CONFIGFD_SET_FLAG, "ro", NULL, 0); configfs_action(fd, CONFIGFD_CMD_RECONFIGURE, NULL, NULL, 0); And the bind properties will be changed. You can also pass the "rw" flag to reset the read only. Signed-off-by: James Bottomley <James.Bottomley@xxxxxxxxxxxxxxxxxxxxx> --- v2: add nodev and noexec mount reconfigurations --- fs/Makefile | 2 +- fs/bind.c | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 fs/bind.c diff --git a/fs/Makefile b/fs/Makefile index de83671ce80d..a7077e5cd60d 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -14,7 +14,7 @@ obj-y := open.o read_write.o file_table.o super.o \ pnode.o splice.o sync.o utimes.o d_path.o \ stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \ fs_types.o fs_context.o fs_parser.o fsopen.o \ - configfd.o + configfd.o bind.o ifeq ($(CONFIG_BLOCK),y) obj-y += buffer.o block_dev.o direct-io.o mpage.o diff --git a/fs/bind.c b/fs/bind.c new file mode 100644 index 000000000000..eea4e6cd5108 --- /dev/null +++ b/fs/bind.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Dummy configfd handler for doing context based configuration + * on bind mounts + * + * Copyright (C) James.Bottomley@xxxxxxxxxxxxxxxxxxxxx + */ + +#include <linux/configfd.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/mount.h> +#include <linux/nsproxy.h> + +#include "internal.h" +#include "mount.h" + +struct bind_data { + bool ro:1; + bool noexec:1; + bool nosuid:1; + bool nodev:1; + bool detached:1; + bool recursive:1; + struct file *file; + struct file *retfile; +}; + +struct bind_data *to_bind_data(const struct configfd_context *cfc) +{ + return cfc->data; +} + +static int bind_set_fd(const struct configfd_context *cfc, + struct configfd_param *p) +{ + struct bind_data *bd = to_bind_data(cfc); + struct path *path; + + if (strcmp(p->key, "pathfd") != 0) + return -EINVAL; + + path = &p->file->f_path; + + if (cfc->op == CONFIGFD_CMD_RECONFIGURE && + path->mnt->mnt_root != path->dentry) { + logger_err(cfc->log, "pathfd must be a bind mount"); + return -EINVAL; + } + bd->file = p->file; + p->file = NULL; /* we now own */ + return 0; +} + +static int bind_set_flag(const struct configfd_context *cfc, + struct configfd_param *p) +{ + struct bind_data *bd = to_bind_data(cfc); + + if (strcmp(p->key, "ro") == 0) { + bd->ro = true; + } else if (strcmp(p->key, "rw") == 0) { + bd->ro = false; + } else if (strcmp(p->key, "nosuid") == 0) { + bd->nosuid = true; + } else if (strcmp(p->key, "nodev") == 0) { + bd->nodev = true; + } else if (strcmp(p->key, "noexec") == 0) { + bd->noexec = true; + } else if (strcmp(p->key, "recursive") == 0 && + cfc->op == CONFIGFD_CMD_CREATE) { + bd->recursive = true; + } else if (strcmp(p->key, "detached") == 0 && + cfc->op == CONFIGFD_CMD_CREATE) { + if (!ns_capable(current->nsproxy->mnt_ns->user_ns, + CAP_SYS_ADMIN)) { + logger_err(cfc->log, "bind set: insufficient permission for detached tree"); + return -EPERM; + } + bd->detached = true; + } else { + logger_err(cfc->log, "bind set: invalid flag %s", p->key); + return -EINVAL; + } + return 0; +} +static int bind_set(const struct configfd_context *cfc, + struct configfd_param *p) +{ + switch (p->cmd) { + case CONFIGFD_SET_FLAG: + return bind_set_flag(cfc, p); + case CONFIGFD_SET_FD: + return bind_set_fd(cfc, p); + default: + logger_err(cfc->log, "bind only takes a flag or fd argument"); + return -EINVAL; + } +} + +static int bind_get(const struct configfd_context *cfc, + struct configfd_param *p) +{ + struct bind_data *bd = to_bind_data(cfc); + + if (strcmp(p->key, "bindfd") != 0 || p->cmd != CONFIGFD_GET_FD) + return -EINVAL; + + if (!bd->retfile) + return -EINVAL; + + p->file = bd->retfile; + bd->retfile = NULL; + + return 0; +} + +static int bind_get_mnt_flags(struct bind_data *bd, int mnt_flags) +{ + /* for an unprivileged bind, the ATIME will be locked so keep the same */ + mnt_flags = mnt_flags & MNT_ATIME_MASK; + if (bd->ro) + mnt_flags |= MNT_READONLY; + if (bd->nosuid) + mnt_flags |= MNT_NOSUID; + if (bd->nodev) + mnt_flags |= MNT_NODEV; + if (bd->noexec) + mnt_flags |= MNT_NOEXEC; + + return mnt_flags; +} + +static int bind_reconfigure(const struct configfd_context *cfc) +{ + struct bind_data *bd = to_bind_data(cfc); + unsigned int mnt_flags; + + if (!bd->file) { + logger_err(cfc->log, "bind reconfigure: fd must be set"); + return -EINVAL; + } + /* for an unprivileged bind, the ATIME will be locked so keep the same */ + mnt_flags = bd->file->f_path.mnt->mnt_flags & MNT_ATIME_MASK; + mnt_flags = bind_get_mnt_flags(bd, mnt_flags); + + return do_reconfigure_mnt(&bd->file->f_path, mnt_flags); +} + +static int bind_create(const struct configfd_context *cfc) +{ + struct bind_data *bd = to_bind_data(cfc); + struct path *p; + struct file *f; + + if (!bd->file) { + logger_err(cfc->log, "bind create: fd must be set"); + return -EINVAL; + } + if (bd->recursive && !bd->detached) { + logger_err(cfc->log, "bind create: recursive cannot be set without detached"); + return -EINVAL; + } + + if ((bd->ro || bd->nosuid || bd->noexec || bd->nodev) && + !bd->detached) { + logger_err(cfc->log, "bind create: to use ro,rw,nosuid or noexec, mount must be detached"); + return -EINVAL; + } + + p = &bd->file->f_path; + + if (bd->detached) + f = open_detached_copy(p, bd->recursive); + else + f = dentry_open(p, O_PATH, current_cred()); + if (IS_ERR(f)) + return PTR_ERR(f); + + if (bd->detached) { + int mnt_flags = f->f_path.mnt->mnt_flags & MNT_ATIME_MASK; + + mnt_flags = bind_get_mnt_flags(bd, mnt_flags); + + /* since this is a detached copy, we can do without locking */ + f->f_path.mnt->mnt_flags |= mnt_flags; + } + + bd->retfile = f; + return 0; +} + +static int bind_act(const struct configfd_context *cfc, unsigned int cmd) +{ + switch (cmd) { + case CONFIGFD_CMD_RECONFIGURE: + return bind_reconfigure(cfc); + case CONFIGFD_CMD_CREATE: + return bind_create(cfc); + default: + logger_err(cfc->log, "bind only responds to reconfigure or create actions"); + return -EINVAL; + } +} + +static void bind_free(const struct configfd_context *cfc) +{ + struct bind_data *bd = to_bind_data(cfc); + + if (bd->file) + fput(bd->file); +} + +static struct configfd_ops bind_type_ops = { + .free = bind_free, + .get = bind_get, + .set = bind_set, + .act = bind_act, +}; + +static struct configfd_type bind_type = { + .name = "bind", + .ops = &bind_type_ops, + .data_size = sizeof(struct bind_data), +}; + +static int __init bind_setup(void) +{ + configfd_type_register(&bind_type); + + return 0; +} +fs_initcall(bind_setup); -- 2.16.4