FILESYSTEM IN USERSPACE (FUSE) development
 help / color / mirror / Atom feed
From: "Darrick J. Wong" <djwong@kernel.org>
To: bernd@bsbernd.com
Cc: fuse-devel@lists.linux.dev, neal@gompa.dev
Subject: Re: [PATCH v2 02/14] mount_service: warn about paths not named on the command line
Date: Mon, 28 Sep 2026 19:10:27 -0700	[thread overview]
Message-ID: <20260929021027.GB6253@frogsfrogsfrogs> (raw)
In-Reply-To: <20260928-mount-service-bound-open-v2-2-0f9f501d05ce@bsbernd.com>

On Mon, Sep 28, 2026 at 01:02:04PM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
> 
> In a service mount, the fuse server runs as a systemd service in a
> sandbox that has no access to the user's files. The user runs mount,
> which starts fuservicemount3, a setuid-root helper. The fuse server
> sends requests to the helper over a socket. With an OPEN request, the
> server asks the helper to open its backing file, for example the disk
> image named on the mount command line. The helper opens the file with
> the user's credentials and passes the file descriptor to the server.
> fusermount3 opens nothing for the fuse server except /dev/fuse; the
> server runs as the user and opens its own files.
> 
> The helper opens any path the server sends. An attacker who controlled
> the server could use this to read every file the user can read, for
> example ~/.ssh/id_ed25519, and the sandbox does not prevent it. The
> helper now prints a warning if the user did not name the path when
> mounting: as a whole argument, as the value of a name=value option, or
> glued to a short option as in "-J/dev/sdb1". The helper splits the
> options with fuse_opt_parse(), as the fuse server does, so both see the
> same option values. The helper still opens the path, because a server
> can take a path in a form that none of these checks recognizes.
> 
> Enforced permissions follow up in the next commit.
> 
> Assisted-by: LLM
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>

I wonder if there are any fuse servers out there that take parameters
like:

-o bdevs=/dev/sda:/dev/sdb,otheroption=whatever

but ... let's let them come out of the woodwork?

I think this is a good addition :)
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

--D

> ---
>  util/mount_service.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++-----
>  1 file changed, 66 insertions(+), 7 deletions(-)
> 
> diff --git a/util/mount_service.c b/util/mount_service.c
> index 7549e0b5024f..446f37f61916 100644
> --- a/util/mount_service.c
> +++ b/util/mount_service.c
> @@ -770,9 +770,55 @@ static bool arg_in_cmdline(int argc, const char * const argv[],
>  	return false;
>  }
>  
> +struct option_value_match {
> +	const char *path;
> +	bool found;
> +};
> +
> +/* fuse_opt_parse() callback: set match->found if the path is this option's value */
> +static int match_option_value(void *data, const char *arg, int key,
> +			      struct fuse_args *outargs)
> +{
> +	struct option_value_match *match = data;
> +	const char *value = strchr(arg, '=');
> +
> +	(void) outargs;
> +
> +	if (key != FUSE_OPT_KEY_OPT)
> +		return 0;
> +
> +	if (value && !strcmp(value + 1, match->path))
> +		match->found = true;
> +
> +	/* Short option with its value glued on, as in "-J/dev/sdb1" */
> +	if (arg[0] == '-' && arg[1] && arg[1] != '-' &&
> +	    !strcmp(arg + 2, match->path))
> +		match->found = true;
> +
> +	return 0;
> +}
> +
> +/* @return true for the path in "-o name=path", "--name=path" or "-Xpath" */
> +static bool option_value_in_cmdline(int argc, const char * const argv[],
> +				    const char *path)
> +{
> +	struct option_value_match match = {
> +		.path = path,
> +	};
> +	struct fuse_args args = FUSE_ARGS_INIT(argc, (char **)argv);
> +	int ret;
> +
> +	/* Parse like the fuse server does, so both see the same values */
> +	ret = fuse_opt_parse(&args, &match, NULL, match_option_value);
> +	fuse_opt_free_args(&args);
> +
> +	return !ret && match.found;
> +}
> +
>  static int mount_service_open_path(const struct mount_service *mo,
>  				   mode_t expected_fmt,
> -				   struct fuse_service_packet *p, size_t psz)
> +				   struct fuse_service_packet *p, size_t psz,
> +				   int argc, const char * const argv[])
>  {
>  	const struct fuse_service_open_command *oc =
>  			container_of(p, struct fuse_service_open_command, p);
> @@ -800,6 +846,15 @@ static int mount_service_open_path(const struct mount_service *mo,
>  		return mount_service_send_file_error(mo, EINVAL, oc->path);
>  	}
>  
> +	/*
> +	 * The file is opened outside the service sandbox, so report a path
> +	 * the user did not name.
> +	 */
> +	if (!arg_in_cmdline(argc, argv, oc->path) &&
> +	    !option_value_in_cmdline(argc, argv, oc->path))
> +		fprintf(stderr, "%s: %s: warning: file not in command line arguments\n",
> +			mo->msgtag, oc->path);
> +
>  	open_flags = ntohl(oc->open_flags) | O_CLOEXEC;
>  	drop_privs();
>  	fd = open(oc->path, open_flags, ntohl(oc->create_mode));
> @@ -834,16 +889,18 @@ static int mount_service_open_path(const struct mount_service *mo,
>  
>  static int mount_service_handle_open_cmd(const struct mount_service *mo,
>  					 struct fuse_service_packet *p,
> -					 size_t psz)
> +					 size_t psz, int argc,
> +					 const char * const argv[])
>  {
> -	return mount_service_open_path(mo, 0, p, psz);
> +	return mount_service_open_path(mo, 0, p, psz, argc, argv);
>  }
>  
>  static int mount_service_handle_open_bdev_cmd(const struct mount_service *mo,
>  					      struct fuse_service_packet *p,
> -					      size_t psz)
> +					      size_t psz, int argc,
> +					      const char * const argv[])
>  {
> -	return mount_service_open_path(mo, S_IFBLK, p, psz);
> +	return mount_service_open_path(mo, S_IFBLK, p, psz, argc, argv);
>  }
>  
>  #ifdef HAVE_NEW_MOUNT_API
> @@ -1838,10 +1895,12 @@ int mount_service_main(int argc, char *argv[])
>  
>  		switch (ntohl(p->magic)) {
>  		case FUSE_SERVICE_OPEN_CMD:
> -			ret = mount_service_handle_open_cmd(&mo, p, sz);
> +			ret = mount_service_handle_open_cmd(&mo, p, sz,
> +					argc, (const char * const *)argv);
>  			break;
>  		case FUSE_SERVICE_OPEN_BDEV_CMD:
> -			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz);
> +			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz,
> +					argc, (const char * const *)argv);
>  			break;
>  		case FUSE_SERVICE_FSOPEN_CMD:
>  			ret = mount_service_handle_fsopen_cmd(&mo, p, sz);
> 
> -- 
> 2.53.0
> 
> 
> 

  reply	other threads:[~2026-09-29  2:10 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-28 11:02 [PATCH v2 00/14] libfuse: Add mount service safety checks and tests Bernd Schubert via B4 Relay
2026-09-28 11:02 ` [PATCH v2 01/14] mount_service: move the command line check into arg_in_cmdline() Bernd Schubert via B4 Relay
2026-09-28 11:02 ` [PATCH v2 02/14] mount_service: warn about paths not named on the command line Bernd Schubert via B4 Relay
2026-09-29  2:10   ` Darrick J. Wong [this message]
2026-09-30 11:06     ` Bernd Schubert
2026-09-28 11:02 ` [PATCH v2 03/14] mount_service: refuse paths the user did not name Bernd Schubert via B4 Relay
2026-09-29  2:26   ` Darrick J. Wong
2026-09-30 11:46     ` Bernd Schubert
2026-09-28 11:02 ` [PATCH v2 04/14] mount_service: use openat to OPEN paths Bernd Schubert via B4 Relay
2026-09-29  2:27   ` Darrick J. Wong
2026-09-28 11:02 ` [PATCH v2 05/14] util: give fuservicemount3 an absolute build-tree runpath Bernd Schubert via B4 Relay
2026-09-28 11:02 ` [PATCH v2 06/14] mount.fuse: free the options on the service mount return path Bernd Schubert via B4 Relay
2026-09-28 11:02 ` [PATCH v2 07/14] example/single_file: take no sector size from a regular backing file Bernd Schubert via B4 Relay
2026-09-28 11:02 ` [PATCH v2 08/14] test: check which files fuservicemount3 opens for the server Bernd Schubert via B4 Relay
2026-09-29  3:52   ` Darrick J. Wong
2026-09-28 11:02 ` [PATCH v2 09/14] test: check what fuservicemount3 refuses Bernd Schubert via B4 Relay
2026-09-29  3:55   ` Darrick J. Wong
2026-09-28 11:02 ` [PATCH v2 10/14] test: mount the service examples through fuservicemount3 Bernd Schubert via B4 Relay
2026-09-28 11:02 ` [PATCH v2 11/14] test: run mkfs.ext4 through the service examples Bernd Schubert via B4 Relay
2026-09-28 11:02 ` [PATCH v2 12/14] fuse_service: bound argc and arg len read from the args memfd Bernd Schubert via B4 Relay
2026-09-29  2:33   ` Darrick J. Wong
2026-09-28 11:02 ` [PATCH v2 13/14] build: move the default service socket directory to /run/fuse Bernd Schubert via B4 Relay
2026-09-29  2:34   ` Darrick J. Wong
2026-09-28 11:02 ` [PATCH v2 14/14] Improve documentation for fuse service mount Bernd Schubert via B4 Relay
2026-09-29  2:43   ` Darrick J. Wong
2026-09-30 13:09     ` Bernd Schubert

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=20260929021027.GB6253@frogsfrogsfrogs \
    --to=djwong@kernel.org \
    --cc=bernd@bsbernd.com \
    --cc=fuse-devel@lists.linux.dev \
    --cc=neal@gompa.dev \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox