From 21122235c77acecd922ced4b17fbc52918751ef2 Mon Sep 17 00:00:00 2001 From: Ben Linsay Date: Tue, 31 May 2022 16:22:54 -0400 Subject: [PATCH] allow passing open /dev/fuse file descriptors (#124) allows passing open /dev/fuse file descriptors so that the FUSE process can run fully unprivileged. uses the /dev/fd/N mountpoint format from libfuse3. --- mount.go | 31 ++++++++++++++++++++++--------- mount_linux.go | 23 +++++++++++++++++++++++ mount_linux_test.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 mount_linux_test.go diff --git a/mount.go b/mount.go index 543f55b..c80cf43 100644 --- a/mount.go +++ b/mount.go @@ -20,6 +20,7 @@ import ( "net" "os" "os/exec" + "strings" "syscall" ) @@ -41,16 +42,8 @@ func Mount( config *MountConfig) (*MountedFileSystem, error) { // Sanity check: make sure the mount point exists and is a directory. This // saves us from some confusing errors later on OS X. - fi, err := os.Stat(dir) - switch { - case os.IsNotExist(err): + if err := checkMountPoint(dir); err != nil { return nil, err - - case err != nil: - return nil, fmt.Errorf("Statting mount point: %v", err) - - case !fi.IsDir(): - return nil, fmt.Errorf("Mount point %s is not a directory", dir) } // Initialize the struct. @@ -97,6 +90,26 @@ func Mount( return mfs, nil } +func checkMountPoint(dir string) error { + if strings.HasPrefix(dir, "/dev/fd") { + return nil + } + + fi, err := os.Stat(dir) + switch { + case os.IsNotExist(err): + return err + + case err != nil: + return fmt.Errorf("Statting mount point: %v", err) + + case !fi.IsDir(): + return fmt.Errorf("Mount point %s is not a directory", dir) + } + + return nil +} + func fusermount(binary string, argv []string, additionalEnv []string, wait bool) (*os.File, error) { // Create a socket pair. fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) diff --git a/mount_linux.go b/mount_linux.go index 34eefb2..8cad616 100644 --- a/mount_linux.go +++ b/mount_linux.go @@ -5,6 +5,8 @@ import ( "fmt" "os" "os/exec" + "strconv" + "strings" "syscall" "golang.org/x/sys/unix" @@ -106,6 +108,14 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) { // On linux, mounting is never delayed. ready <- nil + // If the mountpoint is /dev/fd/N, assume that the file descriptor N is an + // already open FUSE channel. Parse it, cast it to an fd, and don't do any + // other part of the mount dance. + if fd, err := parseFuseFd(dir); err == nil { + dev := os.NewFile(uintptr(fd), "/dev/fuse") + return dev, nil + } + // Try mounting without fusermount(1) first: we might be running as root or // have the CAP_SYS_ADMIN capability. dev, err := directmount(dir, cfg) @@ -123,3 +133,16 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) { } return dev, err } + +func parseFuseFd(dir string) (int, error) { + if !strings.HasPrefix(dir, "/dev/fd/") { + return -1, fmt.Errorf("not a /dev/fd path") + } + + fd, err := strconv.ParseUint(strings.TrimPrefix(dir, "/dev/fd/"), 10, 32) + if err != nil { + return -1, fmt.Errorf("invalid /dev/fd/N path: N must be a positive integer") + } + + return int(fd), nil +} diff --git a/mount_linux_test.go b/mount_linux_test.go new file mode 100644 index 0000000..1e1b9de --- /dev/null +++ b/mount_linux_test.go @@ -0,0 +1,37 @@ +package fuse + +import ( + "testing" +) + +func Test_parseFuseFd(t *testing.T) { + t.Run("valid", func(t *testing.T) { + fd, err := parseFuseFd("/dev/fd/42") + if fd != 42 { + t.Errorf("expected 42, got %d", fd) + } + if err != nil { + t.Errorf("expected no error, got %#v", err) + } + }) + + t.Run("negative", func(t *testing.T) { + fd, err := parseFuseFd("/dev/fd/-42") + if fd != -1 { + t.Errorf("expected an invalid fd, got %d", fd) + } + if err == nil { + t.Errorf("expected an error, nil") + } + }) + + t.Run("not an int", func(t *testing.T) { + fd, err := parseFuseFd("/dev/fd/3.14159") + if fd != -1 { + t.Errorf("expected an invalid fd, got %d", fd) + } + if err == nil { + t.Errorf("expected an error, nil") + } + }) +}