Implement faccessat2.

PiperOrigin-RevId: 425432076
This commit is contained in:
Jamie Liu
2022-01-31 12:25:27 -08:00
committed by gVisor bot
parent 2a3d59997f
commit 5ef032d4df
4 changed files with 128 additions and 25 deletions
+5
View File
@@ -96,6 +96,11 @@ const (
AT_EMPTY_PATH = 0x1000
)
// Constants for faccessat2(2).
const (
AT_EACCESS = 0x200
)
// Constants for all file-related ...at(2) syscalls.
const (
AT_FDCWD = -100
+36 -25
View File
@@ -245,26 +245,29 @@ func Access(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal
addr := args[0].Pointer()
mode := args[1].ModeT()
return 0, nil, accessAt(t, linux.AT_FDCWD, addr, mode)
return 0, nil, accessAt(t, linux.AT_FDCWD, addr, mode, 0 /* flags */)
}
// Faccessat implements Linux syscall faccessat(2).
//
// Note that the faccessat() system call does not take a flags argument:
// "The raw faccessat() system call takes only the first three arguments. The
// AT_EACCESS and AT_SYMLINK_NOFOLLOW flags are actually implemented within
// the glibc wrapper function for faccessat(). If either of these flags is
// specified, then the wrapper function employs fstatat(2) to determine access
// permissions." - faccessat(2)
func Faccessat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
dirfd := args[0].Int()
addr := args[1].Pointer()
mode := args[2].ModeT()
return 0, nil, accessAt(t, dirfd, addr, mode)
return 0, nil, accessAt(t, dirfd, addr, mode, 0 /* flags */)
}
func accessAt(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, mode uint) error {
// Faccessat2 implements Linux syscall faccessat2(2).
func Faccessat2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
dirfd := args[0].Int()
addr := args[1].Pointer()
mode := args[2].ModeT()
flags := args[3].Int()
return 0, nil, accessAt(t, dirfd, addr, mode, flags)
}
func accessAt(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, mode uint, flags int32) error {
const rOK = 4
const wOK = 2
const xOK = 1
@@ -274,30 +277,38 @@ func accessAt(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, mode uint) er
return linuxerr.EINVAL
}
// faccessat2(2) isn't documented as supporting AT_EMPTY_PATH, but it does.
if flags&^(linux.AT_EACCESS|linux.AT_SYMLINK_NOFOLLOW|linux.AT_EMPTY_PATH) != 0 {
return linuxerr.EINVAL
}
path, err := copyInPath(t, pathAddr)
if err != nil {
return err
}
tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, followFinalSymlink)
tpop, err := getTaskPathOperation(t, dirfd, path, shouldAllowEmptyPath(flags&linux.AT_EMPTY_PATH != 0), shouldFollowFinalSymlink(flags&linux.AT_SYMLINK_NOFOLLOW == 0))
if err != nil {
return err
}
defer tpop.Release(t)
// access(2) and faccessat(2) check permissions using real
// UID/GID, not effective UID/GID.
//
// "access() needs to use the real uid/gid, not the effective
// uid/gid. We do this by temporarily clearing all FS-related
// capabilities and switching the fsuid/fsgid around to the
// real ones." -fs/open.c:faccessat
creds := t.Credentials().Fork()
creds.EffectiveKUID = creds.RealKUID
creds.EffectiveKGID = creds.RealKGID
if creds.EffectiveKUID.In(creds.UserNamespace) == auth.RootUID {
creds.EffectiveCaps = creds.PermittedCaps
} else {
creds.EffectiveCaps = 0
creds := t.Credentials()
if flags&linux.AT_EACCESS == 0 {
// access(2) and faccessat(2) check permissions using real
// UID/GID, not effective UID/GID.
//
// "access() needs to use the real uid/gid, not the effective
// uid/gid. We do this by temporarily clearing all FS-related
// capabilities and switching the fsuid/fsgid around to the
// real ones." -fs/open.c:faccessat
creds = creds.Fork()
creds.EffectiveKUID = creds.RealKUID
creds.EffectiveKGID = creds.RealKGID
if creds.EffectiveKUID.In(creds.UserNamespace) == auth.RootUID {
creds.EffectiveCaps = creds.PermittedCaps
} else {
creds.EffectiveCaps = 0
}
}
return t.Kernel().VFS().AccessAt(t, creds, vfs.AccessTypes(mode), &tpop.pop)
+2
View File
@@ -162,6 +162,7 @@ func Override() {
s.Table[327] = syscalls.Supported("preadv2", Preadv2)
s.Table[328] = syscalls.Supported("pwritev2", Pwritev2)
s.Table[332] = syscalls.Supported("statx", Statx)
s.Table[439] = syscalls.Supported("faccessat2", Faccessat2)
s.Table[441] = syscalls.Supported("epoll_pwait2", EpollPwait2)
s.Init()
@@ -276,6 +277,7 @@ func Override() {
s.Table[286] = syscalls.Supported("preadv2", Preadv2)
s.Table[287] = syscalls.Supported("pwritev2", Pwritev2)
s.Table[291] = syscalls.Supported("statx", Statx)
s.Table[439] = syscalls.Supported("faccessat2", Faccessat2)
s.Table[441] = syscalls.Supported("epoll_pwait2", EpollPwait2)
s.Init()
}
+85
View File
@@ -16,6 +16,7 @@
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
@@ -164,6 +165,90 @@ TEST_F(AccessTest, UsrReadWriteExec) {
EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
}
// glibc faccessat() is a wrapper around either the faccessat syscall that tries
// to implement flags in userspace, or the faccessat2 syscall. We want to test
// syscalls specifically, so use syscall(2) directly.
int sys_faccessat(int dirfd, const char* pathname, int mode) {
return syscall(SYS_faccessat, dirfd, pathname, mode);
}
#ifndef SYS_faccessat2
#define SYS_faccessat2 439
#endif // SYS_faccessat2
int sys_faccessat2(int dirfd, const char* pathname, int mode, int flags) {
return syscall(SYS_faccessat2, dirfd, pathname, mode, flags);
}
TEST(FaccessatTest, SymlinkFollowed) {
const std::string target_path = NewTempAbsPath();
const std::string symlink_path = NewTempAbsPath();
ASSERT_THAT(symlink(target_path.c_str(), symlink_path.c_str()),
SyscallSucceeds());
// faccessat() should initially fail with ENOENT since it follows the symlink
// to a file that doesn't exist.
EXPECT_THAT(sys_faccessat(-1, symlink_path.c_str(), F_OK),
SyscallFailsWithErrno(ENOENT));
// After creating the symlink target, faccessat() should succeed.
int fd;
ASSERT_THAT(fd = open(target_path.c_str(), O_CREAT | O_EXCL, 0644),
SyscallSucceeds());
close(fd);
EXPECT_THAT(sys_faccessat(-1, symlink_path.c_str(), F_OK), SyscallSucceeds());
}
PosixErrorOr<bool> Faccessat2Supported() {
if (IsRunningOnGvisor() && !IsRunningWithVFS1()) {
// faccessat2 support is expected on VFS2.
return true;
}
int ret = sys_faccessat2(-1, "/", F_OK, 0);
if (ret == 0) {
return true;
}
if (errno == ENOSYS) {
return false;
}
return PosixError(errno, "unexpected errno from faccessat2(/)");
}
TEST(Faccessat2Test, SymlinkFollowedByDefault) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(Faccessat2Supported()));
const std::string target_path = NewTempAbsPath();
const std::string symlink_path = NewTempAbsPath();
ASSERT_THAT(symlink(target_path.c_str(), symlink_path.c_str()),
SyscallSucceeds());
// faccessat2() should initially fail with ENOENT since, by default, it
// follows the symlink to a file that doesn't exist.
EXPECT_THAT(sys_faccessat2(-1, symlink_path.c_str(), F_OK, 0 /* flags */),
SyscallFailsWithErrno(ENOENT));
// After creating the symlink target, faccessat2() should succeed.
int fd;
ASSERT_THAT(fd = open(target_path.c_str(), O_CREAT | O_EXCL, 0644),
SyscallSucceeds());
close(fd);
EXPECT_THAT(sys_faccessat2(-1, symlink_path.c_str(), F_OK, 0 /* flags */),
SyscallSucceeds());
}
TEST(Faccessat2Test, SymlinkNofollow) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(Faccessat2Supported()));
const std::string target_path = NewTempAbsPath();
const std::string symlink_path = NewTempAbsPath();
ASSERT_THAT(symlink(target_path.c_str(), symlink_path.c_str()),
SyscallSucceeds());
EXPECT_THAT(
sys_faccessat2(-1, symlink_path.c_str(), F_OK, AT_SYMLINK_NOFOLLOW),
SyscallSucceeds());
}
} // namespace
} // namespace testing