* Update meson.build to add mount_util.c to libfuse_sources
unconditionally, it's non Linux-only
* FreeBSD, like NetBSD, doesn't have mntent.h, so don't include
that and define IGNORE_MTAB for both
* FreeBSD, like NetBSD, has no umount2() sysctl, so similarly define
it to unmount()
Kevin Vigor [Tue, 16 Oct 2018 23:23:07 +0000 (17:23 -0600)]
Avoid double unmount on normal unmount in auto_unmount mode.
If a fuse filesystem was mounted in auto_unmount mode on top of an
already mounted filesystem, we would end up doing a double-unmount
when the fuse filesystem was unmounted properly.
Make the auto_unmount code less eager: unmount only if the mounted
filesystem has proper type and is returning 'Transport endpoint not
connected'.
Miklos Szeredi [Wed, 15 Aug 2018 08:36:31 +0000 (10:36 +0200)]
passthrough_ll: initialize unused memory
For '.' and '..' entries only the file type in e.attr.st_mode and the inode
number in e.attr.st_ino are used. But it's prudent to at least initialize
the other fields of struct fuse_entry_param as well, instead of using
random values from the stack.
Miklos Szeredi [Tue, 14 Aug 2018 19:37:02 +0000 (21:37 +0200)]
passthrough_ll: allow configuring caching
Caching can be controlled with the following options:
"cache=never": disable caching
"cache=normal": enable caching but also refresh after the timeout
"cache=always": never refresh cache
The timeout can be controlled with the "timeout=SEC" option, where SEC is
the number of seconds and can be an arbitrary non-negative floating point
number.
Miklos Szeredi [Tue, 14 Aug 2018 19:37:02 +0000 (21:37 +0200)]
passthrough_ll: add *xattr() operations
The extended attribute functionality is enabled with the "xattr" option
(default) and disabled with the "no_xatt" option.
New operations added:
- getxattr
- listxattr
- setxattr
- removexattr
Caveat: none of these operations will work on a symbolic link, because it's
difficult to implement that without races that can result in incorrect
operation.
Miklos Szeredi [Tue, 14 Aug 2018 19:37:02 +0000 (21:37 +0200)]
passthrough_ll: add flock()
Conditionally enable flock() locking on underlying filesystem, based on the
flock/no_flock options. Default is "no_flock", meaning locking will be
local to the fuse filesystem and won't be propagated to the filesystem
passed through.
Vivek Goyal [Tue, 14 Aug 2018 19:37:02 +0000 (21:37 +0200)]
passthrough_ll: add source option
Right now, passthrough_ll will use "/" as source directory for passthrough.
We need more flexibility where user can specify path of directory to be
passed through. Hence add an option "source=<source-dir>".
Mattias Nissler [Fri, 31 Aug 2018 07:44:04 +0000 (09:44 +0200)]
Add unprivileged option in `mount.fuse3`
The unprivileged option allows to run the FUSE file system process
without privileges by dropping capabilities and preventing them from
being re-acquired via setuid / fscaps etc. To accomplish this,
mount.fuse sets up the `/dev/fuse` file descriptor and mount itself
and passes the file descriptor via the `/dev/fd/%u` mountpoint syntax
to the FUSE file system.
Mattias Nissler [Mon, 27 Aug 2018 13:17:57 +0000 (15:17 +0200)]
Allow passing `/dev/fuse` file descriptor from parent process
This adds support for a mode of operation in which a privileged parent
process opens `/dev/fuse` and takes care of mounting. The FUSE file
system daemon can then run as an unprivileged child that merely
processes requests on the FUSE file descriptor, which get passed using
the special `/dev/fd/%u` syntax for the mountpoint parameter.
The main benefit is that no privileged operations need to be performed
by the FUSE file system daemon itself directly or indirectly, so the
FUSE process can run with fully unprivileged and mechanisms like
securebits and no_new_privs can be used to prevent subprocesses from
re-acquiring privilege via setuid, fscaps, etc. This reduces risk in
case the FUSE file system gets exploited by malicious file system
data.
Below is an example that illustrates this. Note that I'm using shell
for presentation purposes, the expectation is that the parent process
will implement the equivalent of the `mount -i` and `capsh` commands.
```
\# example/hello can mount successfully with privilege
$ sudo sh -c "LD_LIBRARY_PATH=build/lib ./example/hello /mnt/tmp"
$ sudo cat /mnt/tmp/hello
Hello World!
$ sudo umount /mnt/tmp
\# example/hello fails to mount without privilege
$ sudo capsh --drop=all --secbits=0x2f -- -c 'LD_LIBRARY_PATH=build/lib ./example/hello -f /mnt/tmp'
fusermount3: mount failed: Operation not permitted
\# Passing FUSE file descriptor via /dev/fd/%u allows example/hello to work without privilege
$ sudo sh -c '
exec 17<>/dev/fuse
mount -i -o nodev,nosuid,noexec,fd=17,rootmode=40000,user_id=0,group_id=0 -t fuse hello /mnt/tmp
capsh --drop=all --secbits=0x2f -- -c "LD_LIBRARY_PATH=build/lib example/hello /dev/fd/17"
'
$ sudo cat /mnt/tmp/hello
Hello World!
$ sudo umount /mnt/tmp
```
Allow skipping utils build & installation (-Dutils=false) and examples
build (-Dexamples=false). By default behaviour is unchanged (both are
true: utils and examples get build).
Martin Blanchard [Mon, 20 Aug 2018 19:32:10 +0000 (20:32 +0100)]
Make meson build scripts subprojects friendly
Multiple meson build scripts improvements including:
* Bump meson requirement to 0.40.1 (0.40 already required)
* Declare a dependency object for main library
* Stop using add_global_arguments()
* Various minor style fixes
Fix readdir() bug when a non-zero offset is specified in filler (#269)
The bug occurs when a filesystem client reads a directory until the end,
seeks using seekdir() to some valid non-zero position and calls
readdir(). A valid 'struct dirent *' is expected, but NULL is returned
instead. Pseudocode demonstrating the bug:
DIR *dp = opendir("some_dir");
struct dirent *de = readdir(dp);
/* Get offset of the second entry */
long offset = telldir(dp);
/* Read directory until the end */
while (de)
de = readdir(de);
seekdir(dp, offset);
de = readdir(dp);
/* de must contain the second entry, but NULL is returned instead */
The reason of the bug is that when the end of directory is reached, the
kernel calls FUSE_READDIR op with an offset at the end of directory, so
the filesystem's .readdir callback never calls the filler function, and
we end up with dh->filled set to 1. After seekdir(), FUSE_READDIR is
called again with a new offset, but this time the filesystem's .readdir
callback is never called, and an empty reply is returned.
Fix by setting dh->filled to 1 only when zero offsets are given to
filler function.
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
Blacklists are notoriously fragile; especially if the kernel wishes to add
some security-critical mount option at a later date, all existing systems
with older versions of fusermount installed will suddenly have a security
problem.
Additionally, if the kernel's option parsing became a tiny bit laxer, the
blacklist could probably be bypassed.
Whitelist known-harmless flags instead, even if it's slightly more
inconvenient.
fusermount: bail out on transient config read failure
If an attacker wishes to use the default configuration instead of the
system's actual configuration, they can attempt to trigger a failure in
read_conf(). This only permits increasing mount_max if it is lower than the
default, so it's not particularly interesting. Still, this should probably
be prevented robustly; bail out if funny stuff happens when we're trying to
read the config.
Note that the classic attack trick of opening so many files that the
system-wide limit is reached won't work here - because fusermount only
drops the fsuid, not the euid, the process is running with euid=0 and
CAP_SYS_ADMIN, so it bypasses the number-of-globally-open-files check in
get_empty_filp() (unless you're inside a user namespace).
However, backslashes do not have any special meaning for the kernel here.
As it happens, you can't abuse this because there is no FUSE mount option
that takes a string value that can contain backslashes; but this is very
brittle. Don't interpret "escape characters" in places where they don't
work.
fusermount: prevent silent truncation of mount options
Currently, in the kernel, copy_mount_options() copies in one page of
userspace memory (or less if some of that memory area is not mapped).
do_mount() then writes a null byte to the last byte of the copied page.
This means that mount option strings longer than PAGE_SIZE-1 bytes get
truncated silently.
Therefore, this can happen:
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4000')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4050')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=100 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4051')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=10 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4052')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1 0 0
user@d9-ut:~$ fusermount -u mount
I'm not aware of any context in which this is actually exploitable - you'd
still need the UIDs to fit, and you can't do it if the three GIDs of the
process don't match (in the case of a typical setgid binary), but it does
look like something that should be fixed.
I also plan to try to get this fixed on the kernel side.
rename: perform user mode dir loop check when not done in kernel
Linux performs the dir loop check (rename(a, a/b/c)
or rename(a/b/c, a), etc.) in kernel. Unfortunately
other systems do not perform this check (e.g. FreeBSD).
This results in a deadlock in get_path2, because libfuse
did not expect to handle such cases.
We add a check_dir_loop function that performs the dir
loop check in user mode and enable it on systems that
need it.
Tomohiro Kusumi [Tue, 8 May 2018 05:57:00 +0000 (22:57 -0700)]
Fix compile-time warnings on IGNORE_MTAB
Silence below warnings which appear if IGNORE_MTAB is defined.
[59/64] Compiling C object 'util/fusermount3@exe/fusermount.c.o'.
../util/fusermount.c:493:12: warning: function declaration isn't a prototype [-Wstrict-prototypes]
static int count_fuse_fs()
^~~~~~~~~~~~~
../util/fusermount.c: In function 'unmount_fuse':
../util/fusermount.c:508:46: warning: unused parameter 'quiet' [-Wunused-parameter]
static int unmount_fuse(const char *mnt, int quiet, int lazy)
^~~~~
This change is bogus. fuse_module_factory_t is already a pointer
type. Additionally, if dlsym returns NULL, then you will be
dereferencing it causing a segfault. In my testing, a segfault will
happen even if dlsym returns successfully.
Adding pointer dereferencing after calling dlsym()
dlsym() resolves the location of the loaded symbol,
therefore dlsym() returns the type (fuse_module_factory_t *), not (fuse_module_factory_t).
Added pinter dereferencing to correctly refer the factory function.
According to "How to Write Shared Libraries" by Ulrich Drepper
(https://www.akkadia.org/drepper/dsohowto.pdf), the version script
should contain the exported name of the versioned symbol once in each
tag for which it has been defined by .symver.