From bd9dbc187b6e1dacfdd2722a87e83093c2d7bd6e Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 27 Jul 2018 14:32:08 -0700 Subject: [PATCH] unix: support Faccessat flags argument The Linux kernel faccessat system call does not take a flags parameter. The flag parameter to the C library faccessat function is implemented in C. The unix.Faccessat function takes a flags parameter. In older releases we have passed the flags parameter to the kernel, which ignored it. In CL 119495 we started returning an error if any flags were set. That seems clearly better than ignoring them, but it turns out that some code was using the flags. The code was previously subtly broken. Now it is obviously broken. That is better, but we can do better still: we can implement the flags as the C library does. That is what this CL does. Change-Id: I02d4bb981ebd39eb35e47c6e5281f85eaea68016 Reviewed-on: https://go-review.googlesource.com/126516 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- unix/syscall_linux.go | 66 ++++++++++++++++++++++++++++++++++++-- unix/syscall_linux_test.go | 38 +++++++++++++++++----- 2 files changed, 93 insertions(+), 11 deletions(-) diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index 3514c1ee..712d1726 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -1415,10 +1415,70 @@ func Vmsplice(fd int, iovs []Iovec, flags int) (int, error) { func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) { if flags & ^(AT_SYMLINK_NOFOLLOW|AT_EACCESS) != 0 { return EINVAL - } else if flags&(AT_SYMLINK_NOFOLLOW|AT_EACCESS) != 0 { - return EOPNOTSUPP } - return faccessat(dirfd, path, mode) + + // The Linux kernel faccessat system call does not take any flags. + // The glibc faccessat implements the flags itself; see + // https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/faccessat.c;hb=HEAD + // Because people naturally expect syscall.Faccessat to act + // like C faccessat, we do the same. + + if flags == 0 { + return faccessat(dirfd, path, mode) + } + + var st Stat_t + if err := Fstatat(dirfd, path, &st, flags&AT_SYMLINK_NOFOLLOW); err != nil { + return err + } + + mode &= 7 + if mode == 0 { + return nil + } + + var uid int + if flags&AT_EACCESS != 0 { + uid = Geteuid() + } else { + uid = Getuid() + } + + if uid == 0 { + if mode&1 == 0 { + // Root can read and write any file. + return nil + } + if st.Mode&0111 != 0 { + // Root can execute any file that anybody can execute. + return nil + } + return EACCES + } + + var fmode uint32 + if uint32(uid) == st.Uid { + fmode = (st.Mode >> 6) & 7 + } else { + var gid int + if flags&AT_EACCESS != 0 { + gid = Getegid() + } else { + gid = Getgid() + } + + if uint32(gid) == st.Gid { + fmode = (st.Mode >> 3) & 7 + } else { + fmode = st.Mode & 7 + } + } + + if fmode&mode == mode { + return nil + } + + return EACCES } /* diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go index eed17268..7fb5804f 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go @@ -394,19 +394,19 @@ func TestFaccessat(t *testing.T) { defer chtmpdir(t)() touch(t, "file1") - err := unix.Faccessat(unix.AT_FDCWD, "file1", unix.O_RDONLY, 0) + err := unix.Faccessat(unix.AT_FDCWD, "file1", unix.R_OK, 0) if err != nil { t.Errorf("Faccessat: unexpected error: %v", err) } - err = unix.Faccessat(unix.AT_FDCWD, "file1", unix.O_RDONLY, 2) + err = unix.Faccessat(unix.AT_FDCWD, "file1", unix.R_OK, 2) if err != unix.EINVAL { t.Errorf("Faccessat: unexpected error: %v, want EINVAL", err) } - err = unix.Faccessat(unix.AT_FDCWD, "file1", unix.O_RDONLY, unix.AT_EACCESS) - if err != unix.EOPNOTSUPP { - t.Errorf("Faccessat: unexpected error: %v, want EOPNOTSUPP", err) + err = unix.Faccessat(unix.AT_FDCWD, "file1", unix.R_OK, unix.AT_EACCESS) + if err != nil { + t.Errorf("Faccessat: unexpected error: %v", err) } err = os.Symlink("file1", "symlink1") @@ -414,8 +414,30 @@ func TestFaccessat(t *testing.T) { t.Fatal(err) } - err = unix.Faccessat(unix.AT_FDCWD, "symlink1", unix.O_RDONLY, unix.AT_SYMLINK_NOFOLLOW) - if err != unix.EOPNOTSUPP { - t.Errorf("Faccessat: unexpected error: %v, want EOPNOTSUPP", err) + err = unix.Faccessat(unix.AT_FDCWD, "symlink1", unix.R_OK, unix.AT_SYMLINK_NOFOLLOW) + if err != nil { + t.Errorf("Faccessat SYMLINK_NOFOLLOW: unexpected error %v", err) + } + + // We can't really test AT_SYMLINK_NOFOLLOW, because there + // doesn't seem to be any way to change the mode of a symlink. + // We don't test AT_EACCESS because such tests are only + // meaningful if run as root. + + err = unix.Fchmodat(unix.AT_FDCWD, "file1", 0, 0) + if err != nil { + t.Errorf("Fchmodat: unexpected error %v", err) + } + + err = unix.Faccessat(unix.AT_FDCWD, "file1", unix.F_OK, unix.AT_SYMLINK_NOFOLLOW) + if err != nil { + t.Errorf("Faccessat: unexpected error: %v", err) + } + + err = unix.Faccessat(unix.AT_FDCWD, "file1", unix.R_OK, unix.AT_SYMLINK_NOFOLLOW) + if err != unix.EACCES { + if unix.Getuid() != 0 { + t.Errorf("Faccessat: unexpected error: %v, want EACCES", err) + } } }