Re: [PATCH 08/13] mount_service: enable unprivileged users in the same manner as fusermount

Darrick J. Wong
2026-04-14T23:53:50Z

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;
>
>