@@ -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
new file mode 100644
@@ -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@HansenPartnership.com
+ */
+
+#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);
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@HansenPartnership.com> --- 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