diff options
-rw-r--r-- | internal/fusefrontend/fs_dir.go | 79 | ||||
-rw-r--r-- | internal/syscallcompat/sys_darwin.go | 5 | ||||
-rw-r--r-- | internal/syscallcompat/sys_linux.go | 22 |
3 files changed, 60 insertions, 46 deletions
diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go index afcc5e5..880f271 100644 --- a/internal/fusefrontend/fs_dir.go +++ b/internal/fusefrontend/fs_dir.go @@ -24,13 +24,13 @@ const dsStoreName = ".DS_Store" // mkdirWithIv - create a new directory and corresponding diriv file. dirfd // should be a handle to the parent directory, cName is the name of the new // directory and mode specifies the access permissions to use. -func (fs *FS) mkdirWithIv(dirfd int, cName string, mode uint32) error { +func (fs *FS) mkdirWithIv(dirfd int, cName string, mode uint32, context *fuse.Context) error { // Between the creation of the directory and the creation of gocryptfs.diriv // the directory is inconsistent. Take the lock to prevent other readers // from seeing it. fs.dirIVLock.Lock() defer fs.dirIVLock.Unlock() - err := syscallcompat.Mkdirat(dirfd, cName, mode) + err := syscallcompat.MkdiratUser(dirfd, cName, mode, context) if err != nil { return err } @@ -63,40 +63,20 @@ func (fs *FS) Mkdir(newPath string, mode uint32, context *fuse.Context) (code fu return fuse.ToStatus(err) } defer syscall.Close(dirfd) - // Don't set the full mode before we have set the correct owner. - // See Create FUSE call for more details. - origMode := mode - if fs.args.PreserveOwner { - mode = 0000 + // Make sure context is nil if we don't want to preserve the owner + if !fs.args.PreserveOwner { + context = nil } if fs.args.PlaintextNames { - err = syscallcompat.Mkdirat(dirfd, cName, mode) - if err != nil { - return fuse.ToStatus(err) - } - // Set owner - if fs.args.PreserveOwner { - err = syscallcompat.Fchownat(dirfd, cName, int(context.Owner.Uid), - int(context.Owner.Gid), unix.AT_SYMLINK_NOFOLLOW) - if err != nil { - tlog.Warn.Printf("Mkdir %q: Fchownat %d:%d failed: %v", cName, context.Owner.Uid, context.Owner.Gid, err) - // In case of a failure, we don't want to proceed setting more - // permissive modes. - return fuse.ToStatus(err) - } - } - // Set mode - if origMode != mode { - err = syscallcompat.Fchmodat(dirfd, cName, origMode, unix.AT_SYMLINK_NOFOLLOW) - if err != nil { - tlog.Warn.Printf("Mkdir %q: Fchmodat %#o -> %#o failed: %v", cName, mode, origMode, err) - } - } - return fuse.OK + err = syscallcompat.MkdiratUser(dirfd, cName, mode, context) + return fuse.ToStatus(err) } - // We need write and execute permissions to create gocryptfs.diriv - mode = mode | 0300 + // We need write and execute permissions to create gocryptfs.diriv. + // Also, we need read permissions to open the directory (to avoid + // race-conditions between getting and setting the mode). + origMode := mode + mode = mode | 0700 // Handle long file name if nametransform.IsLongContent(cName) { @@ -107,33 +87,40 @@ func (fs *FS) Mkdir(newPath string, mode uint32, context *fuse.Context) (code fu } // Create directory - err = fs.mkdirWithIv(dirfd, cName, mode) + err = fs.mkdirWithIv(dirfd, cName, mode, context) if err != nil { nametransform.DeleteLongNameAt(dirfd, cName) return fuse.ToStatus(err) } } else { - err = fs.mkdirWithIv(dirfd, cName, mode) + err = fs.mkdirWithIv(dirfd, cName, mode, context) if err != nil { return fuse.ToStatus(err) } } - // Set owner - if fs.args.PreserveOwner { - err = syscallcompat.Fchownat(dirfd, cName, int(context.Owner.Uid), - int(context.Owner.Gid), unix.AT_SYMLINK_NOFOLLOW) + // Set mode + if origMode != mode { + dirfd2, err := syscallcompat.Openat(dirfd, cName, + syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) if err != nil { - tlog.Warn.Printf("Mkdir %q: Fchownat %d:%d failed: %v", cName, context.Owner.Uid, context.Owner.Gid, err) - // In case of a failure, we don't want to proceed setting more - // permissive modes. + tlog.Warn.Printf("Mkdir %q: Openat failed: %v", cName, err) return fuse.ToStatus(err) } - } - // Set mode - if origMode != mode { - err = syscallcompat.Fchmodat(dirfd, cName, origMode, unix.AT_SYMLINK_NOFOLLOW) + defer syscall.Close(dirfd2) + + var st syscall.Stat_t + err = syscall.Fstat(dirfd2, &st) + if err != nil { + tlog.Warn.Printf("Mkdir %q: Fstat failed: %v", cName, err) + return fuse.ToStatus(err) + } + + // Preserve SGID bit if it was set due to inheritance. + origMode = uint32(st.Mode&^0777) | origMode + err = syscall.Fchmod(dirfd2, origMode) if err != nil { - tlog.Warn.Printf("Mkdir %q: Fchmodat %#o -> %#o failed: %v", cName, mode, origMode, err) + tlog.Warn.Printf("Mkdir %q: Fchmod %#o -> %#o failed: %v", cName, mode, origMode, err) + return fuse.ToStatus(err) } } return fuse.OK diff --git a/internal/syscallcompat/sys_darwin.go b/internal/syscallcompat/sys_darwin.go index 7defc5f..3c431b9 100644 --- a/internal/syscallcompat/sys_darwin.go +++ b/internal/syscallcompat/sys_darwin.go @@ -79,6 +79,11 @@ func Mkdirat(dirfd int, path string, mode uint32) (err error) { return emulateMkdirat(dirfd, path, mode) } +func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) { + // FIXME: take into account context.Owner + return Mkdirat(dirfd, path, mode) +} + func Fstatat(dirfd int, path string, stat *unix.Stat_t, flags int) (err error) { return emulateFstatat(dirfd, path, stat, flags) } diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go index 595aa1d..a431195 100644 --- a/internal/syscallcompat/sys_linux.go +++ b/internal/syscallcompat/sys_linux.go @@ -180,6 +180,28 @@ func Mkdirat(dirfd int, path string, mode uint32) (err error) { return syscall.Mkdirat(dirfd, path, mode) } +// MkdiratUser runs the Mkdirat syscall in the context of a different user. +func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) { + if context != nil { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err = syscall.Setregid(-1, int(context.Owner.Gid)) + if err != nil { + return err + } + defer syscall.Setregid(-1, 0) + + err = syscall.Setreuid(-1, int(context.Owner.Uid)) + if err != nil { + return err + } + defer syscall.Setreuid(-1, 0) + } + + return Mkdirat(dirfd, path, mode) +} + // Fstatat syscall. func Fstatat(dirfd int, path string, stat *unix.Stat_t, flags int) (err error) { // Why would we ever want to call this without AT_SYMLINK_NOFOLLOW? |