Page MenuHomeFreeBSD

mount_fusefs: Implement the fusermount functionality
Needs ReviewPublic

Authored by arrowd on Mar 30 2026, 3:39 PM.
Tags
None
Referenced Files
Unknown Object (File)
Fri, Jul 10, 4:27 PM
Unknown Object (File)
Mon, Jul 6, 6:20 AM
Unknown Object (File)
Fri, Jul 3, 5:36 AM
Unknown Object (File)
Fri, Jun 26, 1:17 AM
Unknown Object (File)
Thu, Jun 18, 1:12 PM
Unknown Object (File)
May 25 2026, 7:53 PM
Unknown Object (File)
May 22 2026, 6:17 PM
Unknown Object (File)
May 14 2026, 2:47 PM
Subscribers

Details

Reviewers
asomers
kib
Group Reviewers
Contributor Reviews (src)
secteam
Summary

The canonical application of FUSE is reimplementing some existing file system
in user space. We have NTFS and exFAT in Ports as example. These FUSE daemons
follow the same policy as the mount(8) command - the mounting is only allowed
for root, unless vfs.usermount is set 1, which poses certain security risks.

There are, however, other usages of FUSE that do not involve real file systems:

  • kio-fuse, a KDE module that allows arbitrary non-KDE applications to access remote files via protocols supported by KIO (sftp, ftp, smb, etc.).
  • AppImage, a "one app = one file" format for program's distribution. An AppImage is a tiny runtime code coupled with a squashfs blob that contains an actual application together with all its dependencies.
  • xdg-document-portal, a D-Bus service that allows sandboxed applications to access files on the host system in a controlled way.

All these examples run as an unprivileged user, yet require mounting a FUSE
file system. As a solution, the libfuse project provides the fusermount
utility, which is a SUID variant of mount(8), but constrained to mounting
fusefs only.

On FreeBSD we already have mount_fusefs(8), which gets called even when
the libfuse code runs as root. This change implements the support necessary
for mount_fusefs to act in the "fusermount" mode:

  • The program is now installed with SUID bit set.
  • If we're running in the "fusermount" mode, perform various checks on the mount point.
  • Add the "-u" flag to allow unmounting by unprivileged user.
  • The "fusermount" mode is disabled if getuid() == 0 or vfs.usermount=1.
Test Plan

All tests were conducted on the hello_ll FUSE daemon that comes from libfuse/examples.
The chosen mount point is /tmp/mnt.
A branch with changes on the libfuse side is here: https://github.com/arrowd/libfuse/tree/kernel-bsd-auto-unmount

Tests done:

  • Running as root, Ctrl+C. The FS gets unmounted as expected.
  • Running as root, umount, while running. The daemon shutdowns itself as expected.
  • Running as root, killall -KILL hello_ll. The mount point stays mounted as expected (no auto_unmount in play).
  • Running as root with -o auto_unmount, Ctrl+C. The FS gets unmounted as expected, but then the unmounting is attempted again. This can be dangerous, see comments in the code.
  • Running as root, umount, while running. The daemon shutdowns itself as expected, but also tries to unmount twice.
  • Running as root, killall -KILL hello_ll. The mount point gets unmounted as expected.
  • Running as unprivileged user, Ctrl+C. The FS gets unmounted as expected.
  • Running as unprivileged user, mount_fusefs -u, while running. The daemon shutdowns itself as expected.
  • Running as unprivileged user, killall -KILL hello_ll. The mount point stays mounted as expected (no auto_unmount in play).
  • Running as unprivileged user with -o auto_unmount, Ctrl+C. The FS gets unmounted as expected. The unmounting happens twice, but we can't unmount anything except our own mount point, so this is safe.
  • Running as unprivileged user with -o auto_unmount, mount_fusefs -u, while running. The daemon shutdowns itself as expected, but also tries to unmount twice.
  • Running as unprivileged user with -o auto_unmount, killall -KILL hello_ll. The mount point gets unmounted as expected.

After setting sysctl vfs.usermount=1:

Tests under unprivileged user behave the same.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Passed
Unit
No Test Coverage
Build Status
Buildable 71827
Build 68710: arc lint + arc unit

Event Timeline

I'm seriously afraid of introducing a security hole with this change, so I highlighted places in the code I'm not sure about.

A thorough review would be greatly appreciated.

sbin/mount_fusefs/mount_fusefs.c
165

What's the point in doing chdir() and then lstat(".") on a seemingly the same directory?

The original code is here: https://github.com/libfuse/libfuse/blob/22d0fcd4c757a86377bc258296e55e2900af2c3c/util/fusermount.c#L1114

391

Here we need to make sure to only allow the user to unmount the same FUSE file system that he mounted before.

This is achieved by passing the user_id=getuid() mount option during mounting, which is then get placed into f_owner field. During unmounting we compare this field against getuid().

This should be safe, because mounting is done with MNT_NOCOVER, so we should be able to accidentally unmount something else.

470
478

fusermount also does path sanitization, but I failed to figure out what's exactly is being checked in https://github.com/libfuse/libfuse/blob/22d0fcd4c757a86377bc258296e55e2900af2c3c/lib/mount_util.c#L277

The code is cryptic to me, so I'm hoping that we're doing a correct thing already.

sys/fs/fuse/fuse_device.c
189

This looks a bit hackish and requires reviewers attention.

sys/fs/fuse/fuse_vfsops.c
447

The answer to this question depends on

  1. whether it is possible for the caller to lie about user_id. From what I gather, it is not, because in usual circumstances mounting is only allowed for root and mount_fusefs in usermount mode does not allow caller to provide an arbitrary value.
  2. how the f_owner is used in other places.

I think that this will be a nice feature. Have you considered how to test it? I think it could all be integrated into the existing fusefs test suite. Or alternatively, the negative tests at least could use atf-sh, with a suitable fuse binary installed from ports.

sbin/mount_fusefs/mount_fusefs.8
35

Don't forget to change the date.

sbin/mount_fusefs/mount_fusefs.c
131
165

Probably as a protection against TOCTOU attacks. Since that directory is specified as a path, it could be replaced after the first lstat and before the chdir. I think a better approach would be to open it first, then use fstat and fchdir. But the first lstat in fusermount.c isn't very useful, because it just checks whether the path is a directory. chdir is perfectly capable of performing that check by itself.

192

I'm not sure that strncmp has any advantage over strcmp here, since the len is fixed at compile time. If anything, you should use the length of the f_fstypename array.

sys/fs/fuse/fuse_vfsops.c
447

I think this is ok, and desirable. I don't see user_id used in very many other places, and I don't see any problems.

As was discussed elsewhere, fuse server which times out the responses could cause lock cascades in VFS. This would have global consequences for the whole system.

Until the vnodes are locked around communication with userspace, I do not think this change is appropriate.

arrowd marked 2 inline comments as done.
  • Address some comments
sbin/mount_fusefs/mount_fusefs.c
192

FUSE mounts might have a "subtype", which results in f_fstypename being set to something like "fusefs.foo".
I'm using strncmp here to compare no more than "fusefs" part.
I can replace strlen with static string lengths, though.

Have you considered how to test it?

The general test should be running a simple FUSE daemon like hello_ll from libfuse examples and reading the file on a mounted fs. This general test should be parametrized by the following matrix:

  • vfs.usermount=0/vfs.usermount=1
  • caller is root / caller is regular user
  • auto unmount enabled/disabled
  • the daemon terminates normally/abnormally

And yes, some negative cases for mounting over other mount point, or into a mountpoint not owned by the regular user.

with a suitable fuse binary installed from ports.

Example daemons are not installed by the port, but if tests are allowed to depend on ports, we can install the libfuse package itself and put the example daemon's code in tests.

Few nits to "help" with the manpage :P

sbin/mount_fusefs/mount_fusefs.8
137

New sentence, new line (this makes a linter warning with $ mandoc -Tlint sbin/mount_fusefs/mount_fusefs.8).

261
278
292
arrowd marked 6 inline comments as done.
  • Address comments
  • Add tests for auto_unmount and usermount features

The added testing suite depends on not-yet-committed port and adjustments to filesystems/fusefs-libs3, which I plan to push forward once I get a positive feedback on this review.

One test (double unmount problem I was talking about in the diff's description) currently fails and it is libfuse that should be fixed.

sbin/mount_fusefs/mount_fusefs.c
92

Look at style(9) how the multiline comment should be formatted.

/*
  * text
  */
124
137

You must check that syscalls did not failed.

156

mnt_fd declaration should go into the corresponding block.
Do you want to set O_CLOEXEC there?

157

Blank lines are not needed.

178

static const char *fs_allowlist

Also should go into the decl block.

191

This is too fragile. I suggest to explicitly compare with "tmpfs" after the strcmp loop.

193
195

Does mnt_fd leak?

229

Why not bool?

236

Wrong indent.

382

'{' should be on the previous line

384

Blank line is needed after the block of locals declarations.

arrowd marked 12 inline comments as done.
  • Address comments
asomers requested changes to this revision.Sun, Jul 12, 4:28 PM
In D56165#1285144, @kib wrote:

As was discussed elsewhere, fuse server which times out the responses could cause lock cascades in VFS. This would have global consequences for the whole system.

Until the vnodes are locked around communication with userspace, I do not think this change is appropriate.

I think kib means "unlocked around communication with userspace". But I do not see how such a change could possibly work.

Another thing: what is the rationale for prohibiting the usermount feature from working on top of certain file system types?

tests/sys/fs/fusefs/auto_unmount.sh
33 ↗(On Diff #181765)

These two functions look at the global mount table. So they might fail if two tests are running in parallel. To fix that, you must mark the tests as exclusive. Or better yet, change these functions to they only look at a known mount point, with a command like df -T mnt.

43 ↗(On Diff #181765)

I suggest unmount -f.

61 ↗(On Diff #181765)

Using killall will also interfere with running tests in parallel. Is there anyway to kill by PID instead?

62 ↗(On Diff #181765)

Instead of hard-coding a 1-second sleep, could you put a polling loop around df -T mnt?

tests/sys/fs/fusefs/usermount.sh
53 ↗(On Diff #181765)

Instead of su -m nobody below, can you set require.user unprivileged here?

102–103 ↗(On Diff #181765)
137 ↗(On Diff #181765)

Is this part forbidden even if vfs.usermount=1 ? If not, then you should skip the test if that setting is true.

236 ↗(On Diff #181765)
267 ↗(On Diff #181765)

It would be very bad to do this in the cleanup if that setting wasn't in use system-wide before the test started.

This revision now requires changes to proceed.Sun, Jul 12, 4:28 PM
In D56165#1285144, @kib wrote:

As was discussed elsewhere, fuse server which times out the responses could cause lock cascades in VFS. This would have global consequences for the whole system.

Until the vnodes are locked around communication with userspace, I do not think this change is appropriate.

I think kib means "unlocked around communication with userspace". But I do not see how such a change could possibly work.

Fuse needs to make some way to ensure liveness of the inode (not vnode) around calls to the userspace.

Another thing: what is the rationale for prohibiting the usermount feature from working on top of certain file system types?

BTW, what prevents a malicious userspace to try to sneak a mount with a type outside the allowed list after the check, but before the mount?

tests/sys/fs/fusefs/auto_unmount.sh
33 ↗(On Diff #181765)

In the usermount.sh tests I do mount | grep nosuid to check that the FS is mounted with nosuid. It seems it isn't possible to do with df -T and this makes the whole test exclusive?

Another thing: what is the rationale for prohibiting the usermount feature from working on top of certain file system types?

I don't know, to be honest. I followed what Linux fusermount does.
I tried searching for rationale, but the only clue I got is Linux overlay mounts.

In D56165#1334631, @kib wrote:
In D56165#1285144, @kib wrote:

As was discussed elsewhere, fuse server which times out the responses could cause lock cascades in VFS. This would have global consequences for the whole system.

Until the vnodes are locked around communication with userspace, I do not think this change is appropriate.

I think kib means "unlocked around communication with userspace". But I do not see how such a change could possibly work.

Fuse needs to make some way to ensure liveness of the inode (not vnode) around calls to the userspace.

Now I'm really confused. Are you saying that you want fusefs to hold the vnode lock, but yet allow other fusefs threads to access the same inode?

tests/sys/fs/fusefs/auto_unmount.sh
33 ↗(On Diff #181765)

How about mount | grep "$MOUNTPOINT.*nosuid"?

arrowd marked 8 inline comments as done.
  • Address comments
In D56165#1334631, @kib wrote:

BTW, what prevents a malicious userspace to try to sneak a mount with a type outside the allowed list after the check, but before the mount?

Sneaking mount requires mounting or unmounting something, which means it should either be root or vfs.usermount be enabled. There is no sense to guard against root and when vfs.usermount is set to 1 the FUSE usermount feature is disabled.

The checking itself is sound, but it is still unclear why it is needed. I'd leave it in place, because it is always easier to disable some unneeded security measures than enabling them later when we bump into some security problem.

sbin/mount_fusefs/mount_fusefs.c
156

I now close this fd in the same function, so I guess O_CLOEXEC would be useless.

tests/sys/fs/fusefs/usermount.sh
53 ↗(On Diff #181765)

Turns out this doesn't work, because we need to unmount in the cleanup and with require.user unprivileged the cleanup step is also ran as unprivileged user.

137 ↗(On Diff #181765)

All these tests expect vfs.usermount=0 at their start. Should I put this assertion at the beginning of each test and also add allow_sysctl_side_effects to each test?

tests/sys/fs/fusefs/auto_unmount.sh
32 ↗(On Diff #181999)

There needs to be some kind of sleep here, and in the other loop too.

tests/sys/fs/fusefs/usermount.sh
31 ↗(On Diff #181999)

You removed the exclusivity requirement in common_cleanup . Can you do it here and in check_mounted too?

53 ↗(On Diff #181765)

Ahh, that makes sense. Being so, I suggest you use user tests instead of nobody. That's what it's for.

137 ↗(On Diff #181765)

You should definitely not assume that. You should skip at the beginning of each test if that sysctl is already 1. And yes, you should add allow_sysctl_side_effects too.