Commit b71377f2 authored by Michael Stapelberg's avatar Michael Stapelberg Committed by Michael Stapelberg

try mounting without fusermount(1) first

This requires root privileges or the CAP_SYS_ADMIN capability, but allows
supporting -o suid.
parent 659cc51b
...@@ -223,10 +223,9 @@ func escapeOptionsKey(s string) (res string) { ...@@ -223,10 +223,9 @@ func escapeOptionsKey(s string) (res string) {
return return
} }
// Create an options string suitable for passing to the mount helper. func mapToOptionsString(opts map[string]string) string {
func (c *MountConfig) toOptionsString() string {
var components []string var components []string
for k, v := range c.toMap() { for k, v := range opts {
k = escapeOptionsKey(k) k = escapeOptionsKey(k)
component := k component := k
...@@ -239,3 +238,8 @@ func (c *MountConfig) toOptionsString() string { ...@@ -239,3 +238,8 @@ func (c *MountConfig) toOptionsString() string {
return strings.Join(components, ",") return strings.Join(components, ",")
} }
// Create an options string suitable for passing to the mount helper.
func (c *MountConfig) toOptionsString() string {
return mapToOptionsString(c.toMap())
}
...@@ -2,29 +2,21 @@ package fuse ...@@ -2,29 +2,21 @@ package fuse
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"net" "net"
"os" "os"
"os/exec" "os/exec"
"syscall" "syscall"
)
// Begin the process of mounting at the given directory, returning a connection "golang.org/x/sys/unix"
// 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. // Create a socket pair.
fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0) fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0)
if err != nil { if err != nil {
err = fmt.Errorf("Socketpair: %v", err) return nil, fmt.Errorf("Socketpair: %v", err)
return
} }
// Wrap the sockets into os.File objects that we will pass off to fusermount. // Wrap the sockets into os.File objects that we will pass off to fusermount.
...@@ -51,23 +43,20 @@ func mount( ...@@ -51,23 +43,20 @@ func mount(
// Run the command. // Run the command.
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
err = fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes()) return nil, fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes())
return
} }
// Wrap the socket file in a connection. // Wrap the socket file in a connection.
c, err := net.FileConn(readFile) c, err := net.FileConn(readFile)
if err != nil { if err != nil {
err = fmt.Errorf("FileConn: %v", err) return nil, fmt.Errorf("FileConn: %v", err)
return
} }
defer c.Close() defer c.Close()
// We expect to have a Unix domain socket. // We expect to have a Unix domain socket.
uc, ok := c.(*net.UnixConn) uc, ok := c.(*net.UnixConn)
if !ok { if !ok {
err = fmt.Errorf("Expected UnixConn, got %T", c) return nil, fmt.Errorf("Expected UnixConn, got %T", c)
return
} }
// Read a message. // Read a message.
...@@ -75,21 +64,18 @@ func mount( ...@@ -75,21 +64,18 @@ func mount(
oob := make([]byte, 32) // expect 24 bytes oob := make([]byte, 32) // expect 24 bytes
_, oobn, _, _, err := uc.ReadMsgUnix(buf, oob) _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
if err != nil { if err != nil {
err = fmt.Errorf("ReadMsgUnix: %v", err) return nil, fmt.Errorf("ReadMsgUnix: %v", err)
return
} }
// Parse the message. // Parse the message.
scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
if err != nil { if err != nil {
err = fmt.Errorf("ParseSocketControlMessage: %v", err) return nil, fmt.Errorf("ParseSocketControlMessage: %v", err)
return
} }
// We expect one message. // We expect one message.
if len(scms) != 1 { if len(scms) != 1 {
err = fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms) return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
return
} }
scm := scms[0] scm := scms[0]
...@@ -97,17 +83,98 @@ func mount( ...@@ -97,17 +83,98 @@ func mount(
// Pull out the FD returned by fusermount // Pull out the FD returned by fusermount
gotFds, err := syscall.ParseUnixRights(&scm) gotFds, err := syscall.ParseUnixRights(&scm)
if err != nil { if err != nil {
err = fmt.Errorf("syscall.ParseUnixRights: %v", err) return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err)
return
} }
if len(gotFds) != 1 { if len(gotFds) != 1 {
err = fmt.Errorf("wanted 1 fd; got %#v", gotFds) return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
return
} }
// Turn the FD into an os.File. // Turn the FD into an os.File.
dev = os.NewFile(uintptr(gotFds[0]), "/dev/fuse") 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)")
return 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
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment