72b975867a
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.
150 lines
3.4 KiB
Go
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)
|
|
}
|