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) + } } }