fusefrontend: ensure directories without W or X perms can be deleted

This fixed the "Permission denied" bug, but still has the problem that
the directory may be replaced behind our back. Mitigated by the fact
that we skip the workaround when running as root with -allow_other.

https://github.com/rfjakob/gocryptfs/issues/354
This commit is contained in:
Jakob Unterwurzacher 2019-01-20 14:29:28 +01:00
parent fab585ec01
commit 962c523644
1 changed files with 29 additions and 24 deletions

View File

@ -151,42 +151,47 @@ func (fs *FS) Rmdir(relPath string, context *fuse.Context) (code fuse.Status) {
err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR) err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
dirfd, err := syscallcompat.Openat(parentDirFd, cName, // Unless we are running as root, we need read, write and execute permissions
syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) // to handle gocryptfs.diriv.
if err == syscall.EACCES { permWorkaround := false
// We need permission to read and modify the directory var origMode uint32
tlog.Debug.Printf("Rmdir: handling EACCESS") if !fs.args.PreserveOwner {
var st unix.Stat_t var st unix.Stat_t
err = syscallcompat.Fstatat(parentDirFd, cName, &st, unix.AT_SYMLINK_NOFOLLOW) err = syscallcompat.Fstatat(parentDirFd, cName, &st, unix.AT_SYMLINK_NOFOLLOW)
if err != nil { if err != nil {
tlog.Debug.Printf("Rmdir: Stat: %v", err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
// This cast is needed on Darwin, where st.Mode is uint16. if st.Mode&0700 != 0700 {
origMode := uint32(st.Mode) tlog.Debug.Printf("Rmdir: permWorkaround")
err = syscallcompat.FchmodatNofollow(parentDirFd, cName, origMode|0700) permWorkaround = true
if err != nil { // This cast is needed on Darwin, where st.Mode is uint16.
tlog.Debug.Printf("Rmdir: Fchmodat failed: %v", err) origMode = uint32(st.Mode)
return fuse.ToStatus(err) err = syscallcompat.FchmodatNofollow(parentDirFd, cName, origMode|0700)
} if err != nil {
// Retry open tlog.Debug.Printf("Rmdir: permWorkaround: chmod failed: %v", err)
dirfd, err = syscallcompat.Openat(parentDirFd, cName, return fuse.ToStatus(err)
syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
// Undo the chmod if removing the directory failed
defer func() {
if code != fuse.OK {
err = syscallcompat.FchmodatNofollow(parentDirFd, cName, origMode)
if err != nil {
tlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err)
}
} }
}() }
} }
dirfd, err := syscallcompat.Openat(parentDirFd, cName,
syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
if err != nil { if err != nil {
tlog.Debug.Printf("Rmdir: Open: %v", err) tlog.Debug.Printf("Rmdir: Open: %v", err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
defer syscall.Close(dirfd) defer syscall.Close(dirfd)
// Undo the chmod if removing the directory failed. This must run before
// closing dirfd, so defer it after (defer is LIFO).
if permWorkaround {
defer func() {
if code != fuse.OK {
err = unix.Fchmod(dirfd, origMode)
if err != nil {
tlog.Warn.Printf("Rmdir: permWorkaround: rollback failed: %v", err)
}
}
}()
}
retry: retry:
// Check directory contents // Check directory contents
children, err := syscallcompat.Getdents(dirfd) children, err := syscallcompat.Getdents(dirfd)