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,
	Keerthana KT <keerthana@labs.digiscrypt.com>
Subject: Re: [PATCH v2 12/14] fuse_service: bound argc and arg len read from the args memfd
Date: Mon, 28 Sep 2026 19:33:31 -0700	[thread overview]
Message-ID: <20260929023331.GE6253@frogsfrogsfrogs> (raw)
In-Reply-To: <20260928-mount-service-bound-open-v2-12-0f9f501d05ce@bsbernd.com>

On Mon, Sep 28, 2026 at 01:02:14PM +0200, Bernd Schubert via B4 Relay wrote:
> From: Keerthana KT <keerthana@labs.digiscrypt.com>
> 
> fuse_service_append_args() takes the argument count and each argument
> length straight from the args memfd, which this file already treats as
> untrusted (see the SO_PASSRIGHTS guard against a malicious mount
> helper). Both fields are uint32_t and feed allocation math with no
> bound: calloc(memfd_args.argc + existing_args->argc, ...) wraps in
> unsigned arithmetic and undersizes the argv array, while
> calloc(1, memfd_arg.len + 1) wraps to a zero-size buffer when len is
> UINT32_MAX, which the following pread() then overflows.
> 
> Nothing bounded the memfd itself, so cap it on both sides with a new
> FUSE_SERVICE_MAX_ARGV_SIZE. The mount helper refuses to write a string
> that would push the file past the cap, and the server fstat()s the file
> and refuses to parse one larger than it. The file size then bounds the
> rest: argc cannot exceed the number of iovecs that fit between the
> header and the strings, and no string can be longer than the file
> holding it. An argc of zero is rejected as well, because only the first
> loop iteration assigns argv[0].
> 
> Signed-off-by: Keerthana KT <keerthana@labs.digiscrypt.com>
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
>  include/fuse_service_priv.h | 10 ++++++++++
>  lib/fuse_service.c          | 43 +++++++++++++++++++++++++++++++++++++++++++
>  util/mount_service.c        | 10 ++++++++++
>  3 files changed, 63 insertions(+)
> 
> diff --git a/include/fuse_service_priv.h b/include/fuse_service_priv.h
> index 988f7c9251c8..5b1edce4b7a8 100644
> --- a/include/fuse_service_priv.h
> +++ b/include/fuse_service_priv.h
> @@ -23,6 +23,16 @@ struct fuse_service_memfd_argv {
>  
>  #define FUSE_SERVICE_MAX_CMD_SIZE	(65536)
>  
> +/*
> + * Upper bound on the whole argv memfd, as opposed to FUSE_SERVICE_MAX_CMD_SIZE
> + * which bounds one socket command.  Both sides check it: the mount helper
> + * refuses to write past it, and the fuse server refuses to parse a file larger
> + * than it.  Generous next to any real mount(8) invocation, but small enough
> + * that the counts and lengths the server reads out of the file cannot overflow
> + * the allocation math they feed.
> + */
> +#define FUSE_SERVICE_MAX_ARGV_SIZE	(1048576)

sysconf(_SC_ARG_MAX) ?

> +
>  #define FUSE_SERVICE_ARGS_MAGIC		0x41524753	/* ARGS */
>  
>  /* mount.service sends a hello to the server and it replies */
> diff --git a/lib/fuse_service.c b/lib/fuse_service.c
> index 0a05b3fbc1f2..3991f92f09cb 100644
> --- a/lib/fuse_service.c
> +++ b/lib/fuse_service.c
> @@ -629,8 +629,10 @@ int fuse_service_append_args(struct fuse_service *sf,
>  	struct fuse_args new_args = {
>  		.allocated = 1,
>  	};
> +	struct stat statbuf;
>  	char *str = NULL;
>  	off_t memfd_pos = 0;
> +	off_t max_argc;
>  	ssize_t received;
>  	unsigned int i;
>  	int ret;
> @@ -656,6 +658,34 @@ int fuse_service_append_args(struct fuse_service *sf,
>  	memfd_args.argc = htonl(memfd_args.argc);
>  	memfd_pos += sizeof(memfd_args);
>  
> +	ret = fstat(sf->argvfd, &statbuf);
> +	if (ret) {
> +		int error = errno;
> +
> +		fuse_log(FUSE_LOG_ERR, "fuse: service args file stat: %s\n",
> +			 strerror(error));
> +		return -error;
> +	}
> +	if (statbuf.st_size > FUSE_SERVICE_MAX_ARGV_SIZE) {
> +		fuse_log(FUSE_LOG_ERR, "fuse: service args file too large\n");
> +		return -EBADMSG;
> +	}
> +
> +	/*
> +	 * The array of argv iovecs sits between the header and the strings, so
> +	 * the file size bounds argc.  Reject a count the file cannot hold: the
> +	 * sum below is computed in unsigned arithmetic and would otherwise wrap
> +	 * and undersize the array.  argc 0 is rejected as well, because only
> +	 * the first loop iteration fills argv[0].
> +	 */
> +	max_argc = (statbuf.st_size - (off_t)sizeof(memfd_args)) /
> +		   (off_t)sizeof(struct fuse_service_memfd_arg);
> +	if (memfd_args.argc == 0 || memfd_args.argc > max_argc) {
> +		fuse_log(FUSE_LOG_ERR, "fuse: service args file argc %u invalid\n",
> +			 memfd_args.argc);
> +		return -EBADMSG;
> +	}
> +
>  	/* Allocate a new array of argv string pointers */
>  	new_args.argv = calloc(memfd_args.argc + existing_args->argc,
>  			       sizeof(char *));
> @@ -722,6 +752,19 @@ int fuse_service_append_args(struct fuse_service *sf,
>  		memfd_arg.len = htonl(memfd_arg.len);
>  		memfd_pos += sizeof(memfd_arg);
>  
> +		/*
> +		 * A string cannot be longer than the file holding it.  len
> +		 * UINT32_MAX would make len + 1 wrap to zero below, handing
> +		 * calloc() a zero-size buffer for the pread() to overflow.
> +		 */
> +		if (memfd_arg.len >= statbuf.st_size) {

You ought to check that (memfd_arg.pos + memfd_arg.len) doesn't exceed
the file size, since this won't catch a correctly sized file with a
garbage memfd_arg array.

--D

> +			fuse_log(FUSE_LOG_ERR,
> +				 "fuse: service args file argv[%u] len %u too large\n",
> +				 i, memfd_arg.len);
> +			ret = -EBADMSG;
> +			goto out_new_args;
> +		}
> +
>  		/* read arg string from file */
>  		str = calloc(1, memfd_arg.len + 1);
>  		if (!str) {
> diff --git a/util/mount_service.c b/util/mount_service.c
> index 84e9d831ce03..c715729b2162 100644
> --- a/util/mount_service.c
> +++ b/util/mount_service.c
> @@ -442,6 +442,16 @@ static int mount_service_capture_arg(const struct mount_service *mo,
>  	};
>  	ssize_t written;
>  
> +	/*
> +	 * string_pos already covers the header and the whole array, so this
> +	 * bounds the entire memfd.  The server rejects anything larger.
> +	 */
> +	if (*string_pos + (off_t)string_len > FUSE_SERVICE_MAX_ARGV_SIZE) {
> +		fprintf(stderr, "%s: memfd argv[%u] exceeds %d byte limit\n",
> +			mo->msgtag, args->argc, FUSE_SERVICE_MAX_ARGV_SIZE);
> +		return -1;
> +	}
> +
>  	written = pwrite(mo->argvfd, string, string_len, *string_pos);
>  	if (written < 0) {
>  		fprintf(stderr, "%s: memfd argv write: %s\n",
> 
> -- 
> 2.53.0
> 
> 
> 

  reply	other threads:[~2026-09-29  2:33 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
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 [this message]
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=20260929023331.GE6253@frogsfrogsfrogs \
    --to=djwong@kernel.org \
    --cc=bernd@bsbernd.com \
    --cc=fuse-devel@lists.linux.dev \
    --cc=keerthana@labs.digiscrypt.com \
    --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