libgocryptfs/internal/syscallcompat/sys_darwin.go
Jakob Unterwurzacher 72b975867a fusefronted: allow_other: close race between mknod and chown
If the user manages to replace the directory with
a symlink at just the right time, we could be tricked
into chown'ing the wrong file.

This change fixes the race by using fchownat, which
unfortunately is not available on darwin, hence a compat
wrapper is added.

Scenario, as described by @slackner at
https://github.com/rfjakob/gocryptfs/issues/177 :

1. Create a forward mount point with `plaintextnames` enabled
2. Mount as root user with `allow_other`
3. For testing purposes create a file `/tmp/file_owned_by_root`
   which is owned by the root user
4. As a regular user run inside of the GoCryptFS mount:

```
mkdir tempdir
mknod tempdir/file_owned_by_root p &
mv tempdir tempdir2
ln -s /tmp tempdir
```

When the steps are done fast enough and in the right order
(run in a loop!), the device file will be created in
`tempdir`, but the `lchown` will be executed by following
the symlink. As a result, the ownership of the file located
at `/tmp/file_owned_by_root` will be changed.
2017-11-27 21:04:45 +01:00

150 lines
3.4 KiB
Go

package syscallcompat
import (
"log"
"os"
"path/filepath"
"sync"
"syscall"
)
// Sorry, fallocate is not available on OSX at all and
// fcntl F_PREALLOCATE is not accessible from Go.
// See https://github.com/rfjakob/gocryptfs/issues/18 if you want to help.
func EnospcPrealloc(fd int, off int64, len int64) error {
return nil
}
// See above.
func Fallocate(fd int, mode uint32, off int64, len int64) error {
return syscall.EOPNOTSUPP
}
var chdirMutex sync.Mutex
// Poor man's Openat
func Openat(dirfd int, path string, flags int, mode uint32) (int, error) {
chdirMutex.Lock()
defer chdirMutex.Unlock()
if !filepath.IsAbs(path) {
// Save the old working directory
oldWd, err := os.Getwd()
if err != nil {
return -1, err
}
// Chdir to target directory
err = syscall.Fchdir(dirfd)
if err != nil {
return -1, err
}
// Chdir back at the end
defer os.Chdir(oldWd)
}
return syscall.Open(path, flags, mode)
}
// Poor man's Renameat
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error {
chdirMutex.Lock()
defer chdirMutex.Unlock()
// Unless both paths are absolute we have to save the old working dir and
// Chdir(oldWd) back to it in the end. If we error out before the first
// chdir, Chdir(oldWd) is unneccassary but does no harm.
if !filepath.IsAbs(oldpath) || !filepath.IsAbs(newpath) {
oldWd, err := os.Getwd()
if err != nil {
return err
}
defer os.Chdir(oldWd)
}
// Make oldpath absolute
oldpath, err := dirfdAbs(olddirfd, oldpath)
if err != nil {
return err
}
// Make newpath absolute
newpath, err = dirfdAbs(newdirfd, newpath)
if err != nil {
return err
}
return syscall.Rename(oldpath, newpath)
}
// Poor man's Unlinkat
func Unlinkat(dirfd int, path string) error {
chdirMutex.Lock()
defer chdirMutex.Unlock()
if !filepath.IsAbs(path) {
oldWd, err := os.Getwd()
if err != nil {
return err
}
defer os.Chdir(oldWd)
}
path, err := dirfdAbs(dirfd, path)
if err != nil {
return err
}
return syscall.Unlink(path)
}
// Poor man's Mknodat
func Mknodat(dirfd int, path string, mode uint32, dev int) error {
chdirMutex.Lock()
defer chdirMutex.Unlock()
if !filepath.IsAbs(path) {
oldWd, err := os.Getwd()
if err != nil {
return err
}
defer os.Chdir(oldWd)
}
path, err := dirfdAbs(dirfd, path)
if err != nil {
return err
}
return syscall.Mknod(path, mode, dev)
}
// dirfdAbs transforms the dirfd-relative "path" to an absolute one. If the
// path is not already absolute, this function will change the working
// directory. The caller has to chdir back.
func dirfdAbs(dirfd int, path string) (string, error) {
if filepath.IsAbs(path) {
return path, nil
}
err := syscall.Fchdir(dirfd)
if err != nil {
return "", err
}
wd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(wd, path), nil
}
// Dup3 is not available on Darwin, so we use Dup2 instead.
func Dup3(oldfd int, newfd int, flags int) (err error) {
if flags != 0 {
log.Panic("darwin does not support dup3 flags")
}
return syscall.Dup2(oldfd, newfd)
}
// Poor man's Fchownat.
func Fchownat(dirfd int, path string, uid int, gid int, flags int) (err error) {
cwd, err := syscall.Open(".", syscall.O_RDONLY, 0)
if err != nil {
return err
}
chdirMutex.Lock()
defer chdirMutex.Unlock()
err = syscall.Fchdir(dirfd)
if err != nil {
return err
}
defer syscall.Fchdir(cwd)
return syscall.Lchown(path, uid, gid)
}