Writing and using a FUSE filesystem can have some Metrodome-sized security concerns that may or may not be obvious, but deserve some mention. In this section, I'll be talking about privilege escalation, giving some notes on checking access rights, and mentioning race conditions.
The main point to make is that the filesystem itself executes with the access privileges of the process running it, not those of the process making use of the filesystem. Here's how this plays out in some typical scenarios:
allow_other
OptionThis is the normal case; the filesystem runs with the privileges of the user that ran it, and only that user can access the filesystem. FUSE doesn't open up any particular security issues in this case.
allow_other
OptionIn this case, the filesystem runs with the privileges of the user that invoked it, not the privileges of any user who happens to make use of the filesystem. It's the responsibility of the user who mounts the filesystem to ensure inappropriate access privileges aren't being granted to other users. In general, users can only hurt themselves this way, since they can only grant privileges that they themselves already have.
It should be noted that an option, user_allow_other
, must
be set in /etc/fuse.conf
to enable this option.
This is really the same as the previous two cases (depending on
whether the allow_other
option is set), but root is a
sufficiently special case that it deserves mention. In this case, any
user making use of the filesystem has root privileges on that
filesystem! If the process has access to the actual filesystem, this
could easily be used to gain pretty much unlimited access.
The next subsection will talk a little bit about checking access
rights, but the simplest way out here is to not allow root to mount the
filesystem. Due to BBFS's intended role as a simple tutorial, that's
what I do. There's a little bit of code right at the start of
main()
:
if ((getuid() == 0) || (geteuid() == 0)) {
fprintf(stderr, "Running BBFS as root opens unnacceptable security holes\n");
return 1;
}
Of course, this means that while I've got code for
bb_chown()
, it doesn't actually work since only root is
able to change the ownership of a file in the underlying filesystem.
In general, a filesystem that might be executed with the
allow_other
flag will need to take steps to ensure its
own security. fuse.h
documents that several calls
need to check that the requested access is permitted; in addition to
those, there are several others that also require access checks (such
as chown()
. It is strictly the programmer's
responsibility to make sure these cautions are followed!
The most important function for checking access rights is
fuse_get_context()
which (as the name implies) returns a
pointer to a struct fuse_context
object. The UID and GID
of the process performing the operation is in fields named
uid
and gid
.
By default, FUSE runs multi-threaded: this means (in brief) that a second request can be handled by the filesystem before an earlier request has completed; this in turn raises the possibility that different threads can be simultaneously modifying a single data structure, which will cause very difficult-to-debug bugs.
There are a couple of things that can be done about the problem:
-s
option,
it is run single-threaded. this eliminates the problem, at a cost
in performance -- frankly, given the nature and intent of many fuse
filesystems, it seems to me like the default should be
single-threaded and multi-threaded should require an option. But I
didn't write it, so it's not my call.
Note that even if you do make your filesystem single-threaded, that doesn't guard against access to the underlying data structures through some other means. Taking BBFS as an example:
Either of these facts is sufficient to completely negate any efforts made in your filesystem to guard atomicity.
I should note that the FUSE code itself is careful about locking its own code and data structures. So far as I know, dangerous race conditions won't occur outside of your code.
Next: Thanks and Other Resources