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.
notifications
Ben Linsay 2022-05-31 16:22:54 -04:00 committed by GitHub
parent 37d63df227
commit 21122235c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 82 additions and 9 deletions

View File

@ -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)

View File

@ -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
}

37
mount_linux_test.go Normal file
View File

@ -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")
}
})
}