Page MenuHomeFreeBSD

mount_fusefs: Implement the fusermount functionality
Needs RevisionPublic

Authored by arrowd on Mar 30 2026, 3:39 PM.
Tags
None
Referenced Files
F170769334: D56165.id175422.diff
Sun, Sep 6, 12:59 PM
Unknown Object (File)
Sat, Sep 5, 9:44 PM
Unknown Object (File)
Sat, Sep 5, 3:34 PM
Unknown Object (File)
Sat, Sep 5, 3:31 PM
Unknown Object (File)
Sat, Sep 5, 3:25 PM
Unknown Object (File)
Sat, Sep 5, 1:19 PM
Unknown Object (File)
Sat, Sep 5, 11:52 AM
Unknown Object (File)
Sat, Sep 5, 10:50 AM
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 75431
Build 72314: arc lint + arc unit

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
sbin/mount_fusefs/mount_fusefs.c
194

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).

262
279
293
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
93

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

/*
  * text
  */
126
139

You must check that syscalls did not failed.

158

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

159

Blank lines are not needed.

180

static const char *fs_allowlist

Also should go into the decl block.

193

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

195
197

Does mnt_fd leak?

244

Why not bool?

251

Wrong indent.

397

'{' should be on the previous line

399

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.Jul 12 2026, 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
34

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.

44

I suggest unmount -f.

62

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

63

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

tests/sys/fs/fusefs/usermount.sh
54

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

103–104
138

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

237
268

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.Jul 12 2026, 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
34

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
34

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
158

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

tests/sys/fs/fusefs/usermount.sh
54

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.

138

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
33

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

tests/sys/fs/fusefs/usermount.sh
32

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

54

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

138

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.

arrowd marked 4 inline comments as done.
  • Address comments
tests/sys/fs/fusefs/usermount.sh
32

Sorry, I failed to parse this. What do you want me to do?

tests/sys/fs/fusefs/usermount.sh
32

I want you to do exactly what you've already done, because I'm stupid and didn't read the code correctly when last I reviewed it.

208

This test doesn't need this setting, because it doesn't change any sysctls.

tests/sys/fs/fusefs/usermount.sh
208

You said

And yes, you should add allow_sysctl_side_effects too.

My understanding is: since all these tests depend on vfs.usermount being 0 during the whole test, they must be marked allow_sysctl_side_effects, so that no any other test would change this sysctl.

tests/sys/fs/fusefs/usermount.sh
208

Not quite. Tests only need to set allow_sysctl_side_effects if they change the setting. They can also depend on it having one value, yet not change it.

arrowd marked 2 inline comments as done.
  • Remove the allow_sysctl_side_effects requirement from tests that don't need it
tests/sys/fs/fusefs/usermount.sh
268

(this comment was about userunmount_negative_cases)

The test now gets skipped if vfs.usermount != 0. This means it should be safe to keep sysctl vfs.usermount=0 || true in the cleanup?

asomers requested changes to this revision.Fri, Aug 28, 7:00 PM
asomers added inline comments.
sbin/mount_fusefs/mount_fusefs.8
283

"could not" is past tense, and doesn't make sense here. Use "may not", "should not", or something like that.

sbin/mount_fusefs/mount_fusefs.c
250

Technically, this is a TOCTOU. You check the value of the sysctl here, but it might change before you mount or unmount anything later on. However, I don't think it will do damage. AFAICT the worst that will happen is that mount_fusefs -u might fail to unmount a mountpoint, if vfs.umount transitioned from 1 to 0 while the program was executing.

407–418

This is another TOCTOU. You stat the mountpoint on line 407, but don't use that information until line 418. The mountpoint may have changed in the meantime. I think the only safe way to do an operation like this would be to either do the permission check in the kernel, or implement some kind of unmountat syscall.

This TOCTOU is exploitable, too. For example:

  • mkdir /tmp/mymountpoint
  • ln -s /tmp/mymountpoint /tmp/mymountlink
  • fuse-hello_ll /tmp/mymountpoint
  • mount_fusefs -u /tmp/mymountpoint
    • statfs follows the symlink and returns information about /tmp/mymountpoint
    • after statfs returns, the user does ln -s / /tmp/mymountlink
  • The checks pass, so mount_fusefs calls restore_privs, and then proceeds to unmount / with root privileges.
588

Another TOCTOU here. check_perm works by pathname, and it follows symlinks. So an attacker could create a symlink pointing to a directory that will satisfy the checks in check_perm. Then, after check_perm succeeds, he replaces that symlink with one point to somewhere else. Then the following nmount call will mount over top of that.

Using nocover and emptydir helps, by preventing the attacker from overwriting /etc. But those aren't good enough. For example, the attacker could use this method to mount over /usr/local/etc/some_daemon/ , if that directory were previously empty, providing his own config file for some_daemon.

Disabling allow_other and allow_root helps a lot. But it would still be possible for an attacker to replace a directory that should be empty with one that returns EPERM on access. So I'm still nervous.

669

You should document -o auto_unmount here, and maybe -u too.

sys/fs/fuse/fuse_device.c
190

If dounmount fails, you should restore mnt_cred->cr_uid

sys/fs/fuse/fuse_vfsops.c
446

Should we restrict the ability to set user_id to privileged processes?

tests/sys/fs/fusefs/auto_unmount.sh
31

You can simplify this expression.

36–37

You need to sleep within the loop, not after it.

40

And simplify here too.

45–46

You need to sleep within the loop, not after it.

68–70

Trying to check the existence of $pid after killing it is unreliable. The pid might still exist even though you killed if either:

  • It hasn't been reaped yet, or
  • A new process has been spawned with that same pid.
tests/sys/fs/fusefs/usermount.sh
78–80
162

If you think it's worth the effort, you could eliminate the special MOUNT_FUSEFS_TESTING code by using tarfs in this test.

303

If it's going to change global settings, it had better be exclusive.

419

If it's going to change global settings, it had better be exclusive.

434

You can delete this line, because it's redundant with the cleanup.

This revision now requires changes to proceed.Fri, Aug 28, 7:00 PM
tests/sys/fs/fusefs/auto_unmount.sh
52

Simplify

57

Simplify

62
tests/sys/fs/fusefs/usermount.sh
52

Simplify

63–65