diff options
author | gVisor bot <gvisor-bot@google.com> | 2021-01-28 12:22:00 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-01-28 12:24:37 -0800 |
commit | d8c330254a7df21cb5edac3440b62a512fcc8d2d (patch) | |
tree | 7c37b273cca0389da1375c7984cbd5fd21c01952 | |
parent | 449c155bc00aa36522f80f816b18e9a9521e1fe6 (diff) |
Add O_PATH support in vfs2
PiperOrigin-RevId: 354367665
36 files changed, 837 insertions, 18 deletions
diff --git a/pkg/sentry/syscalls/linux/vfs2/fd.go b/pkg/sentry/syscalls/linux/vfs2/fd.go index e39f074f2..1a31898e8 100644 --- a/pkg/sentry/syscalls/linux/vfs2/fd.go +++ b/pkg/sentry/syscalls/linux/vfs2/fd.go @@ -123,6 +123,15 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall } defer file.DecRef(t) + if file.StatusFlags()&linux.O_PATH != 0 { + switch cmd { + case linux.F_DUPFD, linux.F_DUPFD_CLOEXEC, linux.F_GETFD, linux.F_SETFD, linux.F_GETFL: + // allowed + default: + return 0, nil, syserror.EBADF + } + } + switch cmd { case linux.F_DUPFD, linux.F_DUPFD_CLOEXEC: minfd := args[2].Int() @@ -395,6 +404,10 @@ func Fadvise64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys } defer file.DecRef(t) + if file.StatusFlags()&linux.O_PATH != 0 { + return 0, nil, syserror.EBADF + } + // If the FD refers to a pipe or FIFO, return error. if _, isPipe := file.Impl().(*pipe.VFSPipeFD); isPipe { return 0, nil, syserror.ESPIPE diff --git a/pkg/sentry/syscalls/linux/vfs2/ioctl.go b/pkg/sentry/syscalls/linux/vfs2/ioctl.go index 20c264fef..c7c3fed57 100644 --- a/pkg/sentry/syscalls/linux/vfs2/ioctl.go +++ b/pkg/sentry/syscalls/linux/vfs2/ioctl.go @@ -32,6 +32,10 @@ func Ioctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall } defer file.DecRef(t) + if file.StatusFlags()&linux.O_PATH != 0 { + return 0, nil, syserror.EBADF + } + // Handle ioctls that apply to all FDs. switch args[1].Int() { case linux.FIONCLEX: diff --git a/pkg/sentry/syscalls/linux/vfs2/sync.go b/pkg/sentry/syscalls/linux/vfs2/sync.go index 6e9b599e2..1f8a5878c 100644 --- a/pkg/sentry/syscalls/linux/vfs2/sync.go +++ b/pkg/sentry/syscalls/linux/vfs2/sync.go @@ -36,6 +36,10 @@ func Syncfs(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal } defer file.DecRef(t) + if file.StatusFlags()&linux.O_PATH != 0 { + return 0, nil, syserror.EBADF + } + return 0, nil, file.SyncFS(t) } diff --git a/pkg/sentry/vfs/BUILD b/pkg/sentry/vfs/BUILD index a3868bf16..df4990854 100644 --- a/pkg/sentry/vfs/BUILD +++ b/pkg/sentry/vfs/BUILD @@ -83,6 +83,7 @@ go_library( "mount.go", "mount_namespace_refs.go", "mount_unsafe.go", + "opath.go", "options.go", "pathname.go", "permissions.go", diff --git a/pkg/sentry/vfs/opath.go b/pkg/sentry/vfs/opath.go new file mode 100644 index 000000000..39fbac987 --- /dev/null +++ b/pkg/sentry/vfs/opath.go @@ -0,0 +1,139 @@ +// Copyright 2019 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vfs + +import ( + "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/sentry/arch" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + "gvisor.dev/gvisor/pkg/sentry/memmap" + "gvisor.dev/gvisor/pkg/syserror" + "gvisor.dev/gvisor/pkg/usermem" +) + +// opathFD implements vfs.FileDescriptionImpl for a file description opened with O_PATH. +// +// +stateify savable +type opathFD struct { + vfsfd FileDescription + FileDescriptionDefaultImpl + NoLockFD +} + +// Release implements vfs.FileDescriptionImpl.Release. +func (fd *opathFD) Release(context.Context) { + // noop +} + +// Allocate implements vfs.FileDescriptionImpl.Allocate. +func (fd *opathFD) Allocate(ctx context.Context, mode, offset, length uint64) error { + return syserror.EBADF +} + +// PRead implements vfs.FileDescriptionImpl.PRead. +func (fd *opathFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error) { + return 0, syserror.EBADF +} + +// Read implements vfs.FileDescriptionImpl.Read. +func (fd *opathFD) Read(ctx context.Context, dst usermem.IOSequence, opts ReadOptions) (int64, error) { + return 0, syserror.EBADF +} + +// PWrite implements vfs.FileDescriptionImpl.PWrite. +func (fd *opathFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts WriteOptions) (int64, error) { + return 0, syserror.EBADF +} + +// Write implements vfs.FileDescriptionImpl.Write. +func (fd *opathFD) Write(ctx context.Context, src usermem.IOSequence, opts WriteOptions) (int64, error) { + return 0, syserror.EBADF +} + +// Ioctl implements vfs.FileDescriptionImpl.Ioctl. +func (fd *opathFD) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) { + return 0, syserror.EBADF +} + +// IterDirents implements vfs.FileDescriptionImpl.IterDirents. +func (fd *opathFD) IterDirents(ctx context.Context, cb IterDirentsCallback) error { + return syserror.EBADF +} + +// Seek implements vfs.FileDescriptionImpl.Seek. +func (fd *opathFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) { + return 0, syserror.EBADF +} + +// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap. +func (fd *opathFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error { + return syserror.EBADF +} + +// ListXattr implements vfs.FileDescriptionImpl.ListXattr. +func (fd *opathFD) ListXattr(ctx context.Context, size uint64) ([]string, error) { + return nil, syserror.EBADF +} + +// GetXattr implements vfs.FileDescriptionImpl.GetXattr. +func (fd *opathFD) GetXattr(ctx context.Context, opts GetXattrOptions) (string, error) { + return "", syserror.EBADF +} + +// SetXattr implements vfs.FileDescriptionImpl.SetXattr. +func (fd *opathFD) SetXattr(ctx context.Context, opts SetXattrOptions) error { + return syserror.EBADF +} + +// RemoveXattr implements vfs.FileDescriptionImpl.RemoveXattr. +func (fd *opathFD) RemoveXattr(ctx context.Context, name string) error { + return syserror.EBADF +} + +// Sync implements vfs.FileDescriptionImpl.Sync. +func (fd *opathFD) Sync(ctx context.Context) error { + return syserror.EBADF +} + +// SetStat implements vfs.FileDescriptionImpl.SetStat. +func (fd *opathFD) SetStat(ctx context.Context, opts SetStatOptions) error { + return syserror.EBADF +} + +// Stat implements vfs.FileDescriptionImpl.Stat. +func (fd *opathFD) Stat(ctx context.Context, opts StatOptions) (linux.Statx, error) { + vfsObj := fd.vfsfd.vd.mount.vfs + rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{ + Root: fd.vfsfd.vd, + Start: fd.vfsfd.vd, + }) + stat, err := fd.vfsfd.vd.mount.fs.impl.StatAt(ctx, rp, opts) + vfsObj.putResolvingPath(ctx, rp) + return stat, err +} + +// StatFS returns metadata for the filesystem containing the file represented +// by fd. +func (fd *opathFD) StatFS(ctx context.Context) (linux.Statfs, error) { + vfsObj := fd.vfsfd.vd.mount.vfs + rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{ + Root: fd.vfsfd.vd, + Start: fd.vfsfd.vd, + }) + statfs, err := fd.vfsfd.vd.mount.fs.impl.StatFSAt(ctx, rp) + vfsObj.putResolvingPath(ctx, rp) + return statfs, err +} diff --git a/pkg/sentry/vfs/options.go b/pkg/sentry/vfs/options.go index bc79e5ecc..c9907843c 100644 --- a/pkg/sentry/vfs/options.go +++ b/pkg/sentry/vfs/options.go @@ -129,7 +129,7 @@ type OpenOptions struct { // // FilesystemImpls are responsible for implementing the following flags: // O_RDONLY, O_WRONLY, O_RDWR, O_APPEND, O_CREAT, O_DIRECT, O_DSYNC, - // O_EXCL, O_NOATIME, O_NOCTTY, O_NONBLOCK, O_PATH, O_SYNC, O_TMPFILE, and + // O_EXCL, O_NOATIME, O_NOCTTY, O_NONBLOCK, O_SYNC, O_TMPFILE, and // O_TRUNC. VFS is responsible for handling O_DIRECTORY, O_LARGEFILE, and // O_NOFOLLOW. VFS users are responsible for handling O_CLOEXEC, since file // descriptors are mostly outside the scope of VFS. diff --git a/pkg/sentry/vfs/vfs.go b/pkg/sentry/vfs/vfs.go index 6fd1bb0b2..0aff2dd92 100644 --- a/pkg/sentry/vfs/vfs.go +++ b/pkg/sentry/vfs/vfs.go @@ -425,6 +425,18 @@ func (vfs *VirtualFilesystem) OpenAt(ctx context.Context, creds *auth.Credential rp.mustBeDir = true rp.mustBeDirOrig = true } + if opts.Flags&linux.O_PATH != 0 { + vd, err := vfs.GetDentryAt(ctx, creds, pop, &GetDentryOptions{}) + if err != nil { + return nil, err + } + fd := &opathFD{} + if err := fd.vfsfd.Init(fd, opts.Flags, vd.Mount(), vd.Dentry(), &FileDescriptionOptions{}); err != nil { + return nil, err + } + vd.DecRef(ctx) + return &fd.vfsfd, err + } for { fd, err := rp.mount.fs.impl.OpenAt(ctx, rp, *opts) if err == nil { diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 0da295e2d..2b4b6f348 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -582,6 +582,7 @@ cc_binary( "//test/util:eventfd_util", "//test/util:file_descriptor", gtest, + "//test/util:fs_util", "//test/util:posix_error", "//test/util:temp_path", "//test/util:test_main", diff --git a/test/syscalls/linux/chmod.cc b/test/syscalls/linux/chmod.cc index a06b5cfd6..8233df0f8 100644 --- a/test/syscalls/linux/chmod.cc +++ b/test/syscalls/linux/chmod.cc @@ -98,6 +98,42 @@ TEST(ChmodTest, FchmodatBadF) { ASSERT_THAT(fchmodat(-1, "foo", 0444, 0), SyscallFailsWithErrno(EBADF)); } +TEST(ChmodTest, FchmodFileWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + ASSERT_THAT(fchmod(fd.get(), 0444), SyscallFailsWithErrno(EBADF)); +} + +TEST(ChmodTest, FchmodDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const auto fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH)); + + ASSERT_THAT(fchmod(fd.get(), 0444), SyscallFailsWithErrno(EBADF)); +} + +TEST(ChmodTest, FchmodatWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + // Drop capabilities that allow us to override file permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + + auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + + const auto parent_fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(GetAbsoluteTestTmpdir().c_str(), O_PATH | O_DIRECTORY)); + + ASSERT_THAT( + fchmodat(parent_fd.get(), std::string(Basename(temp_file.path())).c_str(), + 0444, 0), + SyscallSucceeds()); + + EXPECT_THAT(open(temp_file.path().c_str(), O_RDWR), + SyscallFailsWithErrno(EACCES)); +} + TEST(ChmodTest, FchmodatNotDir) { ASSERT_THAT(fchmodat(-1, "", 0444, 0), SyscallFailsWithErrno(ENOENT)); } diff --git a/test/syscalls/linux/chown.cc b/test/syscalls/linux/chown.cc index 5530ad18f..ff0d39343 100644 --- a/test/syscalls/linux/chown.cc +++ b/test/syscalls/linux/chown.cc @@ -48,6 +48,36 @@ TEST(ChownTest, FchownatBadF) { ASSERT_THAT(fchownat(-1, "fff", 0, 0, 0), SyscallFailsWithErrno(EBADF)); } +TEST(ChownTest, FchownFileWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + ASSERT_THAT(fchown(fd.get(), geteuid(), getegid()), + SyscallFailsWithErrno(EBADF)); +} + +TEST(ChownTest, FchownDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const auto fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH)); + + ASSERT_THAT(fchown(fd.get(), geteuid(), getegid()), + SyscallFailsWithErrno(EBADF)); +} + +TEST(ChownTest, FchownatWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); + const auto dirfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH)); + ASSERT_THAT( + fchownat(dirfd.get(), file.path().c_str(), geteuid(), getegid(), 0), + SyscallSucceeds()); +} + TEST(ChownTest, FchownatEmptyPath) { const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const auto fd = @@ -209,6 +239,14 @@ INSTANTIATE_TEST_SUITE_P( owner, group, 0); MaybeSave(); return errorFromReturn("fchownat-dirfd", rc); + }, + [](const std::string& path, uid_t owner, gid_t group) -> PosixError { + ASSIGN_OR_RETURN_ERRNO(auto dirfd, Open(std::string(Dirname(path)), + O_DIRECTORY | O_PATH)); + int rc = fchownat(dirfd.get(), std::string(Basename(path)).c_str(), + owner, group, 0); + MaybeSave(); + return errorFromReturn("fchownat-opathdirfd", rc); })); } // namespace diff --git a/test/syscalls/linux/dup.cc b/test/syscalls/linux/dup.cc index 4f773bc75..ba4e13fb9 100644 --- a/test/syscalls/linux/dup.cc +++ b/test/syscalls/linux/dup.cc @@ -18,6 +18,7 @@ #include "gtest/gtest.h" #include "test/util/eventfd_util.h" #include "test/util/file_descriptor.h" +#include "test/util/fs_util.h" #include "test/util/posix_error.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" @@ -44,14 +45,6 @@ PosixErrorOr<FileDescriptor> Dup3(const FileDescriptor& fd, int target_fd, return FileDescriptor(new_fd); } -void CheckSameFile(const FileDescriptor& fd1, const FileDescriptor& fd2) { - struct stat stat_result1, stat_result2; - ASSERT_THAT(fstat(fd1.get(), &stat_result1), SyscallSucceeds()); - ASSERT_THAT(fstat(fd2.get(), &stat_result2), SyscallSucceeds()); - EXPECT_EQ(stat_result1.st_dev, stat_result2.st_dev); - EXPECT_EQ(stat_result1.st_ino, stat_result2.st_ino); -} - TEST(DupTest, Dup) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); @@ -59,7 +52,7 @@ TEST(DupTest, Dup) { // Dup the descriptor and make sure it's the same file. FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); ASSERT_NE(fd.get(), nfd.get()); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); } TEST(DupTest, DupClearsCloExec) { @@ -70,10 +63,24 @@ TEST(DupTest, DupClearsCloExec) { // Duplicate the descriptor. Ensure that it doesn't have FD_CLOEXEC set. FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); ASSERT_NE(fd.get(), nfd.get()); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); } +TEST(DupTest, DupWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); + int flags; + ASSERT_THAT(flags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); + + // Dup the descriptor and make sure it's the same file. + FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); + ASSERT_NE(fd.get(), nfd.get()); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); +} + TEST(DupTest, Dup2) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); @@ -82,13 +89,13 @@ TEST(DupTest, Dup2) { FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); ASSERT_NE(fd.get(), nfd.get()); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); // Dup over the file above. int target_fd = nfd.release(); FileDescriptor nfd2 = ASSERT_NO_ERRNO_AND_VALUE(Dup2(fd, target_fd)); EXPECT_EQ(target_fd, nfd2.get()); - CheckSameFile(fd, nfd2); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd2)); } TEST(DupTest, Dup2SameFD) { @@ -99,6 +106,28 @@ TEST(DupTest, Dup2SameFD) { ASSERT_THAT(dup2(fd.get(), fd.get()), SyscallSucceedsWithValue(fd.get())); } +TEST(DupTest, Dup2WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); + int flags; + ASSERT_THAT(flags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); + + // Regular dup once. + FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); + + ASSERT_NE(fd.get(), nfd.get()); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); + + // Dup over the file above. + int target_fd = nfd.release(); + FileDescriptor nfd2 = ASSERT_NO_ERRNO_AND_VALUE(Dup2(fd, target_fd)); + EXPECT_EQ(target_fd, nfd2.get()); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd2)); + EXPECT_THAT(fcntl(nfd2.get(), F_GETFL), SyscallSucceedsWithValue(flags)); +} + TEST(DupTest, Dup3) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); @@ -106,16 +135,16 @@ TEST(DupTest, Dup3) { // Regular dup once. FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); ASSERT_NE(fd.get(), nfd.get()); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); // Dup over the file above, check that it has no CLOEXEC. nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), 0)); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); // Dup over the file again, check that it does not CLOEXEC. nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), O_CLOEXEC)); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); } @@ -127,6 +156,32 @@ TEST(DupTest, Dup3FailsSameFD) { ASSERT_THAT(dup3(fd.get(), fd.get(), 0), SyscallFailsWithErrno(EINVAL)); } +TEST(DupTest, Dup3WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); + EXPECT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0)); + int flags; + ASSERT_THAT(flags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); + + // Regular dup once. + FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); + ASSERT_NE(fd.get(), nfd.get()); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + + // Dup over the file above, check that it has no CLOEXEC. + nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), 0)); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); + + // Dup over the file again, check that it does not CLOEXEC. + nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), O_CLOEXEC)); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/fadvise64.cc b/test/syscalls/linux/fadvise64.cc index 2af7aa6d9..ac24c4066 100644 --- a/test/syscalls/linux/fadvise64.cc +++ b/test/syscalls/linux/fadvise64.cc @@ -45,6 +45,17 @@ TEST(FAdvise64Test, Basic) { SyscallSucceeds()); } +TEST(FAdvise64Test, FAdvise64WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_NORMAL), + SyscallFailsWithErrno(EBADF)); + ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_NORMAL), + SyscallFailsWithErrno(EBADF)); +} + TEST(FAdvise64Test, InvalidArgs) { auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); diff --git a/test/syscalls/linux/fallocate.cc b/test/syscalls/linux/fallocate.cc index edd23e063..5c839447e 100644 --- a/test/syscalls/linux/fallocate.cc +++ b/test/syscalls/linux/fallocate.cc @@ -108,6 +108,13 @@ TEST_F(AllocateTest, FallocateReadonly) { EXPECT_THAT(fallocate(fd.get(), 0, 0, 10), SyscallFailsWithErrno(EBADF)); } +TEST_F(AllocateTest, FallocateWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + EXPECT_THAT(fallocate(fd.get(), 0, 0, 10), SyscallFailsWithErrno(EBADF)); +} + TEST_F(AllocateTest, FallocatePipe) { int pipes[2]; EXPECT_THAT(pipe(pipes), SyscallSucceeds()); diff --git a/test/syscalls/linux/fchdir.cc b/test/syscalls/linux/fchdir.cc index 08bcae1e8..c6675802d 100644 --- a/test/syscalls/linux/fchdir.cc +++ b/test/syscalls/linux/fchdir.cc @@ -71,6 +71,18 @@ TEST(FchdirTest, NotDir) { EXPECT_THAT(close(fd), SyscallSucceeds()); } +TEST(FchdirTest, FchdirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_dir.path(), O_PATH)); + ASSERT_THAT(open(temp_dir.path().c_str(), O_DIRECTORY | O_PATH), + SyscallSucceeds()); + + EXPECT_THAT(fchdir(fd.get()), SyscallSucceeds()); + // Change CWD to a permanent location as temp dirs will be cleaned up. + EXPECT_THAT(chdir("/"), SyscallSucceeds()); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc index 75a5c9f17..4fa6751ff 100644 --- a/test/syscalls/linux/fcntl.cc +++ b/test/syscalls/linux/fcntl.cc @@ -207,6 +207,41 @@ PosixErrorOr<Cleanup> SubprocessLock(std::string const& path, bool for_write, return std::move(cleanup); } +TEST(FcntlTest, FcntlDupWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); + + int new_fd; + // Dup the descriptor and make sure it's the same file. + EXPECT_THAT(new_fd = fcntl(fd.get(), F_DUPFD, 0), SyscallSucceeds()); + + FileDescriptor nfd = FileDescriptor(new_fd); + ASSERT_NE(fd.get(), nfd.get()); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(O_PATH)); +} + +TEST(FcntlTest, SetFileStatusFlagWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + + EXPECT_THAT(fcntl(fd.get(), F_SETFL, 0), SyscallFailsWithErrno(EBADF)); +} + +TEST(FcntlTest, BadFcntlsWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + + EXPECT_THAT(fcntl(fd.get(), F_SETOWN, 0), SyscallFailsWithErrno(EBADF)); + EXPECT_THAT(fcntl(fd.get(), F_GETOWN, 0), SyscallFailsWithErrno(EBADF)); + + EXPECT_THAT(fcntl(fd.get(), F_SETOWN_EX, 0), SyscallFailsWithErrno(EBADF)); + EXPECT_THAT(fcntl(fd.get(), F_GETOWN_EX, 0), SyscallFailsWithErrno(EBADF)); +} + TEST(FcntlTest, SetCloExecBadFD) { // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag not set. FileDescriptor f = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); @@ -226,6 +261,32 @@ TEST(FcntlTest, SetCloExec) { ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); } +TEST(FcntlTest, SetCloExecWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + // Open a file descriptor with FD_CLOEXEC descriptor flag not set. + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0)); + + // Set the FD_CLOEXEC flag. + ASSERT_THAT(fcntl(fd.get(), F_SETFD, FD_CLOEXEC), SyscallSucceeds()); + ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); +} + +TEST(FcntlTest, DupFDCloExecWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + // Open a file descriptor with FD_CLOEXEC descriptor flag not set. + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + int nfd; + ASSERT_THAT(nfd = fcntl(fd.get(), F_DUPFD_CLOEXEC, 0), SyscallSucceeds()); + FileDescriptor dup_fd(nfd); + + // Check for the FD_CLOEXEC flag. + ASSERT_THAT(fcntl(dup_fd.get(), F_GETFD), + SyscallSucceedsWithValue(FD_CLOEXEC)); +} + TEST(FcntlTest, ClearCloExec) { // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag set. FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_CLOEXEC)); @@ -267,6 +328,22 @@ TEST(FcntlTest, GetAllFlags) { EXPECT_EQ(rflags, expected); } +// When O_PATH is specified in flags, flag bits other than O_CLOEXEC, +// O_DIRECTORY, and O_NOFOLLOW are ignored. +TEST(FcntlTest, GetOpathFlag) { + SKIP_IF(IsRunningWithVFS1()); + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + int flags = O_RDWR | O_DIRECT | O_SYNC | O_NONBLOCK | O_APPEND | O_PATH | + O_NOFOLLOW | O_DIRECTORY; + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), flags)); + + int expected = O_PATH | O_NOFOLLOW | O_DIRECTORY; + + int rflags; + EXPECT_THAT(rflags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); + EXPECT_EQ(rflags, expected); +} + TEST(FcntlTest, SetFlags) { TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), 0)); @@ -395,6 +472,22 @@ TEST_F(FcntlLockTest, SetLockBadOpenFlagsRead) { EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl1), SyscallFailsWithErrno(EBADF)); } +TEST_F(FcntlLockTest, SetLockWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + struct flock fl0; + fl0.l_type = F_WRLCK; + fl0.l_whence = SEEK_SET; + fl0.l_start = 0; + fl0.l_len = 0; // Lock all file + + // Expect that setting a write lock using a Opath file descriptor + // won't work. + EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl0), SyscallFailsWithErrno(EBADF)); +} + TEST_F(FcntlLockTest, SetLockUnlockOnNothing) { auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); FileDescriptor fd = diff --git a/test/syscalls/linux/getdents.cc b/test/syscalls/linux/getdents.cc index 93c692dd6..2f2b14037 100644 --- a/test/syscalls/linux/getdents.cc +++ b/test/syscalls/linux/getdents.cc @@ -429,6 +429,32 @@ TYPED_TEST(GetdentsTest, NotDir) { SyscallFailsWithErrno(ENOTDIR)); } +// Test that getdents returns EBADF when called on an opath file. +TYPED_TEST(GetdentsTest, OpathFile) { + SKIP_IF(IsRunningWithVFS1()); + + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + typename TestFixture::DirentBufferType dirents(256); + EXPECT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(), + dirents.Size()), + SyscallFailsWithErrno(EBADF)); +} + +// Test that getdents returns EBADF when called on an opath directory. +TYPED_TEST(GetdentsTest, OpathDirectory) { + SKIP_IF(IsRunningWithVFS1()); + + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_PATH | O_DIRECTORY)); + + typename TestFixture::DirentBufferType dirents(256); + ASSERT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(), + dirents.Size()), + SyscallFailsWithErrno(EBADF)); +} + // Test that SEEK_SET to 0 causes getdents to re-read the entries. TYPED_TEST(GetdentsTest, SeekResetsCursor) { // . and .. should be in an otherwise empty directory. diff --git a/test/syscalls/linux/ioctl.cc b/test/syscalls/linux/ioctl.cc index b0a07a064..9b16d1558 100644 --- a/test/syscalls/linux/ioctl.cc +++ b/test/syscalls/linux/ioctl.cc @@ -76,6 +76,19 @@ TEST_F(IoctlTest, InvalidControlNumber) { EXPECT_THAT(ioctl(STDOUT_FILENO, 0), SyscallFailsWithErrno(ENOTTY)); } +TEST_F(IoctlTest, IoctlWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_PATH)); + + int set = 1; + EXPECT_THAT(ioctl(fd.get(), FIONBIO, &set), SyscallFailsWithErrno(EBADF)); + + EXPECT_THAT(ioctl(fd.get(), FIONCLEX), SyscallFailsWithErrno(EBADF)); + + EXPECT_THAT(ioctl(fd.get(), FIOCLEX), SyscallFailsWithErrno(EBADF)); +} + TEST_F(IoctlTest, FIONBIOSucceeds) { EXPECT_FALSE(CheckNonBlocking(fd())); int set = 1; diff --git a/test/syscalls/linux/link.cc b/test/syscalls/linux/link.cc index 544681168..4f9ca1a65 100644 --- a/test/syscalls/linux/link.cc +++ b/test/syscalls/linux/link.cc @@ -50,6 +50,8 @@ bool IsSameFile(const std::string& f1, const std::string& f2) { return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino; } +// TODO(b/178640646): Add test for linkat with AT_EMPTY_PATH + TEST(LinkTest, CanCreateLinkFile) { auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const std::string newname = NewTempAbsPath(); @@ -235,6 +237,59 @@ TEST(LinkTest, AbsPathsWithNonDirFDs) { SyscallSucceeds()); } +TEST(LinkTest, NewDirFDWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const std::string newname_parent = NewTempAbsPath(); + const std::string newname_base = "child"; + const std::string newname = JoinPath(newname_parent, newname_base); + + // Create newname_parent directory, and get an FD. + EXPECT_THAT(mkdir(newname_parent.c_str(), 0777), SyscallSucceeds()); + const FileDescriptor newname_parent_fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(newname_parent, O_DIRECTORY | O_PATH)); + + // Link newname to oldfile, using newname_parent_fd. + EXPECT_THAT(linkat(AT_FDCWD, oldfile.path().c_str(), newname_parent_fd.get(), + newname.c_str(), 0), + SyscallSucceeds()); + + EXPECT_TRUE(IsSameFile(oldfile.path(), newname)); +} + +TEST(LinkTest, RelPathsNonDirFDsWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + + // Create a file that will be passed as the directory fd for old/new names. + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor file_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + + // Using file_fd as olddirfd will fail. + EXPECT_THAT(linkat(file_fd.get(), "foo", AT_FDCWD, "bar", 0), + SyscallFailsWithErrno(ENOTDIR)); + + // Using file_fd as newdirfd will fail. + EXPECT_THAT(linkat(AT_FDCWD, oldfile.path().c_str(), file_fd.get(), "bar", 0), + SyscallFailsWithErrno(ENOTDIR)); +} + +TEST(LinkTest, AbsPathsNonDirFDsWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + + auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const std::string newname = NewTempAbsPath(); + + // Create a file that will be passed as the directory fd for old/new names. + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor file_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + + // Using file_fd as the dirfds is OK as long as paths are absolute. + EXPECT_THAT(linkat(file_fd.get(), oldfile.path().c_str(), file_fd.get(), + newname.c_str(), 0), + SyscallSucceeds()); +} + TEST(LinkTest, LinkDoesNotFollowSymlinks) { // Create oldfile, and oldsymlink which points to it. auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); diff --git a/test/syscalls/linux/mmap.cc b/test/syscalls/linux/mmap.cc index 83546830d..93a6d9cde 100644 --- a/test/syscalls/linux/mmap.cc +++ b/test/syscalls/linux/mmap.cc @@ -930,6 +930,18 @@ TEST_F(MMapFileTest, WriteSharedOnReadOnlyFd) { SyscallFailsWithErrno(EACCES)); } +// Mmap not allowed on O_PATH FDs. +TEST_F(MMapFileTest, MmapFileWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + uintptr_t addr; + EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd.get(), 0), + SyscallFailsWithErrno(EBADF)); +} + // The FD must be readable. TEST_P(MMapFileParamTest, WriteOnlyFd) { const FileDescriptor fd = diff --git a/test/syscalls/linux/open.cc b/test/syscalls/linux/open.cc index fcd162ca2..733b17834 100644 --- a/test/syscalls/linux/open.cc +++ b/test/syscalls/linux/open.cc @@ -45,7 +45,7 @@ namespace { // * O_CREAT // * O_DIRECTORY // * O_NOFOLLOW -// * O_PATH <- Will we ever support this? +// * O_PATH // // Special operations on open: // * O_EXCL @@ -517,6 +517,26 @@ TEST_F(OpenTest, OpenWithStrangeFlags) { EXPECT_THAT(read(fd.get(), &c, 1), SyscallFailsWithErrno(EBADF)); } +TEST_F(OpenTest, OpenWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); + const DisableSave ds; // Permissions are dropped. + std::string path = NewTempAbsPath(); + + // Create a file without user permissions. + const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 055)); + + // Cannot open file as read only because we are owner and have no permissions + // set. + EXPECT_THAT(open(path.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); + + // Can open file with O_PATH because don't need permissions on the object when + // opening with O_PATH. + ASSERT_NO_ERRNO(Open(path, O_PATH)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/pread64.cc b/test/syscalls/linux/pread64.cc index bcdbbb044..c74990ba1 100644 --- a/test/syscalls/linux/pread64.cc +++ b/test/syscalls/linux/pread64.cc @@ -77,6 +77,16 @@ TEST_F(Pread64Test, WriteOnlyNotReadable) { EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallFailsWithErrno(EBADF)); } +TEST_F(Pread64Test, Pread64WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + char buf[1024]; + EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallFailsWithErrno(EBADF)); +} + TEST_F(Pread64Test, DirNotReadable) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), O_RDONLY)); diff --git a/test/syscalls/linux/preadv.cc b/test/syscalls/linux/preadv.cc index 5b0743fe9..1c40f0915 100644 --- a/test/syscalls/linux/preadv.cc +++ b/test/syscalls/linux/preadv.cc @@ -89,6 +89,20 @@ TEST(PreadvTest, MMConcurrencyStress) { // The test passes if it neither deadlocks nor crashes the OS. } +// This test calls preadv with an O_PATH fd. +TEST(PreadvTest, PreadvWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + struct iovec iov; + iov.iov_base = nullptr; + iov.iov_len = 0; + + EXPECT_THAT(preadv(fd.get(), &iov, 1, 0), SyscallFailsWithErrno(EBADF)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/preadv2.cc b/test/syscalls/linux/preadv2.cc index 4a9acd7ae..cb58719c4 100644 --- a/test/syscalls/linux/preadv2.cc +++ b/test/syscalls/linux/preadv2.cc @@ -226,6 +226,24 @@ TEST(Preadv2Test, TestUnreadableFile) { SyscallFailsWithErrno(EBADF)); } +// This test calls preadv2 with a file opened with O_PATH. +TEST(Preadv2Test, Preadv2WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + auto iov = absl::make_unique<struct iovec[]>(1); + iov[0].iov_base = nullptr; + iov[0].iov_len = 0; + + EXPECT_THAT(preadv2(fd.get(), iov.get(), /*iovcnt=*/1, /*offset=*/0, + /*flags=*/0), + SyscallFailsWithErrno(EBADF)); +} + // Calling preadv2 with a non-negative offset calls preadv. Calling preadv with // an unseekable file is not allowed. A pipe is used for an unseekable file. TEST(Preadv2Test, TestUnseekableFileInvalid) { diff --git a/test/syscalls/linux/pwrite64.cc b/test/syscalls/linux/pwrite64.cc index e69794910..1b2f25363 100644 --- a/test/syscalls/linux/pwrite64.cc +++ b/test/syscalls/linux/pwrite64.cc @@ -77,6 +77,17 @@ TEST_F(Pwrite64, Overflow) { EXPECT_THAT(close(fd), SyscallSucceeds()); } +TEST_F(Pwrite64, Pwrite64WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + std::vector<char> buf(1); + EXPECT_THAT(PwriteFd(fd.get(), buf.data(), 1, 0), + SyscallFailsWithErrno(EBADF)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/pwritev2.cc b/test/syscalls/linux/pwritev2.cc index 63b686c62..00aed61b4 100644 --- a/test/syscalls/linux/pwritev2.cc +++ b/test/syscalls/linux/pwritev2.cc @@ -283,6 +283,23 @@ TEST(Pwritev2Test, ReadOnlyFile) { SyscallFailsWithErrno(EBADF)); } +TEST(Pwritev2Test, Pwritev2WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + char buf[16]; + struct iovec iov; + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/0, /*flags=*/0), + SyscallFailsWithErrno(EBADF)); +} + // This test calls pwritev2 with an invalid flag. TEST(Pwritev2Test, InvalidFlag) { SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); diff --git a/test/syscalls/linux/read.cc b/test/syscalls/linux/read.cc index 2633ba31b..98d5e432d 100644 --- a/test/syscalls/linux/read.cc +++ b/test/syscalls/linux/read.cc @@ -112,6 +112,15 @@ TEST_F(ReadTest, ReadDirectoryFails) { EXPECT_THAT(ReadFd(file.get(), buf.data(), 1), SyscallFailsWithErrno(EISDIR)); } +TEST_F(ReadTest, ReadWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + std::vector<char> buf(1); + EXPECT_THAT(ReadFd(fd.get(), buf.data(), 1), SyscallFailsWithErrno(EBADF)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/readv.cc b/test/syscalls/linux/readv.cc index baaf9f757..86808d255 100644 --- a/test/syscalls/linux/readv.cc +++ b/test/syscalls/linux/readv.cc @@ -251,6 +251,20 @@ TEST_F(ReadvTest, IovecOutsideTaskAddressRangeInNonemptyArray) { SyscallFailsWithErrno(EFAULT)); } +TEST_F(ReadvTest, ReadvWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + char buffer[1024]; + struct iovec iov[1]; + iov[0].iov_base = buffer; + iov[0].iov_len = 1024; + + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); + + ASSERT_THAT(readv(fd.get(), iov, 1), SyscallFailsWithErrno(EBADF)); +} + // This test depends on the maximum extent of a single readv() syscall, so // we can't tolerate interruption from saving. TEST(ReadvTestNoFixture, TruncatedAtMax_NoRandomSave) { diff --git a/test/syscalls/linux/stat.cc b/test/syscalls/linux/stat.cc index 6e7142a42..72f888659 100644 --- a/test/syscalls/linux/stat.cc +++ b/test/syscalls/linux/stat.cc @@ -221,6 +221,43 @@ TEST_F(StatTest, TrailingSlashNotCleanedReturnsENOTDIR) { EXPECT_THAT(lstat(bad_path.c_str(), &buf), SyscallFailsWithErrno(ENOTDIR)); } +TEST_F(StatTest, FstatFileWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + struct stat st; + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); + + // Stat the directory. + ASSERT_THAT(fstat(fd.get(), &st), SyscallSucceeds()); +} + +TEST_F(StatTest, FstatDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + struct stat st; + TempPath tmpdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( + Open(tmpdir.path().c_str(), O_PATH | O_DIRECTORY)); + + // Stat the directory. + ASSERT_THAT(fstat(dirfd.get(), &st), SyscallSucceeds()); +} + +// fstatat with an O_PATH fd +TEST_F(StatTest, FstatatDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath tmpdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( + Open(tmpdir.path().c_str(), O_PATH | O_DIRECTORY)); + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + + struct stat st = {}; + EXPECT_THAT(fstatat(dirfd.get(), tmpfile.path().c_str(), &st, 0), + SyscallSucceeds()); + EXPECT_FALSE(S_ISDIR(st.st_mode)); + EXPECT_TRUE(S_ISREG(st.st_mode)); +} + // Test fstatating a symlink directory. TEST_F(StatTest, FstatatSymlinkDir) { // Create a directory and symlink to it. diff --git a/test/syscalls/linux/statfs.cc b/test/syscalls/linux/statfs.cc index f0fb166bd..d4ea8e026 100644 --- a/test/syscalls/linux/statfs.cc +++ b/test/syscalls/linux/statfs.cc @@ -64,6 +64,16 @@ TEST(FstatfsTest, InternalTmpfs) { EXPECT_THAT(fstatfs(fd.get(), &st), SyscallSucceeds()); } +TEST(FstatfsTest, CanStatFileWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_PATH)); + + struct statfs st; + EXPECT_THAT(fstatfs(fd.get(), &st), SyscallSucceeds()); +} + TEST(FstatfsTest, InternalDevShm) { auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const FileDescriptor fd = diff --git a/test/syscalls/linux/symlink.cc b/test/syscalls/linux/symlink.cc index 4d9eba7f0..ea219a091 100644 --- a/test/syscalls/linux/symlink.cc +++ b/test/syscalls/linux/symlink.cc @@ -269,6 +269,36 @@ TEST(SymlinkTest, SymlinkAtDegradedPermissions_NoRandomSave) { EXPECT_THAT(close(dirfd), SyscallSucceeds()); } +TEST(SymlinkTest, SymlinkAtDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string filepath = NewTempAbsPathInDir(dir.path()); + const std::string base = std::string(Basename(filepath)); + FileDescriptor dirfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path().c_str(), O_DIRECTORY | O_PATH)); + + EXPECT_THAT(symlinkat("/dangling", dirfd.get(), base.c_str()), + SyscallSucceeds()); +} + +TEST(SymlinkTest, ReadlinkAtDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string filepath = NewTempAbsPathInDir(dir.path()); + const std::string base = std::string(Basename(filepath)); + ASSERT_THAT(symlink("/dangling", filepath.c_str()), SyscallSucceeds()); + + FileDescriptor dirfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path().c_str(), O_DIRECTORY | O_PATH)); + + std::vector<char> buf(1024); + int linksize; + EXPECT_THAT( + linksize = readlinkat(dirfd.get(), base.c_str(), buf.data(), 1024), + SyscallSucceeds()); + EXPECT_EQ(0, strncmp("/dangling", buf.data(), linksize)); +} + TEST(SymlinkTest, ReadlinkAtDegradedPermissions_NoRandomSave) { // Drop capabilities that allow us to override file and directory permissions. ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); diff --git a/test/syscalls/linux/sync.cc b/test/syscalls/linux/sync.cc index 8aa2525a9..84a2c4ed7 100644 --- a/test/syscalls/linux/sync.cc +++ b/test/syscalls/linux/sync.cc @@ -49,10 +49,20 @@ TEST(SyncTest, SyncFromPipe) { EXPECT_THAT(close(pipes[1]), SyscallSucceeds()); } -TEST(SyncTest, CannotSyncFileSytemAtBadFd) { +TEST(SyncTest, CannotSyncFileSystemAtBadFd) { EXPECT_THAT(syncfs(-1), SyscallFailsWithErrno(EBADF)); } +TEST(SyncTest, CannotSyncFileSystemAtOpathFD) { + SKIP_IF(IsRunningWithVFS1()); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( + GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + EXPECT_THAT(syncfs(fd.get()), SyscallFailsWithErrno(EBADF)); +} } // namespace } // namespace testing diff --git a/test/syscalls/linux/truncate.cc b/test/syscalls/linux/truncate.cc index bfc95ed38..17832c47d 100644 --- a/test/syscalls/linux/truncate.cc +++ b/test/syscalls/linux/truncate.cc @@ -196,6 +196,16 @@ TEST(TruncateTest, FtruncateNonWriteable) { EXPECT_THAT(ftruncate(fd.get(), 0), SyscallFailsWithErrno(EINVAL)); } +TEST(TruncateTest, FtruncateWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( + GetAbsoluteTestTmpdir(), absl::string_view(), 0555 /* mode */)); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_PATH)); + EXPECT_THAT(ftruncate(fd.get(), 0), AnyOf(SyscallFailsWithErrno(EBADF), + SyscallFailsWithErrno(EINVAL))); +} + // ftruncate(2) should succeed as long as the file descriptor is writeable, // regardless of whether the file permissions allow writing. TEST(TruncateTest, FtruncateWithoutWritePermission_NoRandomSave) { diff --git a/test/syscalls/linux/write.cc b/test/syscalls/linux/write.cc index 77bcfbb8a..740992d0a 100644 --- a/test/syscalls/linux/write.cc +++ b/test/syscalls/linux/write.cc @@ -218,6 +218,44 @@ TEST_F(WriteTest, PwriteNoChangeOffset) { EXPECT_THAT(lseek(fd, 0, SEEK_CUR), SyscallSucceedsWithValue(bytes_total)); } +TEST_F(WriteTest, WriteWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor f = + ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); + int fd = f.get(); + + EXPECT_THAT(WriteBytes(fd, 1024), SyscallFailsWithErrno(EBADF)); +} + +TEST_F(WriteTest, WritevWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor f = + ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); + int fd = f.get(); + + char buf[16]; + struct iovec iov; + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + EXPECT_THAT(writev(fd, &iov, /*__count=*/1), SyscallFailsWithErrno(EBADF)); +} + +TEST_F(WriteTest, PwriteWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor f = + ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); + int fd = f.get(); + + const std::string data = "hello world\n"; + + EXPECT_THAT(pwrite(fd, data.data(), data.size(), 0), + SyscallFailsWithErrno(EBADF)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/xattr.cc b/test/syscalls/linux/xattr.cc index bd3f829c4..a953a55fe 100644 --- a/test/syscalls/linux/xattr.cc +++ b/test/syscalls/linux/xattr.cc @@ -607,6 +607,27 @@ TEST_F(XattrTest, XattrWithFD) { EXPECT_THAT(fremovexattr(fd.get(), name), SyscallSucceeds()); } +TEST_F(XattrTest, XattrWithOPath) { + SKIP_IF(IsRunningWithVFS1()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_.c_str(), O_PATH)); + const char name[] = "user.test"; + int val = 1234; + size_t size = sizeof(val); + EXPECT_THAT(fsetxattr(fd.get(), name, &val, size, /*flags=*/0), + SyscallFailsWithErrno(EBADF)); + + int buf; + EXPECT_THAT(fgetxattr(fd.get(), name, &buf, size), + SyscallFailsWithErrno(EBADF)); + + char list[sizeof(name)]; + EXPECT_THAT(flistxattr(fd.get(), list, sizeof(list)), + SyscallFailsWithErrno(EBADF)); + + EXPECT_THAT(fremovexattr(fd.get(), name), SyscallFailsWithErrno(EBADF)); +} + TEST_F(XattrTest, TrustedNamespaceWithCapSysAdmin) { // Trusted namespace not supported in VFS1. SKIP_IF(IsRunningWithVFS1()); diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc index b16055dd8..5f1ce0d8a 100644 --- a/test/util/fs_util.cc +++ b/test/util/fs_util.cc @@ -663,5 +663,21 @@ PosixErrorOr<bool> IsOverlayfs(const std::string& path) { return stat.f_type == OVERLAYFS_SUPER_MAGIC; } +PosixError CheckSameFile(const FileDescriptor& fd1, const FileDescriptor& fd2) { + struct stat stat_result1, stat_result2; + int res = fstat(fd1.get(), &stat_result1); + if (res < 0) { + return PosixError(errno, absl::StrCat("fstat ", fd1.get())); + } + + res = fstat(fd2.get(), &stat_result2); + if (res < 0) { + return PosixError(errno, absl::StrCat("fstat ", fd2.get())); + } + EXPECT_EQ(stat_result1.st_dev, stat_result2.st_dev); + EXPECT_EQ(stat_result1.st_ino, stat_result2.st_ino); + + return NoError(); +} } // namespace testing } // namespace gvisor diff --git a/test/util/fs_util.h b/test/util/fs_util.h index c99cf5eb7..2190c3bca 100644 --- a/test/util/fs_util.h +++ b/test/util/fs_util.h @@ -191,6 +191,8 @@ PosixErrorOr<bool> IsTmpfs(const std::string& path); // IsOverlayfs returns true if the file at path is backed by overlayfs. PosixErrorOr<bool> IsOverlayfs(const std::string& path); +PosixError CheckSameFile(const FileDescriptor& fd1, const FileDescriptor& fd2); + namespace internal { // Not part of the public API. std::string JoinPathImpl(std::initializer_list<absl::string_view> paths); |