From a7d59032d3790e117a48be6be1fb3a968266093b Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Mon, 14 Jan 2019 21:54:16 +0100 Subject: syscallcompat: rework Fchmodat to FchmodatNofollow We never want Fchmodat to follow symlinks, so follow what Qemu does, and call our function FchmodatNofollow. --- internal/syscallcompat/sys_common_test.go | 6 +++--- internal/syscallcompat/sys_darwin.go | 11 ++-------- internal/syscallcompat/sys_linux.go | 35 +++++++++++++------------------ 3 files changed, 19 insertions(+), 33 deletions(-) (limited to 'internal/syscallcompat') diff --git a/internal/syscallcompat/sys_common_test.go b/internal/syscallcompat/sys_common_test.go index 14e1b3f..3729111 100644 --- a/internal/syscallcompat/sys_common_test.go +++ b/internal/syscallcompat/sys_common_test.go @@ -175,7 +175,7 @@ func TestFchmodat(t *testing.T) { defer syscall.Close(dirfd) // Check that chmod on a regular file works ok - err = Fchmodat(dirfd, regular, 0111, 0) + err = FchmodatNofollow(dirfd, regular, 0111, 0) if err != nil { t.Fatal(err) } @@ -185,7 +185,7 @@ func TestFchmodat(t *testing.T) { if st.Mode != 0111 { t.Errorf("wrong mode: %#0o", st.Mode) } - err = Fchmodat(dirfd, regular, 0000, 0) + err = FchmodatNofollow(dirfd, regular, 0000, 0) if err != nil { t.Error(err) } @@ -196,7 +196,7 @@ func TestFchmodat(t *testing.T) { } // Check what happens on a symlink - err = Fchmodat(dirfd, symlink, 0333, 0) + err = FchmodatNofollow(dirfd, symlink, 0333, 0) if err == nil { syscall.Lstat(tmpDir+"/"+symlink, &st) st.Mode &= 0777 diff --git a/internal/syscallcompat/sys_darwin.go b/internal/syscallcompat/sys_darwin.go index b8b82c8..ebda80f 100644 --- a/internal/syscallcompat/sys_darwin.go +++ b/internal/syscallcompat/sys_darwin.go @@ -8,8 +8,6 @@ import ( "golang.org/x/sys/unix" "github.com/hanwen/go-fuse/fuse" - - "github.com/rfjakob/gocryptfs/internal/tlog" ) const ( @@ -93,13 +91,8 @@ func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Con return Mknodat(dirfd, path, mode, dev) } -func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) { - // Why would we ever want to call this without AT_SYMLINK_NOFOLLOW? - if flags&unix.AT_SYMLINK_NOFOLLOW == 0 { - tlog.Warn.Printf("Fchmodat: adding missing AT_SYMLINK_NOFOLLOW flag") - flags |= unix.AT_SYMLINK_NOFOLLOW - } - return unix.Fchmodat(dirfd, path, mode, flags) +func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) { + return unix.Fchmodat(dirfd, path, mode, unix.AT_SYMLINK_NOFOLLOW) } func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) { diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go index e662001..625e136 100644 --- a/internal/syscallcompat/sys_linux.go +++ b/internal/syscallcompat/sys_linux.go @@ -113,20 +113,17 @@ func Dup3(oldfd int, newfd int, flags int) (err error) { return syscall.Dup3(oldfd, newfd, flags) } -// Fchmodat syscall. -func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) { - // Linux does not support passing flags to Fchmodat! From the man page: - // AT_SYMLINK_NOFOLLOW ... This flag is not currently implemented. - // Linux ignores any flags, but Go stdlib rejects them with EOPNOTSUPP starting - // with Go 1.11. See https://github.com/golang/go/issues/20130 for more info. - // TODO: Use fchmodat2 once available on Linux. - - // Why would we ever want to call this without AT_SYMLINK_NOFOLLOW? - if flags&unix.AT_SYMLINK_NOFOLLOW == 0 { - tlog.Warn.Printf("Fchmodat: adding missing AT_SYMLINK_NOFOLLOW flag") - } - +// FchmodatNofollow is like Fchmodat but never follows symlinks. +// +// This should be handled by the AT_SYMLINK_NOFOLLOW flag, but Linux +// does not implement it, so we have to perform an elaborate dance +// with O_PATH and /proc/self/fd. +// +// See also: Qemu implemented the same logic as fchmodat_nofollow(): +// https://git.qemu.org/?p=qemu.git;a=blob;f=hw/9pfs/9p-local.c#l335 +func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) { // Open handle to the filename (but without opening the actual file). + // This succeeds even when we don't have read permissions to the file. fd, err := syscall.Openat(dirfd, path, syscall.O_NOFOLLOW|O_PATH, 0) if err != nil { return err @@ -134,22 +131,18 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) { defer syscall.Close(fd) // Now we can check the type without the risk of race-conditions. + // Return syscall.ELOOP if it is a symlink. var st syscall.Stat_t err = syscall.Fstat(fd, &st) if err != nil { return err } - - // Return syscall.ELOOP if path refers to a symlink. - var a fuse.Attr - a.FromStat(&st) - if a.IsSymlink() { + if st.Mode&syscall.S_IFMT == syscall.S_IFLNK { return syscall.ELOOP } - // Change mode of the actual file. Note that we can neither use - // Fchmodat (since fd is not necessarily a directory) nor Fchmod - // (since we are using O_PATH). + // Change mode of the actual file. Fchmod does not work with O_PATH, + // but Chmod via /proc/self/fd works. procPath := fmt.Sprintf("/proc/self/fd/%d", fd) return syscall.Chmod(procPath, mode) } -- cgit v1.2.3