mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement faccessat2.
PiperOrigin-RevId: 425432076
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user