summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Görgens <matthias.goergens@gmail.com>2023-04-12 15:39:32 +0800
committerGitHub <noreply@github.com>2023-04-12 08:39:32 +0100
commit7297044ada625da583211f0a574410cddb4f7d8d (patch)
treefa4a6fa67325614526f06fb9eae6c1d039eb64a6
parent681a0c1178fa93017a363a901d0348710582e90b (diff)
downloadfuse-7297044ada625da583211f0a574410cddb4f7d8d.tar.gz
Fuse mount: make auto_unmount compatible with suid/dev mount options (#762)
* Fuse mount: make auto_unmount compatible with suid/dev mount options > When you run as root, fuse normally does not call fusermount but uses > the mount system call directly. When you specify auto_unmount, it goes > through fusermount instead. However, fusermount is a setuid binary that > is normally called by regular users, so it cannot in general accept suid > or dev options. In this patch, we split up how fuse mounts as root when `auto_unmount` is specified. First, we mount using system calls directly, then we reach out to fusermount to set up auto_unmount only (with no actual mounting done in fusermount). Fixes: #148
-rw-r--r--example/passthrough_ll.c9
-rw-r--r--lib/mount.c73
-rwxr-xr-xtest/test_examples.py31
-rw-r--r--util/fusermount.c19
4 files changed, 120 insertions, 12 deletions
diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c
index 8b2eb4b..070cef1 100644
--- a/example/passthrough_ll.c
+++ b/example/passthrough_ll.c
@@ -89,7 +89,7 @@ struct lo_data {
int writeback;
int flock;
int xattr;
- const char *source;
+ char *source;
double timeout;
int cache;
int timeout_set;
@@ -1240,7 +1240,11 @@ int main(int argc, char *argv[])
}
} else {
- lo.source = "/";
+ lo.source = strdup("/");
+ if(!lo.source) {
+ fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+ exit(1);
+ }
}
if (!lo.timeout_set) {
switch (lo.cache) {
@@ -1302,5 +1306,6 @@ err_out1:
if (lo.root.fd >= 0)
close(lo.root.fd);
+ free(lo.source);
return ret ? 1 : 0;
}
diff --git a/lib/mount.c b/lib/mount.c
index 3990243..9c233a3 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -322,6 +322,65 @@ void fuse_kern_unmount(const char *mountpoint, int fd)
waitpid(pid, NULL, 0);
}
+static int setup_auto_unmount(const char *mountpoint, int quiet)
+{
+ int fds[2], pid;
+ int res;
+
+ if (!mountpoint) {
+ fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+ return -1;
+ }
+
+ res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+ if(res == -1) {
+ perror("fuse: socketpair() failed");
+ return -1;
+ }
+
+ pid = fork();
+ if(pid == -1) {
+ perror("fuse: fork() failed");
+ close(fds[0]);
+ close(fds[1]);
+ return -1;
+ }
+
+ if(pid == 0) {
+ char env[10];
+ const char *argv[32];
+ int a = 0;
+
+ if (quiet) {
+ int fd = open("/dev/null", O_RDONLY);
+ if (fd != -1) {
+ dup2(fd, 1);
+ dup2(fd, 2);
+ }
+ }
+
+ argv[a++] = FUSERMOUNT_PROG;
+ argv[a++] = "--auto-unmount";
+ argv[a++] = "--";
+ argv[a++] = mountpoint;
+ argv[a++] = NULL;
+
+ close(fds[1]);
+ fcntl(fds[0], F_SETFD, 0);
+ snprintf(env, sizeof(env), "%i", fds[0]);
+ setenv(FUSE_COMMFD_ENV, env, 1);
+ exec_fusermount(argv);
+ perror("fuse: failed to exec fusermount3");
+ _exit(1);
+ }
+
+ close(fds[0]);
+
+ // Now fusermount3 will only exit when fds[1] closes automatically when our
+ // process exits.
+ return 0;
+}
+
static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
const char *opts, int quiet)
{
@@ -422,12 +481,6 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
return -1;
}
- if (mo->auto_unmount) {
- /* Tell the caller to fallback to fusermount3 because
- auto-unmount does not work otherwise. */
- return -2;
- }
-
fd = open(devname, O_RDWR | O_CLOEXEC);
if (fd == -1) {
if (errno == ENODEV || errno == ENOENT)
@@ -590,7 +643,13 @@ int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
goto out;
res = fuse_mount_sys(mountpoint, mo, mnt_opts);
- if (res == -2) {
+ if (res >= 0 && mo->auto_unmount) {
+ if(0 > setup_auto_unmount(mountpoint, 0)) {
+ // Something went wrong, let's umount like in fuse_mount_sys.
+ umount2(mountpoint, MNT_DETACH); /* lazy umount */
+ res = -1;
+ }
+ } else if (res == -2) {
if (mo->fusermount_opts &&
fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
goto out;
diff --git a/test/test_examples.py b/test/test_examples.py
index a7ba998..f0aa63d 100755
--- a/test/test_examples.py
+++ b/test/test_examples.py
@@ -372,6 +372,37 @@ def test_notify_inval_entry(tmpdir, only_expire, notify, output_checker):
else:
umount(mount_process, mnt_dir)
+@pytest.mark.parametrize("intended_user", ('root', 'non_root'))
+def test_dev_auto_unmount(short_tmpdir, output_checker, intended_user):
+ """Check that root can mount with dev and auto_unmount
+ (but non-root cannot).
+ Split into root vs non-root, so that the output of pytest
+ makes clear what functionality is being tested."""
+ if os.getuid() == 0 and intended_user == 'non_root':
+ pytest.skip('needs to run as non-root')
+ if os.getuid() != 0 and intended_user == 'root':
+ pytest.skip('needs to run as root')
+ mnt_dir = str(short_tmpdir.mkdir('mnt'))
+ src_dir = str('/dev')
+ cmdline = base_cmdline + \
+ [ pjoin(basename, 'example', 'passthrough_ll'),
+ '-o', f'source={src_dir},dev,auto_unmount',
+ '-f', mnt_dir ]
+ mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd,
+ stderr=output_checker.fd)
+ try:
+ wait_for_mount(mount_process, mnt_dir)
+ if os.getuid() == 0:
+ open(pjoin(mnt_dir, 'null')).close()
+ else:
+ with pytest.raises(PermissionError):
+ open(pjoin(mnt_dir, 'null')).close()
+ except:
+ cleanup(mount_process, mnt_dir)
+ raise
+ else:
+ umount(mount_process, mnt_dir)
+
@pytest.mark.skipif(os.getuid() != 0,
reason='needs to run as root')
def test_cuse(output_checker):
diff --git a/util/fusermount.c b/util/fusermount.c
index 32d3fbd..034383e 100644
--- a/util/fusermount.c
+++ b/util/fusermount.c
@@ -1356,9 +1356,13 @@ int main(int argc, char *argv[])
int cfd;
const char *opts = "";
const char *type = NULL;
+ int setup_auto_unmount_only = 0;
static const struct option long_opts[] = {
{"unmount", no_argument, NULL, 'u'},
+ // Note: auto-unmount deliberately does not have a short version.
+ // It's meant for internal use by mount.c's setup_auto_unmount.
+ {"auto-unmount", no_argument, NULL, 'U'},
{"lazy", no_argument, NULL, 'z'},
{"quiet", no_argument, NULL, 'q'},
{"help", no_argument, NULL, 'h'},
@@ -1390,7 +1394,11 @@ int main(int argc, char *argv[])
case 'u':
unmount = 1;
break;
-
+ case 'U':
+ unmount = 1;
+ auto_unmount = 1;
+ setup_auto_unmount_only = 1;
+ break;
case 'z':
lazy = 1;
break;
@@ -1434,7 +1442,7 @@ int main(int argc, char *argv[])
exit(1);
umask(033);
- if (unmount)
+ if (!setup_auto_unmount_only && unmount)
goto do_unmount;
commfd = getenv(FUSE_COMMFD_ENV);
@@ -1444,11 +1452,15 @@ int main(int argc, char *argv[])
goto err_out;
}
+ cfd = atoi(commfd);
+
+ if (setup_auto_unmount_only)
+ goto wait_for_auto_unmount;
+
fd = mount_fuse(mnt, opts, &type);
if (fd == -1)
goto err_out;
- cfd = atoi(commfd);
res = send_fd(cfd, fd);
if (res == -1)
goto err_out;
@@ -1459,6 +1471,7 @@ int main(int argc, char *argv[])
return 0;
}
+wait_for_auto_unmount:
/* Become a daemon and wait for the parent to exit or die.
ie For the control socket to get closed.
btw We don't want to use daemon() function here because