Fix `auto_unmount` to work without `allow_other`
authorMatthias Goergens <matthias.goergens@gmail.com>
Mon, 27 Mar 2023 10:43:26 +0000 (18:43 +0800)
committerNikolaus Rath <Nikolaus@rath.org>
Tue, 28 Mar 2023 20:31:18 +0000 (21:31 +0100)
In
https://github.com/libfuse/libfuse/blob/77d662459a0fcdf358d515477d33795837e859d5/util/fusermount.c#L1219
`open` is executed as root which does not have access to the mount
point if `allow_other` was not used and the real user id is not 0. Since
`allow_other` usually cannot be specified by unprivileged users,
`auto_unmount` has no effect for unprivileged users.

In this commit, we work around this limitation:

We first try to open the mountpoint as root, and if we get `EACCES`, we
retry as the user who started fusermount, and see if we get `ENOTCONN`.

In my testing, I found that `setfsuid` and `setfsgid` don't work to get
around the lack of `allow_other`.  (Sorry, I don't know enough about the
Linux kernel to tell whether that's significant.)  As a workaround, I
decided to use `setresuid` and `setresgid` in a forked child process,
and communicate via its exit status.

Please give feedback on correctness, style and suggest tests.

Fixes https://github.com/libfuse/libfuse/issues/586

util/fusermount.c

index 6e72f0d344aa5417731a9a4e113faa427723c4ec..f752bfdbad6c3de79ff71716d8b806bb4919f59e 100644 (file)
@@ -1190,6 +1190,40 @@ static int send_fd(int sock_fd, int fd)
        return 0;
 }
 
+static int mnt_is_ENOTCONN_for_owner(const char *mnt)
+{
+       int pid = fork();
+       if(pid == -1) {
+               perror("fuse: mnt_is_ENOTCONN_for_owner can't fork");
+               _exit(EXIT_FAILURE);
+       } else if(pid == 0) {
+               uid_t uid = getuid();
+               gid_t gid = getgid();
+               if(setresgid(gid, gid, gid) == -1) {
+                       perror("fuse: can't set resgid");
+                       _exit(EXIT_FAILURE);
+               }
+               if(setresuid(uid, uid, uid) == -1) {
+                       perror("fuse: can't set resuid");
+                       _exit(EXIT_FAILURE);
+               }
+
+               int fd = open(mnt, O_RDONLY);
+               if(fd == -1 && errno == ENOTCONN)
+                       _exit(EXIT_SUCCESS);
+               else
+                       _exit(EXIT_FAILURE);
+       } else {
+               int status;
+               int res = waitpid(pid, &status, 0);
+               if (res == -1) {
+                       perror("fuse: waiting for child failed");
+                       _exit(EXIT_FAILURE);
+               }
+               return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
+       }
+}
+
 /* The parent fuse process has died: decide whether to auto_unmount.
  *
  * In the normal case (umount or fusermount -u), the filesystem
@@ -1225,10 +1259,21 @@ static int should_auto_unmount(const char *mnt, const char *type)
                goto out;
 
        fd = open(mnt, O_RDONLY);
+
        if (fd != -1) {
                close(fd);
        } else {
-               result = errno == ENOTCONN;
+               switch(errno) {
+               case ENOTCONN:
+                       result = 1;
+                       break;
+               case EACCES:
+                       result = mnt_is_ENOTCONN_for_owner(mnt);
+                       break;
+               default:
+                       result = 0;
+                       break;
+               }
        }
 out:
        free(copy);