From a8447a81d2b76962a5a3e54f4f374f20c2a39bfb Mon Sep 17 00:00:00 2001 From: Jeremy Stribling Date: Thu, 25 Oct 2018 13:01:18 -0700 Subject: [PATCH] mount: better mount/unmount by avoiding binary exec Issue: KBFS-3553 --- mount_linux.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++- unmount_linux.go | 14 ++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/mount_linux.go b/mount_linux.go index 197d1044..b8402301 100644 --- a/mount_linux.go +++ b/mount_linux.go @@ -9,6 +9,8 @@ import ( "strings" "sync" "syscall" + + sysunix "golang.org/x/sys/unix" ) func handleFusermountStderr(errCh chan<- error) func(line string) (ignore bool) { @@ -55,10 +57,92 @@ func isBoringFusermountError(err error) bool { return false } -func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { +func getDirectMountOptions(dir string, conf *mountConfig, f *os.File) ( + fsName, options string, flag uintptr, err error) { + fsName = conf.options["fsname"] + + fi, err := os.Lstat(dir) + if err != nil { + return "", "", 0, err + } + if !fi.IsDir() { + return "", "", 0, fmt.Errorf("%s is not a directory", dir) + } + mode := fi.Mode() | 040000 // expected directory mode in a C-style stat buf + + // TODO: support more of fusermount's options here. + optionsSlice := []string{ + fmt.Sprintf("fd=%d", f.Fd()), + fmt.Sprintf("rootmode=%o", mode&syscall.S_IFMT), + "user_id=0", + "group_id=0", + } + if _, ok := conf.options["allow_other"]; ok { + optionsSlice = append(optionsSlice, "allow_other") + } + + flag = sysunix.MS_NOSUID | sysunix.MS_NODEV + if _, ok := conf.options["ro"]; ok { + flag |= sysunix.MS_RDONLY + } + + return fsName, strings.Join(optionsSlice, ","), flag, nil +} + +func doDirectMountAsRoot( + dir string, conf *mountConfig) (f *os.File, err error) { + f, err = os.OpenFile("/dev/fuse", os.O_RDWR, 0600) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + f.Close() + } + }() + + fsName, options, flag, err := getDirectMountOptions(dir, conf, f) + if err != nil { + return nil, err + } + + // Make sure the directory is empty before mounting over it. + d, err := os.Open(dir) + if err != nil { + return nil, err + } + fis, err := d.Readdir(0) + if err != nil { + d.Close() + return nil, err + } + err = d.Close() + if err != nil { + return nil, err + } + // TODO: Support the `nonempty` config option. + if len(fis) != 0 { + return nil, fmt.Errorf("%s is non-empty", dir) + } + + err = syscall.Mount(fsName, dir, "fuse", flag, options) + if err != nil { + return nil, err + } + return f, nil +} + +func mount(dir string, conf *mountConfig, ready chan<- struct{}, _ *error) (fusefd *os.File, err error) { // linux mount is never delayed close(ready) + if os.Geteuid() == 0 { + // If we are running as root, we can avoid the security risks + // that come along with exec'ing fusermount and just mount + // directly. + return doDirectMountAsRoot(dir, conf) + } + fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0) if err != nil { return nil, fmt.Errorf("socketpair error: %v", err) diff --git a/unmount_linux.go b/unmount_linux.go index 088f0cfe..f02448af 100644 --- a/unmount_linux.go +++ b/unmount_linux.go @@ -3,10 +3,24 @@ package fuse import ( "bytes" "errors" + "os" "os/exec" + + sysunix "golang.org/x/sys/unix" ) func unmount(dir string) error { + if os.Geteuid() == 0 { + // If we are running as root, we can avoid the security risks + // that come along with exec'ing fusermount and just unmount + // directly. Since we are root, let's just always detach the + // unmount to enable cases where the root user is forcing an + // upgrade of a user-based file system but there are still + // open file handles. TODO: plumb the detach flag through + // the public unmount interface. + return sysunix.Unmount(dir, sysunix.MNT_DETACH) + } + cmd := exec.Command("fusermount", "-u", dir) output, err := cmd.CombinedOutput() if err != nil {