I recently wrote a FUSE driver for Linux. My FUSE driver had to work in an environment where root access was not permitted and SUID binaries were also not permitted. Overcoming this constraint was tricky and this blog post outlines how I did it.
How does FUSE work?
FUSE on Linux requires the driver to conduct a very specific sequence in order to create and mount the filesystem. First the driver needs to open
/dev/fuse for reading and writing. Opening the device returns a file descriptor which will be used to communicate with the kernel.
Next, the driver needs to initiate a
mount(2) system call for a
fuse filesystem and provide the desired mountpoint. The driver also needs to provide an
fd mount option which is set to the file descriptor from above.
After, the above the driver can use the file descriptor to communicate with the kernel. The kernel implements a RPC protocol over the file descriptor which tells the driver which files are opened, closed, etc.
The problem with this sequence is that issuing a
mount(2) system call requires the caller to have
CAP_SYS_ADMIN which a regular user does not have. Without it, it’s not possible for a fuse driver to actually make the filesystem available.
The typical way to solve this is to implement a FUSE driver using libfuse. This library allows a user to implement each file system operation as a C callback. The user implements these functions and passes them to a libfuse main function which does the mounting described above. It overcomes the privilege issue above by being bundled with a SUID helper binary called
fusermount binary to exist and delegates the mounting operation to
fusermount. A driver using
libfuse actually allocates a local unix domain socket, passes one of them to
fusermount which then performs the
mount(2) call and proxies the I/O between
/dev/fuse and the driver. The
fusermount process runs as long as the filesystem is mounted.
This is easy to observe by using a driver that uses
libfuse. For example mounting a filesystem with
Once this command is run two processes will have been launched:
and they have established a bi-directional unix socket pair between them.
We can see that this approach will fail to work if
fusermount can not execute.
The alternative to the above is to take advantage of a feature of Linux introduced in 4.18: fuse: Allow fully unprivileged mounts. This change allows a root in a user namespace to issue
mount(2) for a FUSE filesystem.
With this any regular user can call
mount(2) if they created a new user and mount namespace. This technique even works with
libfuse which tries to use
mount(2) directly before falling back to using
fusermount. Simply creating a new user, pid and mount namespaces with
unshare with the same restrictions as before works.
With the mount in a different namespace however it’s not possible to see the contents of the mount outside of the namespace. With the above mount, another shell session would only see an empty directory at the mount point.
This is because
mount_namespaces(7) comes with two restrictions:
 Each mount namespace has an owner user namespace. As explained above, when a new mount namespace is created, its mount list is initialized as a copy of the mount list of another mount namespace. If the new namespace and the namespace from which the mount list was copied are owned by different user namespaces, then the new mount namespace is considered less privileged.
 When creating a less privileged mount namespace, shared mounts are reduced to slave mounts. This ensures that mappings performed in less privileged mount namespaces will not propagate to more privileged mount namespaces.
The combination of the two points means it’s impossible to bind mount the mount point in the mount namespace to the root namespace.
However the onwer of the mount namespace can peek into the contents of the mount from the root namespace through
procfs. If the user can get the pid of the process that called
mount(2) or any other process in the same mount namespace then running the following will show the contents of the mount.
It’s possible to mount a FUSE filesystem without use of root permissions or SUID binaries by doing the mount inside of a user namespace. This comes with the issue that viewing the contents of the mount from outside of the namespace is restricted to using