public inbox for linux-fsdevel@vger.kernel.org
 help / color / mirror / Atom feed
From: "Darrick J. Wong" <djwong@kernel.org>
To: bschubert@ddn.com
Cc: miklos@szeredi.hu, neal@gompa.dev, linux-fsdevel@vger.kernel.org,
	bernd@bsbernd.com, joannelkoong@gmail.com
Subject: Re: [PATCH 08/13] mount_service: enable unprivileged users in the same manner as fusermount
Date: Tue, 14 Apr 2026 16:53:49 -0700	[thread overview]
Message-ID: <20260414235349.GE604658@frogsfrogsfrogs> (raw)
In-Reply-To: <177577270359.2064074.2765615359461460085.stgit@frogsfrogsfrogs>

On Thu, Apr 09, 2026 at 03:22:38PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> Some Linux distributions allow unprivileged users to mount fuse
> filesystems through the use of the setuid fusermount helper program.  It
> would be useful to provide similar functionality when mounting a
> filesystem that runs as a systemd service.
> 
> Therefore, read the fuse config file and implement the same checks as
> fusermount.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
>  util/mount_service.c |  150 +++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 147 insertions(+), 3 deletions(-)
> 
> 
> diff --git a/util/mount_service.c b/util/mount_service.c
> index 8e9c721a56fd2a..0f7eae94ada377 100644
> --- a/util/mount_service.c
> +++ b/util/mount_service.c
> @@ -33,6 +33,7 @@
>  #include "fuse_i.h"
>  #include "fuse_service_priv.h"
>  #include "mount_service.h"
> +#include "fuser_conf.h"
>  
>  struct mount_service {
>  	/* prefix for printing error messages */
> @@ -166,7 +167,9 @@ static int mount_service_connect(struct mount_service *mo)
>  		return -1;
>  	}
>  
> +	drop_privs();
>  	ret = connect(sockfd, (const struct sockaddr *)&name, sizeof(name));
> +	restore_privs();
>  	if (ret && (errno == ENOENT || errno == ECONNREFUSED)) {
>  		fprintf(stderr, "%s: no safe filesystem driver for %s available.\n",
>  			mo->msgtag, mo->subtype);
> @@ -208,7 +211,7 @@ static int mount_service_send_hello(struct mount_service *mo)
>  	};
>  	ssize_t size;
>  
> -	if (getuid() == 0)
> +	if (getuid() == 0 || user_allow_other)
>  		hello.flags |= htonl(FUSE_SERVICE_FLAG_ALLOW_OTHER);
>  
>  	size = sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> @@ -490,7 +493,9 @@ static int mount_service_send_required_files(struct mount_service *mo,
>  {
>  	int ret;
>  
> +	drop_privs();
>  	mo->fusedevfd = open(fusedev, O_RDWR | O_CLOEXEC);
> +	restore_privs();
>  	if (mo->fusedevfd < 0) {
>  		fprintf(stderr, "%s: %s: %s\n",
>  			mo->msgtag, fusedev, strerror(errno));
> @@ -628,7 +633,9 @@ static int prepare_bdev(struct mount_service *mo,
>  	if (oc->block_size) {
>  		int block_size = ntohl(oc->block_size);
>  
> +		drop_privs();
>  		ret = ioctl(fd, BLKBSZSET, &block_size);
> +		restore_privs();
>  		if (ret) {
>  			int error = errno;
>  
> @@ -675,7 +682,9 @@ static int mount_service_open_path(struct mount_service *mo,
>  		return mount_service_send_file_error(mo, EINVAL, oc->path);
>  	}
>  
> -	fd = open(oc->path, ntohl(oc->open_flags), ntohl(oc->create_mode));
> +	drop_privs();
> +	fd = open(oc->path, ntohl(oc->open_flags) | O_CLOEXEC, ntohl(oc->create_mode));
> +	restore_privs();
>  	if (fd < 0) {
>  		int error = errno;
>  
> @@ -908,6 +917,20 @@ static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
>  			*equals = 0;
>  		}
>  
> +		if (getuid() != 0 && !user_allow_other &&
> +		    (!strcmp(tok, "allow_other") ||
> +		     !strcmp(tok, "allow_root"))) {
> +			fprintf(stderr,
> +"%s: option %s only allowed if 'user_allow_other' is set in %s\n",
> +				mo->msgtag, tok, FUSE_CONF);
> +			return mount_service_send_reply(mo, EPERM);
> +		}
> +		if (!strcmp(tok, "blkdev") && getuid() != 0) {

blkdev is an internal mount option that libfuse never sends to the
kernel, so there's no need to check it here either.  Sending "blkdev" to
the kernel will just cause mount() EINVAL failures.


> +			fprintf(stderr, "%s: option blkdev is privileged\n",
> +				mo->msgtag);
> +			return mount_service_send_reply(mo, EPERM);
> +		}
> +
>  #ifdef HAVE_NEW_MOUNT_API
>  		if (mo->fsopenfd >= 0) {
>  			int ret;
> @@ -985,10 +1008,16 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
>  	int error;
>  	int ret;
>  
> +	drop_privs();
> +
>  	/*
>  	 * Open the alleged mountpoint, make sure it's a dir or a file.
> +	 * For unprivileged callers, we only allow mounting on paths that the
> +	 * user can write to.
>  	 */
> -	mountfd = open(mntpt, O_RDONLY | O_CLOEXEC);
> +	mountfd = open(mntpt, (getuid() == 0 ? O_RDONLY : O_WRONLY) | O_CLOEXEC);
> +	if (mountfd < 0 && errno == EISDIR)
> +		mountfd = open(mntpt, O_RDONLY | O_CLOEXEC);

This mess!

This might add a new requirement that the unprivileged caller has to
have read access to the mountpoint.  This isn't explicitly checked in
fusermount, but I'm pretty sure that the lstat in fusermount will fail
with EACCESS.

That said, if we're unprivileged and fall back to opening readonly, then
we should require that the fd point to a directory.  Note that the
access check just prior to mount()/fsmount() will confirm that the
unprivileged caller could (at least in theory) create a new child entry.

--D

>  	if (mountfd < 0) {
>  		error = errno;
>  		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> @@ -1067,6 +1096,7 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
>  	mo->mountfd = mountfd;
>  	mo->resv_mountpoint = res_mntpt;
>  
> +	restore_privs();
>  	return mount_service_send_reply(mo, 0);
>  
>  out_res_mntpt:
> @@ -1075,6 +1105,7 @@ static int attach_to_mountpoint(struct mount_service *mo, char *mntpt)
>  	close(mountfd);
>  out_error:
>  	free(mntpt);
> +	restore_privs();
>  	return mount_service_send_reply(mo, error);
>  }
>  
> @@ -1399,6 +1430,77 @@ static int mount_service_fsopen_mount(struct mount_service *mo,
>  # define mount_service_fsopen_mount(...)	(FUSE_MOUNT_FALLBACK_NEEDED)
>  #endif
>  
> +static int check_nonroot_file_access(struct mount_service *mo)
> +{
> +	struct stat sb1, sb2;
> +	int fd;
> +	int ret;
> +
> +	/*
> +	 * If we already succeeded in opening the file with write access, then
> +	 * we're good.
> +	 */
> +	ret = fcntl(mo->mountfd, F_GETFL);
> +	if (ret < 0) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mo->mountpoint,
> +			strerror(error));
> +		return -1;
> +	}
> +
> +	if ((ret & O_ACCMODE) != O_RDONLY)
> +		return 0;
> +
> +	ret = fstat(mo->mountfd, &sb1);
> +	if (ret) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, mo->mountpoint, strerror(error));
> +		return -1;
> +	}
> +
> +	/* Try to reopen the file with write access this time. */
> +	fd = open(mo->real_mountpoint, O_WRONLY | O_CLOEXEC);
> +	if (fd < 0) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, mo->mountpoint, strerror(error));
> +		return -1;
> +	}
> +
> +	/* Is this the same file? */
> +	ret = fstat(fd, &sb2);
> +	if (ret) {
> +		int error = errno;
> +
> +		fprintf(stderr, "%s: %s: %s\n",
> +			mo->msgtag, mo->mountpoint, strerror(error));
> +		goto out_fd;
> +	}
> +
> +	if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
> +		fprintf(stderr, "%s: %s: Mount point moved during fuse startup.\n",
> +			mo->msgtag, mo->mountpoint);
> +		ret = -1;
> +		goto out_fd;
> +	}
> +
> +	/*
> +	 * We reopened the same file with write access, everything is ok.  Swap
> +	 * the two file descriptors so that we retain our write access.
> +	 */
> +	ret = mo->mountfd;
> +	mo->mountfd = fd;
> +	fd = ret;
> +	ret = 0;
> +out_fd:
> +	close(fd);
> +	return ret;
> +}
> +
>  static int mount_service_handle_mount_cmd(struct mount_service *mo,
>  					  struct fuse_service_packet *p,
>  					  size_t psz)
> @@ -1471,6 +1573,44 @@ static int mount_service_handle_mount_cmd(struct mount_service *mo,
>  		return mount_service_send_reply(mo, EINVAL);
>  	}
>  
> +	/*
> +	 * fuse.conf can limit the number of unprivileged fuse mounts.
> +	 * For unprivileged mounts (via setuid) we also require write access
> +	 * to the mountpoint, and we'll only accept certain underlying
> +	 * filesystems.
> +	 */
> +	if (getuid() != 0) {
> +		struct statfs fs_buf;
> +
> +		ret = check_nonroot_mount_count(mo->msgtag);
> +		if (ret)
> +			return mount_service_send_reply(mo, EUSERS);
> +
> +		ret = fstatfs(mo->mountfd, &fs_buf);
> +		if (ret) {
> +			int error = errno;
> +
> +			fprintf(stderr, "%s: %s: %s\n",
> +				mo->msgtag, mo->mountpoint, strerror(error));
> +			return mount_service_send_reply(mo, error);
> +		}
> +
> +		drop_privs();
> +		if (S_ISDIR(stbuf.st_mode))
> +			ret = check_nonroot_dir_access(mo->msgtag,
> +						       mo->mountpoint,
> +						       mo->real_mountpoint,
> +						       &stbuf);
> +		else
> +			ret = check_nonroot_file_access(mo);
> +		if (!ret)
> +			ret = check_nonroot_fstype(mo->msgtag, &fs_buf);
> +		restore_privs();
> +
> +		if (ret)
> +			return mount_service_send_reply(mo, EPERM);
> +	}
> +
>  	if (mo->fsopenfd >= 0) {
>  		ret = mount_service_fsopen_mount(mo, oc, &stbuf);
>  		if (ret != FUSE_MOUNT_FALLBACK_NEEDED)
> @@ -1601,6 +1741,10 @@ int mount_service_main(int argc, char *argv[])
>  	else
>  		mo.msgtag = "mount.service";
>  
> +	drop_privs();
> +	read_conf(mo.msgtag);
> +	restore_privs();
> +
>  	ret = mount_service_init(&mo, argc, argv);
>  	if (ret)
>  		return EXIT_FAILURE;
> 
> 

  reply	other threads:[~2026-04-14 23:53 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-09 22:20 [PATCHSET v4] libfuse: run fuse servers as a contained service Darrick J. Wong
2026-04-09 22:20 ` [PATCH 01/13] Refactor mount code / move common functions to mount_util.c Darrick J. Wong
2026-04-09 22:21 ` [PATCH 02/13] mount_service: add systemd/inetd socket service mounting helper Darrick J. Wong
2026-04-14  1:00   ` Darrick J. Wong
2026-04-14 23:48   ` Darrick J. Wong
2026-04-17 23:19   ` Darrick J. Wong
2026-04-09 22:21 ` [PATCH 03/13] mount_service: create high level fuse helpers Darrick J. Wong
2026-04-14 23:58   ` Darrick J. Wong
2026-04-09 22:21 ` [PATCH 04/13] mount_service: use the new mount api for the mount service Darrick J. Wong
2026-04-17 22:03   ` Darrick J. Wong
2026-04-09 22:21 ` [PATCH 05/13] mount_service: update mtab after a successful mount Darrick J. Wong
2026-04-09 22:22 ` [PATCH 06/13] util: hoist the fuse.conf parsing and setuid mode enforcement code Darrick J. Wong
2026-04-09 22:22 ` [PATCH 07/13] util: fix checkpatch complaints in fuser_conf.[ch] Darrick J. Wong
2026-04-09 22:22 ` [PATCH 08/13] mount_service: enable unprivileged users in the same manner as fusermount Darrick J. Wong
2026-04-14 23:53   ` Darrick J. Wong [this message]
2026-04-17 22:01     ` Darrick J. Wong
2026-04-09 22:22 ` [PATCH 09/13] mount.fuse3: integrate systemd service startup Darrick J. Wong
2026-04-17 22:41   ` Darrick J. Wong
2026-04-09 22:23 ` [PATCH 10/13] mount_service: allow installation as a setuid program Darrick J. Wong
2026-04-09 22:23 ` [PATCH 11/13] example/service_ll: create a sample systemd service fuse server Darrick J. Wong
2026-04-14 23:56   ` Darrick J. Wong
2026-04-17 21:56   ` Darrick J. Wong
2026-04-09 22:23 ` [PATCH 12/13] example/service: create a sample systemd service for a high-level " Darrick J. Wong
2026-04-09 22:23 ` [PATCH 13/13] nullfs: support fuse systemd service mode Darrick J. Wong

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=20260414235349.GE604658@frogsfrogsfrogs \
    --to=djwong@kernel.org \
    --cc=bernd@bsbernd.com \
    --cc=bschubert@ddn.com \
    --cc=joannelkoong@gmail.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=miklos@szeredi.hu \
    --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