aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMattias Nissler <mnissler@chromium.org>2018-08-27 15:17:57 +0200
committerNikolaus Rath <Nikolaus@rath.org>2018-10-09 20:36:22 +0100
commit64e11073b9347fcf9c6d1eea143763ba9e946f70 (patch)
tree52c725e34474d9a1678a21d174b08f51ba5f1a49 /lib
parent4a6a5daec0384aa91ab142b5f6f4e1b3def7160c (diff)
downloadlibfuse-64e11073b9347fcf9c6d1eea143763ba9e946f70.tar.gz
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 ```
Diffstat (limited to 'lib')
-rw-r--r--lib/fuse_lowlevel.c27
-rw-r--r--lib/helper.c7
-rw-r--r--lib/mount_util.c13
-rw-r--r--lib/mount_util.h1
4 files changed, 44 insertions, 4 deletions
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index e6e3d8d..844e797 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -16,6 +16,7 @@
#include "fuse_kernel.h"
#include "fuse_opt.h"
#include "fuse_misc.h"
+#include "mount_util.h"
#include <stdio.h>
#include <stdlib.h>
@@ -2891,6 +2892,24 @@ int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
close(fd);
} while (fd >= 0 && fd <= 2);
+ /*
+ * To allow FUSE daemons to run without privileges, the caller may open
+ * /dev/fuse before launching the file system and pass on the file
+ * descriptor by specifying /dev/fd/N as the mount point. Note that the
+ * parent process takes care of performing the mount in this case.
+ */
+ fd = fuse_mnt_parse_fuse_fd(mountpoint);
+ if (fd != -1) {
+ if (fcntl(fd, F_GETFD) == -1) {
+ fprintf(stderr,
+ "fuse: Invalid file descriptor /dev/fd/%u\n",
+ fd);
+ return -1;
+ }
+ se->fd = fd;
+ return 0;
+ }
+
/* Open channel */
fd = fuse_kern_mount(mountpoint, se->mo);
if (fd == -1)
@@ -2916,9 +2935,11 @@ int fuse_session_fd(struct fuse_session *se)
void fuse_session_unmount(struct fuse_session *se)
{
- fuse_kern_unmount(se->mountpoint, se->fd);
- free(se->mountpoint);
- se->mountpoint = NULL;
+ if (se->mountpoint != NULL) {
+ fuse_kern_unmount(se->mountpoint, se->fd);
+ free(se->mountpoint);
+ se->mountpoint = NULL;
+ }
}
#ifdef linux
diff --git a/lib/helper.c b/lib/helper.c
index 07cef81..e1de362 100644
--- a/lib/helper.c
+++ b/lib/helper.c
@@ -15,6 +15,7 @@
#include "fuse_misc.h"
#include "fuse_opt.h"
#include "fuse_lowlevel.h"
+#include "mount_util.h"
#include <stdio.h>
#include <stdlib.h>
@@ -147,7 +148,11 @@ static int fuse_helper_opt_proc(void *data, const char *arg, int key,
switch (key) {
case FUSE_OPT_KEY_NONOPT:
if (!opts->mountpoint) {
- char mountpoint[PATH_MAX];
+ if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+ return fuse_opt_add_opt(&opts->mountpoint, arg);
+ }
+
+ char mountpoint[PATH_MAX] = "";
if (realpath(arg, mountpoint) == NULL) {
fprintf(stderr,
"fuse: bad mount point `%s': %s\n",
diff --git a/lib/mount_util.c b/lib/mount_util.c
index 56ed85a..95e038f 100644
--- a/lib/mount_util.c
+++ b/lib/mount_util.c
@@ -352,3 +352,16 @@ int fuse_mnt_check_fuseblk(void)
fclose(f);
return 0;
}
+
+int fuse_mnt_parse_fuse_fd(const char *mountpoint)
+{
+ int fd = -1;
+ int len = 0;
+
+ if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
+ len == strlen(mountpoint)) {
+ return fd;
+ }
+
+ return -1;
+}
diff --git a/lib/mount_util.h b/lib/mount_util.h
index 55c6c5e..0ef0fbe 100644
--- a/lib/mount_util.h
+++ b/lib/mount_util.h
@@ -15,3 +15,4 @@ int fuse_mnt_umount(const char *progname, const char *abs_mnt,
const char *rel_mnt, int lazy);
char *fuse_mnt_resolve_path(const char *progname, const char *orig);
int fuse_mnt_check_fuseblk(void);
+int fuse_mnt_parse_fuse_fd(const char *mountpoint);