From b71377f2876b351e506be85c14742d4ecc8c803d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 26 Dec 2018 15:44:07 +0100 Subject: [PATCH 1/2] try mounting without fusermount(1) first This requires root privileges or the CAP_SYS_ADMIN capability, but allows supporting -o suid. --- mount_config.go | 10 ++-- mount_linux.go | 131 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 106 insertions(+), 35 deletions(-) diff --git a/mount_config.go b/mount_config.go index 96e7910..485ed96 100644 --- a/mount_config.go +++ b/mount_config.go @@ -223,10 +223,9 @@ func escapeOptionsKey(s string) (res string) { return } -// Create an options string suitable for passing to the mount helper. -func (c *MountConfig) toOptionsString() string { +func mapToOptionsString(opts map[string]string) string { var components []string - for k, v := range c.toMap() { + for k, v := range opts { k = escapeOptionsKey(k) component := k @@ -239,3 +238,8 @@ func (c *MountConfig) toOptionsString() string { return strings.Join(components, ",") } + +// Create an options string suitable for passing to the mount helper. +func (c *MountConfig) toOptionsString() string { + return mapToOptionsString(c.toMap()) +} diff --git a/mount_linux.go b/mount_linux.go index 2df4e78..0decf0b 100644 --- a/mount_linux.go +++ b/mount_linux.go @@ -2,29 +2,21 @@ package fuse import ( "bytes" + "errors" "fmt" "net" "os" "os/exec" "syscall" + + "golang.org/x/sys/unix" ) -// Begin the process of mounting at the given directory, returning a connection -// to the kernel. Mounting continues in the background, and is complete when an -// error is written to the supplied channel. The file system may need to -// service the connection in order for mounting to complete. -func mount( - dir string, - cfg *MountConfig, - ready chan<- error) (dev *os.File, err error) { - // On linux, mounting is never delayed. - ready <- nil - +func fusermount(dir string, cfg *MountConfig) (*os.File, error) { // Create a socket pair. fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0) if err != nil { - err = fmt.Errorf("Socketpair: %v", err) - return + return nil, fmt.Errorf("Socketpair: %v", err) } // Wrap the sockets into os.File objects that we will pass off to fusermount. @@ -51,23 +43,20 @@ func mount( // Run the command. err = cmd.Run() if err != nil { - err = fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes()) - return + return nil, fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes()) } // Wrap the socket file in a connection. c, err := net.FileConn(readFile) if err != nil { - err = fmt.Errorf("FileConn: %v", err) - return + return nil, fmt.Errorf("FileConn: %v", err) } defer c.Close() // We expect to have a Unix domain socket. uc, ok := c.(*net.UnixConn) if !ok { - err = fmt.Errorf("Expected UnixConn, got %T", c) - return + return nil, fmt.Errorf("Expected UnixConn, got %T", c) } // Read a message. @@ -75,21 +64,18 @@ func mount( oob := make([]byte, 32) // expect 24 bytes _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob) if err != nil { - err = fmt.Errorf("ReadMsgUnix: %v", err) - return + return nil, fmt.Errorf("ReadMsgUnix: %v", err) } // Parse the message. scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) if err != nil { - err = fmt.Errorf("ParseSocketControlMessage: %v", err) - return + return nil, fmt.Errorf("ParseSocketControlMessage: %v", err) } // We expect one message. if len(scms) != 1 { - err = fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms) - return + return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms) } scm := scms[0] @@ -97,17 +83,98 @@ func mount( // Pull out the FD returned by fusermount gotFds, err := syscall.ParseUnixRights(&scm) if err != nil { - err = fmt.Errorf("syscall.ParseUnixRights: %v", err) - return + return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err) } if len(gotFds) != 1 { - err = fmt.Errorf("wanted 1 fd; got %#v", gotFds) - return + return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds) } // Turn the FD into an os.File. - dev = os.NewFile(uintptr(gotFds[0]), "/dev/fuse") - - return + return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil +} + +func enableFunc(flag uintptr) func(uintptr) uintptr { + return func(v uintptr) uintptr { + return v | flag + } +} + +func disableFunc(flag uintptr) func(uintptr) uintptr { + return func(v uintptr) uintptr { + return v &^ flag + } +} + +// As per libfuse/fusermount.c:602: https://bit.ly/2SgtWYM#L602 +var mountflagopts = map[string]func(uintptr) uintptr{ + "rw": enableFunc(unix.MS_RDONLY), + "ro": disableFunc(unix.MS_RDONLY), + "suid": disableFunc(unix.MS_NOSUID), + "nosuid": enableFunc(unix.MS_NOSUID), + "dev": disableFunc(unix.MS_NODEV), + "nodev": enableFunc(unix.MS_NODEV), + "exec": disableFunc(unix.MS_NOEXEC), + "noexec": enableFunc(unix.MS_NOEXEC), + "async": disableFunc(unix.MS_SYNCHRONOUS), + "sync": enableFunc(unix.MS_SYNCHRONOUS), + "atime": disableFunc(unix.MS_NOATIME), + "noatime": enableFunc(unix.MS_NOATIME), + "dirsync": enableFunc(unix.MS_DIRSYNC), +} + +var errFallback = errors.New("sentinel: fallback to fusermount(1)") + +func directmount(dir string, cfg *MountConfig) (*os.File, error) { + dev, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0644) + if err != nil { + return nil, errFallback + } + // As per libfuse/fusermount.c:847: https://bit.ly/2SgtWYM#L847 + data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=%d,group_id=%d", + dev.Fd(), os.Getuid(), os.Getgid()) + // As per libfuse/fusermount.c:749: https://bit.ly/2SgtWYM#L749 + mountflag := uintptr(unix.MS_NODEV | unix.MS_NOSUID) + opts := cfg.toMap() + for k := range opts { + fn, ok := mountflagopts[k] + if !ok { + continue + } + mountflag = fn(mountflag) + delete(opts, k) + } + delete(opts, "fsname") // handled via fstype mount(2) parameter + data += "," + mapToOptionsString(opts) + if err := unix.Mount( + cfg.FSName, // source + dir, // target + "fuse", // fstype + mountflag, // mountflag + data, // data + ); err != nil { + if err == syscall.EPERM { + return nil, errFallback + + } + return nil, err + } + return dev, nil +} + +// Begin the process of mounting at the given directory, returning a connection +// to the kernel. Mounting continues in the background, and is complete when an +// error is written to the supplied channel. The file system may need to +// service the connection in order for mounting to complete. +func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) { + // On linux, mounting is never delayed. + ready <- 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) + if err == errFallback { + return fusermount(dir, cfg) + } + return dev, err } From 53aac50a1c027397c9f0fd6c13f840adbbd36f21 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Jul 2019 19:48:37 -0700 Subject: [PATCH 2/2] open /dev/fuse in blocking mode so the runtime does not use a poller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /dev/fuse is not pollable. Go ≥ 1.13 starts reporting this on Read(). --- mount_linux.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mount_linux.go b/mount_linux.go index 0decf0b..ce83d9f 100644 --- a/mount_linux.go +++ b/mount_linux.go @@ -126,10 +126,14 @@ var mountflagopts = map[string]func(uintptr) uintptr{ var errFallback = errors.New("sentinel: fallback to fusermount(1)") func directmount(dir string, cfg *MountConfig) (*os.File, error) { - dev, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0644) + // We use syscall.Open + os.NewFile instead of os.OpenFile so that the file + // is opened in blocking mode. When opened in non-blocking mode, the Go + // runtime tries to use poll(2), which does not work with /dev/fuse. + fd, err := syscall.Open("/dev/fuse", syscall.O_RDWR, 0644) if err != nil { return nil, errFallback } + dev := os.NewFile(uintptr(fd), "/dev/fuse") // As per libfuse/fusermount.c:847: https://bit.ly/2SgtWYM#L847 data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=%d,group_id=%d", dev.Fd(), os.Getuid(), os.Getgid())