All of lore.kernel.org
 help / color / mirror / Atom feed
From: Eric Curtin <ericcurtin17@gmail.com>
To: Alexander Viro <viro@zeniv.linux.org.uk>,
	Christian Brauner <brauner@kernel.org>
Cc: Jan Kara <jack@suse.cz>, Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	Eric Biggers <ebiggers@kernel.org>,
	"Theodore Y . Ts'o" <tytso@mit.edu>, Gao Xiang <xiang@kernel.org>,
	Chao Yu <chao@kernel.org>,
	fsverity@lists.linux.dev, linux-erofs@lists.ozlabs.org,
	linux-fsdevel@vger.kernel.org, linux-doc@vger.kernel.org,
	linux-kernel@vger.kernel.org,
	Eric Curtin <ericcurtin17@gmail.com>
Subject: [RFC PATCH v2 1/4] fs: allow in-kernel mounters to hand filesystems an already-open source file
Date: Mon, 27 Jul 2026 11:48:42 +0100	[thread overview]
Message-ID: <20260727104845.2607444-2-ericcurtin17@gmail.com> (raw)
In-Reply-To: <20260727-gepaukt-eislauf-waran-7c03f0e47609@brauner>

do_new_mount() and its path_mount() wrapper only ever let a filesystem's
->get_tree() resolve its own source, either from a path string that the
kernel translates to fc->source via vfs_parse_fs_param_source(), or from
whatever the filesystem's own ->parse_param() does with it. There is no
way for a caller that already has an open, already-validated struct file
in hand to mount from *that exact file*: the best it can do is hand the
filesystem a path and hope nothing about the target changed by the time
the filesystem re-resolves it, which is not a guarantee the VFS can make.

Add struct fs_context::source_file alongside the existing ::source, set
via a new vfs_parse_fs_param_file() instead of the string parameter
path, and a new path_mount_file() / init_mount_file() pair mirroring
path_mount() / init_mount() that use it. This is deliberately not wired
up to the fsconfig(FSCONFIG_SET_FD) uAPI: it is for in-kernel callers
only, so no existing filesystem's ->parse_param() needs to change to
avoid misinterpreting it, and none is affected by this patch.

Filesystems that support being mounted from a plain file and want to
accept exactly the file a caller hands them, instead of independently
re-resolving fc->source (unset in this case), check fc->source_file
from their ->get_tree(). erofs is converted to do so in the next patch;
the immediate motivation is letting init/do_mounts.c mount an fsverity-
checked root image from the same struct file it just checked, instead
of a path that erofs would then look up again on its own.

Signed-off-by: Eric Curtin <ericcurtin17@gmail.com>
---
 Documentation/filesystems/mount_api.rst | 18 +++++
 fs/fs_context.c                         | 39 +++++++++-
 fs/init.c                               | 18 +++++
 fs/internal.h                           |  2 +
 fs/namespace.c                          | 96 ++++++++++++++++++++++++-
 include/linux/fs_context.h              |  4 ++
 include/linux/init_syscalls.h           |  2 +
 7 files changed, 175 insertions(+), 4 deletions(-)

diff --git a/Documentation/filesystems/mount_api.rst b/Documentation/filesystems/mount_api.rst
index e8b94357b4df..7b799f61c39d 100644
--- a/Documentation/filesystems/mount_api.rst
+++ b/Documentation/filesystems/mount_api.rst
@@ -138,6 +138,24 @@ The fs_context fields are as follows:
      This specifies the source.  It may be a block device (e.g. /dev/sda1) or
      something more exotic, such as the "host:/path" that NFS desires.
 
+   * ::
+
+       struct file *source_file
+
+     An alternative to ``source`` for in-kernel mounters only (there is no
+     userspace-visible way to set this): an already-open file to use as the
+     source, set by vfs_parse_fs_param_file() instead of parsing a path
+     string.  At most one of ``source`` and ``source_file`` is ever set.
+
+     Filesystems that support being mounted from a plain file (as opposed to
+     a block device) and want to let such an in-kernel caller hand them an
+     exact, already-validated file - rather than a path they would then have
+     to re-resolve themselves, with no way to guarantee the two refer to the
+     same thing - should check this field in their ``get_tree`` and use it
+     directly instead of falling back to opening ``source``.  See
+     fs/erofs/super.c:erofs_fc_get_tree() for reference, and
+     init/do_mounts.c:mount_root_image() for the in-kernel caller.
+
    * ::
 
        char *subtype
diff --git a/fs/fs_context.c b/fs/fs_context.c
index 23ad66cd94e1..ab853147b87b 100644
--- a/fs/fs_context.c
+++ b/fs/fs_context.c
@@ -85,7 +85,7 @@ int vfs_parse_fs_param_source(struct fs_context *fc, struct fs_parameter *param)
 	if (param->type != fs_value_is_string)
 		return invalf(fc, "Non-string source");
 
-	if (fc->source)
+	if (fc->source || fc->source_file)
 		return invalf(fc, "Multiple sources");
 
 	fc->source = param->string;
@@ -94,6 +94,36 @@ int vfs_parse_fs_param_source(struct fs_context *fc, struct fs_parameter *param)
 }
 EXPORT_SYMBOL(vfs_parse_fs_param_source);
 
+/**
+ * vfs_parse_fs_param_file - Set an already-open file as the mount source
+ * @fc: The filesystem context to modify
+ * @key: Parameter name; only "source" is currently supported
+ * @file: The file to use as the mount source
+ *
+ * In-kernel callers (see path_mount_file()) can use this to hand a
+ * filesystem an already-opened struct file as its source, instead of a
+ * path string for the filesystem to resolve on its own.  A reference is
+ * taken; it is released when @fc is freed.
+ *
+ * This deliberately has no fsconfig(FSCONFIG_SET_FD) counterpart: it is
+ * not reachable from userspace, so no filesystem needs to opt in to
+ * receive it and existing ->parse_param() implementations are unaffected.
+ * Filesystems that want to use the file directly (rather than silently
+ * ignoring it and falling back to re-resolving fc->source, which is not
+ * set in this case) check fc->source_file from their ->get_tree().
+ */
+int vfs_parse_fs_param_file(struct fs_context *fc, const char *key,
+			    struct file *file)
+{
+	if (strcmp(key, "source"))
+		return -EINVAL;
+	if (fc->source || fc->source_file)
+		return invalf(fc, "Multiple sources");
+	fc->source_file = get_file(file);
+	return 0;
+}
+EXPORT_SYMBOL(vfs_parse_fs_param_file);
+
 /**
  * vfs_parse_fs_param - Add a single parameter to a superblock config
  * @fc: The filesystem context to modify
@@ -376,6 +406,7 @@ struct fs_context *vfs_dup_fs_context(struct fs_context *src_fc)
 	fc->fs_private	= NULL;
 	fc->s_fs_info	= NULL;
 	fc->source	= NULL;
+	fc->source_file	= NULL;
 	fc->security	= NULL;
 	get_filesystem(fc->fs_type);
 	get_net(fc->net_ns);
@@ -504,6 +535,8 @@ void put_fs_context(struct fs_context *fc)
 	put_fc_log(fc);
 	put_filesystem(fc->fs_type);
 	kfree(fc->source);
+	if (fc->source_file)
+		fput(fc->source_file);
 	kfree(fc);
 }
 EXPORT_SYMBOL(put_fs_context);
@@ -543,6 +576,10 @@ void vfs_clean_context(struct fs_context *fc)
 	security_free_mnt_opts(&fc->security);
 	kfree(fc->source);
 	fc->source = NULL;
+	if (fc->source_file) {
+		fput(fc->source_file);
+		fc->source_file = NULL;
+	}
 	fc->exclusive = false;
 
 	fc->purpose = FS_CONTEXT_FOR_RECONFIGURE;
diff --git a/fs/init.c b/fs/init.c
index 33e312d74f58..d896d937e3e2 100644
--- a/fs/init.c
+++ b/fs/init.c
@@ -44,6 +44,24 @@ int __init init_mount(const char *dev_name, const char *dir_name,
 	return ret;
 }
 
+/*
+ * Like init_mount(), but the source is an already-open file rather than a
+ * path for the filesystem to resolve on its own; see path_mount_file().
+ */
+int __init init_mount_file(struct file *file, const char *dir_name,
+		const char *type_page, unsigned long flags, void *data_page)
+{
+	struct path path;
+	int ret;
+
+	ret = kern_path(dir_name, LOOKUP_FOLLOW, &path);
+	if (ret)
+		return ret;
+	ret = path_mount_file(file, &path, type_page, flags, data_page);
+	path_put(&path);
+	return ret;
+}
+
 int __init init_umount(const char *name, int flags)
 {
 	int lookup_flags = LOOKUP_MOUNTPOINT;
diff --git a/fs/internal.h b/fs/internal.h
index d77578d66d42..04fb75383024 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -91,6 +91,8 @@ extern bool may_mount(void);
 
 int path_mount(const char *dev_name, const struct path *path,
 		const char *type_page, unsigned long flags, void *data_page);
+int path_mount_file(struct file *file, const struct path *path,
+		const char *type_page, unsigned long flags, void *data_page);
 int path_umount(const struct path *path, int flags);
 int path_pivot_root(struct path *new, struct path *old);
 
diff --git a/fs/namespace.c b/fs/namespace.c
index fe919abd2f01..31bf263d1efc 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3783,10 +3783,14 @@ static int do_new_mount_fc(struct fs_context *fc, const struct path *mountpoint,
 /*
  * create a new mount for userspace and request it to be added into the
  * namespace's tree
+ *
+ * @name is the source given as a path string, as usual; @file is an
+ * alternative, already-open source (see path_mount_file()).  At most one
+ * of the two is ever set by callers.
  */
-static int do_new_mount(const struct path *path, const char *fstype,
-			int sb_flags, int mnt_flags,
-			const char *name, void *data)
+static int do_new_mount_2(const struct path *path, const char *fstype,
+			  int sb_flags, int mnt_flags,
+			  const char *name, struct file *file, void *data)
 {
 	struct file_system_type *type;
 	struct fs_context *fc;
@@ -3826,6 +3830,8 @@ static int do_new_mount(const struct path *path, const char *fstype,
 		err = vfs_parse_fs_string(fc, "subtype", subtype);
 	if (!err && name)
 		err = vfs_parse_fs_string(fc, "source", name);
+	if (!err && file)
+		err = vfs_parse_fs_param_file(fc, "source", file);
 	if (!err)
 		err = parse_monolithic_mount_data(fc, data);
 	if (!err && !mount_capable(fc))
@@ -3837,6 +3843,14 @@ static int do_new_mount(const struct path *path, const char *fstype,
 	return err;
 }
 
+static int do_new_mount(const struct path *path, const char *fstype,
+			int sb_flags, int mnt_flags,
+			const char *name, void *data)
+{
+	return do_new_mount_2(path, fstype, sb_flags, mnt_flags, name, NULL,
+			      data);
+}
+
 static void lock_mount_exact(const struct path *path,
 			     struct pinned_mountpoint *mp, bool copy_mount,
 			     unsigned int copy_flags)
@@ -4155,6 +4169,82 @@ int path_mount(const char *dev_name, const struct path *path,
 			    data_page);
 }
 
+/**
+ * path_mount_file - Mount a new filesystem sourced from an already-open file
+ * @file: The already-open source file
+ * @path: The mountpoint
+ * @type_page: Filesystem type
+ * @flags: MS_* flags
+ * @data_page: Filesystem-specific mount data
+ *
+ * Like path_mount(), except the source is an already-open struct file
+ * rather than a path for the filesystem to look up on its own.  This is
+ * only usable for a plain new mount: bind/move/remount/propagation-change
+ * requests, which path_mount() distinguishes by inspecting @dev_name, make
+ * no sense for an anonymous open file and are rejected.
+ *
+ * @file is not consumed; the fs_context takes its own reference (see
+ * vfs_parse_fs_param_file()).
+ *
+ * This has no io_uring/syscall-visible counterpart: it exists so that
+ * in-kernel mounters (see init_mount_file()) can hand a filesystem the
+ * exact file they resolved and validated, instead of a path that the
+ * filesystem then re-resolves independently, which is the only way to
+ * guarantee the two refer to the same thing.
+ */
+int path_mount_file(struct file *file, const struct path *path,
+		    const char *type_page, unsigned long flags,
+		    void *data_page)
+{
+	unsigned int mnt_flags = 0, sb_flags;
+
+	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
+		flags &= ~MS_MGC_MSK;
+
+	if (data_page)
+		((char *)data_page)[PAGE_SIZE - 1] = 0;
+
+	if (flags & (MS_NOUSER | MS_REMOUNT | MS_BIND | MS_MOVE |
+		     MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
+		return -EINVAL;
+
+	if (!may_mount())
+		return -EPERM;
+	if (flags & SB_MANDLOCK)
+		warn_mandlock();
+
+	if (!(flags & MS_NOATIME))
+		mnt_flags |= MNT_RELATIME;
+	if (flags & MS_NOSUID)
+		mnt_flags |= MNT_NOSUID;
+	if (flags & MS_NODEV)
+		mnt_flags |= MNT_NODEV;
+	if (flags & MS_NOEXEC)
+		mnt_flags |= MNT_NOEXEC;
+	if (flags & MS_NOATIME)
+		mnt_flags |= MNT_NOATIME;
+	if (flags & MS_NODIRATIME)
+		mnt_flags |= MNT_NODIRATIME;
+	if (flags & MS_STRICTATIME)
+		mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
+	if (flags & MS_RDONLY)
+		mnt_flags |= MNT_READONLY;
+	if (flags & MS_NOSYMFOLLOW)
+		mnt_flags |= MNT_NOSYMFOLLOW;
+
+	sb_flags = flags & (SB_RDONLY |
+			    SB_SYNCHRONOUS |
+			    SB_MANDLOCK |
+			    SB_DIRSYNC |
+			    SB_SILENT |
+			    SB_POSIXACL |
+			    SB_LAZYTIME |
+			    SB_I_VERSION);
+
+	return do_new_mount_2(path, type_page, sb_flags, mnt_flags, NULL,
+			     file, data_page);
+}
+
 int do_mount(const char *dev_name, const char __user *dir_name,
 		const char *type_page, unsigned long flags, void *data_page)
 {
diff --git a/include/linux/fs_context.h b/include/linux/fs_context.h
index 0d6c8a6d7be2..b35d7583f0bd 100644
--- a/include/linux/fs_context.h
+++ b/include/linux/fs_context.h
@@ -16,6 +16,7 @@
 
 struct cred;
 struct dentry;
+struct file;
 struct file_operations;
 struct file_system_type;
 struct mnt_namespace;
@@ -99,6 +100,7 @@ struct fs_context {
 	const struct cred	*cred;		/* The mounter's credentials */
 	struct p_log		log;		/* Logging buffer */
 	const char		*source;	/* The source name (eg. dev path) */
+	struct file		*source_file;	/* Already-open source file, in-kernel only */
 	void			*security;	/* LSM options */
 	void			*s_fs_info;	/* Proposed s_fs_info */
 	unsigned int		sb_flags;	/* Proposed superblock flags (SB_*) */
@@ -148,6 +150,8 @@ extern int vfs_get_tree(struct fs_context *fc);
 extern void put_fs_context(struct fs_context *fc);
 extern int vfs_parse_fs_param_source(struct fs_context *fc,
 				     struct fs_parameter *param);
+int vfs_parse_fs_param_file(struct fs_context *fc, const char *key,
+			    struct file *file);
 extern void fc_drop_locked(struct fs_context *fc);
 
 extern int get_tree_nodev(struct fs_context *fc,
diff --git a/include/linux/init_syscalls.h b/include/linux/init_syscalls.h
index 28776ee28d8e..ac937cdb9d46 100644
--- a/include/linux/init_syscalls.h
+++ b/include/linux/init_syscalls.h
@@ -2,6 +2,8 @@
 
 int __init init_mount(const char *dev_name, const char *dir_name,
 		const char *type_page, unsigned long flags, void *data_page);
+int __init init_mount_file(struct file *file, const char *dir_name,
+		const char *type_page, unsigned long flags, void *data_page);
 int __init init_umount(const char *name, int flags);
 int __init init_chdir(const char *filename);
 int __init init_chroot(const char *filename);
-- 
2.43.0



  parent reply	other threads:[~2026-07-27 10:48 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-18 19:15 [RFC PATCH 0/2] init: boot image-based systems without an initramfs (rootimage=) Eric Curtin
2026-07-18 19:15 ` [RFC PATCH 1/2] init: support mounting the root filesystem from an image file Eric Curtin
2026-07-18 19:15 ` [RFC PATCH 2/2] init: support pinning the root image's fsverity digest Eric Curtin
2026-07-27  8:55 ` [RFC PATCH 0/2] init: boot image-based systems without an initramfs (rootimage=) Christian Brauner
2026-07-27 10:31   ` Eric Curtin
2026-07-27 10:48   ` [RFC PATCH v2 0/4] " Eric Curtin
2026-07-27 10:48   ` Eric Curtin [this message]
2026-07-27 10:48   ` [RFC PATCH v2 2/4] erofs: use fs_context source_file for file-backed mounts when given Eric Curtin
2026-07-27 10:48   ` [RFC PATCH v2 3/4] init: support mounting the root filesystem from an image file Eric Curtin
2026-07-27 10:48   ` [RFC PATCH v2 4/4] init: support pinning the root image's fsverity digest Eric Curtin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260727104845.2607444-2-ericcurtin17@gmail.com \
    --to=ericcurtin17@gmail.com \
    --cc=brauner@kernel.org \
    --cc=chao@kernel.org \
    --cc=corbet@lwn.net \
    --cc=ebiggers@kernel.org \
    --cc=fsverity@lists.linux.dev \
    --cc=jack@suse.cz \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-erofs@lists.ozlabs.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=skhan@linuxfoundation.org \
    --cc=tytso@mit.edu \
    --cc=viro@zeniv.linux.org.uk \
    --cc=xiang@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.