diff options
Diffstat (limited to 'test/syscalls/linux/open.cc')
-rw-r--r-- | test/syscalls/linux/open.cc | 451 |
1 files changed, 451 insertions, 0 deletions
diff --git a/test/syscalls/linux/open.cc b/test/syscalls/linux/open.cc new file mode 100644 index 000000000..bb7d108e8 --- /dev/null +++ b/test/syscalls/linux/open.cc @@ -0,0 +1,451 @@ +// Copyright 2018 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. + +#include <errno.h> +#include <fcntl.h> +#include <linux/capability.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "test/syscalls/linux/file_base.h" +#include "test/util/capability_util.h" +#include "test/util/cleanup.h" +#include "test/util/file_descriptor.h" +#include "test/util/fs_util.h" +#include "test/util/temp_path.h" +#include "test/util/test_util.h" +#include "test/util/thread_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +// This test is currently very rudimentary. +// +// There are plenty of extra cases to cover once the sentry supports them. +// +// Different types of opens: +// * O_CREAT +// * O_DIRECTORY +// * O_NOFOLLOW +// * O_PATH <- Will we ever support this? +// +// Special operations on open: +// * O_EXCL +// +// Special files: +// * Blocking behavior for a named pipe. +// +// Different errors: +// * EACCES +// * EEXIST +// * ENAMETOOLONG +// * ELOOP +// * ENOTDIR +// * EPERM +class OpenTest : public FileTest { + void SetUp() override { + FileTest::SetUp(); + + ASSERT_THAT( + write(test_file_fd_.get(), test_data_.c_str(), test_data_.length()), + SyscallSucceedsWithValue(test_data_.length())); + EXPECT_THAT(lseek(test_file_fd_.get(), 0, SEEK_SET), SyscallSucceeds()); + } + + public: + const std::string test_data_ = "hello world\n"; +}; + +TEST_F(OpenTest, OTrunc) { + auto dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncd"); + ASSERT_THAT(mkdir(dirpath.c_str(), 0777), SyscallSucceeds()); + ASSERT_THAT(open(dirpath.c_str(), O_TRUNC, 0666), + SyscallFailsWithErrno(EISDIR)); +} + +TEST_F(OpenTest, OTruncAndReadOnlyDir) { + auto dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncd"); + ASSERT_THAT(mkdir(dirpath.c_str(), 0777), SyscallSucceeds()); + ASSERT_THAT(open(dirpath.c_str(), O_TRUNC | O_RDONLY, 0666), + SyscallFailsWithErrno(EISDIR)); +} + +TEST_F(OpenTest, OTruncAndReadOnlyFile) { + auto dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncfile"); + const FileDescriptor existing = + ASSERT_NO_ERRNO_AND_VALUE(Open(dirpath.c_str(), O_RDWR | O_CREAT, 0666)); + const FileDescriptor otrunc = ASSERT_NO_ERRNO_AND_VALUE( + Open(dirpath.c_str(), O_TRUNC | O_RDONLY, 0666)); +} + +TEST_F(OpenTest, ReadOnly) { + char buf; + const FileDescriptor ro_file = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); + + EXPECT_THAT(read(ro_file.get(), &buf, 1), SyscallSucceedsWithValue(1)); + EXPECT_THAT(lseek(ro_file.get(), 0, SEEK_SET), SyscallSucceeds()); + EXPECT_THAT(write(ro_file.get(), &buf, 1), SyscallFailsWithErrno(EBADF)); +} + +TEST_F(OpenTest, WriteOnly) { + char buf; + const FileDescriptor wo_file = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_WRONLY)); + + EXPECT_THAT(read(wo_file.get(), &buf, 1), SyscallFailsWithErrno(EBADF)); + EXPECT_THAT(lseek(wo_file.get(), 0, SEEK_SET), SyscallSucceeds()); + EXPECT_THAT(write(wo_file.get(), &buf, 1), SyscallSucceedsWithValue(1)); +} + +TEST_F(OpenTest, ReadWrite) { + char buf; + const FileDescriptor rw_file = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); + + EXPECT_THAT(read(rw_file.get(), &buf, 1), SyscallSucceedsWithValue(1)); + EXPECT_THAT(lseek(rw_file.get(), 0, SEEK_SET), SyscallSucceeds()); + EXPECT_THAT(write(rw_file.get(), &buf, 1), SyscallSucceedsWithValue(1)); +} + +TEST_F(OpenTest, RelPath) { + auto name = std::string(Basename(test_file_name_)); + + ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds()); + const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name, O_RDONLY)); +} + +TEST_F(OpenTest, AbsPath) { + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); +} + +TEST_F(OpenTest, AtRelPath) { + auto name = std::string(Basename(test_file_name_)); + const FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( + Open(GetAbsoluteTestTmpdir(), O_RDONLY | O_DIRECTORY)); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(OpenAt(dirfd.get(), name, O_RDONLY)); +} + +TEST_F(OpenTest, AtAbsPath) { + const FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( + Open(GetAbsoluteTestTmpdir(), O_RDONLY | O_DIRECTORY)); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(OpenAt(dirfd.get(), test_file_name_, O_RDONLY)); +} + +TEST_F(OpenTest, OpenNoFollowSymlink) { + const std::string link_path = JoinPath(GetAbsoluteTestTmpdir(), "link"); + ASSERT_THAT(symlink(test_file_name_.c_str(), link_path.c_str()), + SyscallSucceeds()); + auto cleanup = Cleanup([link_path]() { + EXPECT_THAT(unlink(link_path.c_str()), SyscallSucceeds()); + }); + + // Open will succeed without O_NOFOLLOW and fails with O_NOFOLLOW. + const FileDescriptor fd2 = + ASSERT_NO_ERRNO_AND_VALUE(Open(link_path, O_RDONLY)); + ASSERT_THAT(open(link_path.c_str(), O_RDONLY | O_NOFOLLOW), + SyscallFailsWithErrno(ELOOP)); +} + +TEST_F(OpenTest, OpenNoFollowStillFollowsLinksInPath) { + // We will create the following structure: + // tmp_folder/real_folder/file + // tmp_folder/sym_folder -> tmp_folder/real_folder + // + // We will then open tmp_folder/sym_folder/file with O_NOFOLLOW and it + // should succeed as O_NOFOLLOW only applies to the final path component. + auto tmp_path = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir())); + auto sym_path = ASSERT_NO_ERRNO_AND_VALUE( + TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), tmp_path.path())); + auto file_path = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(tmp_path.path())); + + auto path_via_symlink = JoinPath(sym_path.path(), Basename(file_path.path())); + const FileDescriptor fd2 = + ASSERT_NO_ERRNO_AND_VALUE(Open(path_via_symlink, O_RDONLY | O_NOFOLLOW)); +} + +// Test that open(2) can follow symlinks that point back to the same tree. +// Test sets up files as follows: +// root/child/symlink => redirects to ../.. +// root/child/target => regular file +// +// open("root/child/symlink/root/child/file") +TEST_F(OpenTest, SymlinkRecurse) { + auto root = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir())); + auto child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path())); + auto symlink = ASSERT_NO_ERRNO_AND_VALUE( + TempPath::CreateSymlinkTo(child.path(), "../..")); + auto target = ASSERT_NO_ERRNO_AND_VALUE( + TempPath::CreateFileWith(child.path(), "abc", 0644)); + auto path_via_symlink = + JoinPath(symlink.path(), Basename(root.path()), Basename(child.path()), + Basename(target.path())); + const auto contents = + ASSERT_NO_ERRNO_AND_VALUE(GetContents(path_via_symlink)); + ASSERT_EQ(contents, "abc"); +} + +TEST_F(OpenTest, Fault) { + char* totally_not_null = nullptr; + ASSERT_THAT(open(totally_not_null, O_RDONLY), SyscallFailsWithErrno(EFAULT)); +} + +TEST_F(OpenTest, AppendOnly) { + // First write some data to the fresh file. + const int64_t kBufSize = 1024; + std::vector<char> buf(kBufSize, 'a'); + + FileDescriptor fd0 = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); + + std::fill(buf.begin(), buf.end(), 'a'); + EXPECT_THAT(WriteFd(fd0.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + fd0.reset(); // Close the file early. + + // Next get two handles to the same file. We open two files because we want + // to make sure that appending is respected between them. + const FileDescriptor fd1 = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR | O_APPEND)); + EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); + + const FileDescriptor fd2 = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR | O_APPEND)); + EXPECT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); + + // Then try to write to the first file and make sure the bytes are appended. + EXPECT_THAT(WriteFd(fd1.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + + // Check that the size of the file is correct and that the offset has been + // incremented to that size. + struct stat s0; + EXPECT_THAT(fstat(fd1.get(), &s0), SyscallSucceeds()); + EXPECT_EQ(s0.st_size, kBufSize * 2); + EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), + SyscallSucceedsWithValue(kBufSize * 2)); + + // Then try to write to the second file and make sure the bytes are appended. + EXPECT_THAT(WriteFd(fd2.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + + // Check that the size of the file is correct and that the offset has been + // incremented to that size. + struct stat s1; + EXPECT_THAT(fstat(fd2.get(), &s1), SyscallSucceeds()); + EXPECT_EQ(s1.st_size, kBufSize * 3); + EXPECT_THAT(lseek(fd2.get(), 0, SEEK_CUR), + SyscallSucceedsWithValue(kBufSize * 3)); +} + +TEST_F(OpenTest, AppendConcurrentWrite) { + constexpr int kThreadCount = 5; + constexpr int kBytesPerThread = 10000; + std::unique_ptr<ScopedThread> threads[kThreadCount]; + + // In case of the uncached policy, we expect that a file system can be changed + // externally, so we create a new inode each time when we open a file and we + // can't guarantee that writes to files with O_APPEND will work correctly. + SKIP_IF(getenv("GVISOR_GOFER_UNCACHED")); + + EXPECT_THAT(truncate(test_file_name_.c_str(), 0), SyscallSucceeds()); + + std::string filename = test_file_name_; + DisableSave ds; // Too many syscalls. + // Start kThreadCount threads which will write concurrently into the same + // file. + for (int i = 0; i < kThreadCount; i++) { + threads[i] = absl::make_unique<ScopedThread>([filename]() { + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDWR | O_APPEND)); + + for (int j = 0; j < kBytesPerThread; j++) { + EXPECT_THAT(WriteFd(fd.get(), &j, 1), SyscallSucceedsWithValue(1)); + } + }); + } + for (int i = 0; i < kThreadCount; i++) { + threads[i]->Join(); + } + + // Check that the size of the file is correct. + struct stat st; + EXPECT_THAT(stat(test_file_name_.c_str(), &st), SyscallSucceeds()); + EXPECT_EQ(st.st_size, kThreadCount * kBytesPerThread); +} + +TEST_F(OpenTest, Truncate) { + { + // First write some data to the new file and close it. + FileDescriptor fd0 = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_WRONLY)); + std::vector<char> orig(10, 'a'); + EXPECT_THAT(WriteFd(fd0.get(), orig.data(), orig.size()), + SyscallSucceedsWithValue(orig.size())); + } + + // Then open with truncate and verify that offset is set to 0. + const FileDescriptor fd1 = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR | O_TRUNC)); + EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); + + // Then write less data to the file and ensure the old content is gone. + std::vector<char> want(5, 'b'); + EXPECT_THAT(WriteFd(fd1.get(), want.data(), want.size()), + SyscallSucceedsWithValue(want.size())); + + struct stat stat; + EXPECT_THAT(fstat(fd1.get(), &stat), SyscallSucceeds()); + EXPECT_EQ(stat.st_size, want.size()); + EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), + SyscallSucceedsWithValue(want.size())); + + // Read the data and ensure only the latest write is in the file. + std::vector<char> got(want.size() + 1, 'c'); + ASSERT_THAT(pread(fd1.get(), got.data(), got.size(), 0), + SyscallSucceedsWithValue(want.size())); + EXPECT_EQ(memcmp(want.data(), got.data(), want.size()), 0) + << "rbuf=" << got.data(); + EXPECT_EQ(got.back(), 'c'); // Last byte should not have been modified. +} + +TEST_F(OpenTest, NameTooLong) { + char buf[4097] = {}; + memset(buf, 'a', 4097); + EXPECT_THAT(open(buf, O_RDONLY), SyscallFailsWithErrno(ENAMETOOLONG)); +} + +TEST_F(OpenTest, DotsFromRoot) { + const FileDescriptor rootfd = + ASSERT_NO_ERRNO_AND_VALUE(Open("/", O_RDONLY | O_DIRECTORY)); + const FileDescriptor other_rootfd = + ASSERT_NO_ERRNO_AND_VALUE(OpenAt(rootfd.get(), "..", O_RDONLY)); +} + +TEST_F(OpenTest, DirectoryWritableFails) { + ASSERT_THAT(open(GetAbsoluteTestTmpdir().c_str(), O_RDWR), + SyscallFailsWithErrno(EISDIR)); +} + +TEST_F(OpenTest, FileNotDirectory) { + // Create a file and try to open it with O_DIRECTORY. + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + ASSERT_THAT(open(file.path().c_str(), O_RDONLY | O_DIRECTORY), + SyscallFailsWithErrno(ENOTDIR)); +} + +TEST_F(OpenTest, Null) { + char c = '\0'; + ASSERT_THAT(open(&c, O_RDONLY), SyscallFailsWithErrno(ENOENT)); +} + +// NOTE(b/119785738): While the man pages specify that this behavior should be +// undefined, Linux truncates the file on opening read only if we have write +// permission, so we will too. +TEST_F(OpenTest, CanTruncateReadOnly) { + const FileDescriptor fd1 = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY | O_TRUNC)); + + struct stat stat; + EXPECT_THAT(fstat(fd1.get(), &stat), SyscallSucceeds()); + EXPECT_EQ(stat.st_size, 0); +} + +// If we don't have read permission on the file, opening with +// O_TRUNC should fail. +TEST_F(OpenTest, CanTruncateReadOnlyNoWritePermission_NoRandomSave) { + // Drop capabilities that allow us to override file permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + + const DisableSave ds; // Permissions are dropped. + ASSERT_THAT(chmod(test_file_name_.c_str(), S_IRUSR | S_IRGRP), + SyscallSucceeds()); + + ASSERT_THAT(open(test_file_name_.c_str(), O_RDONLY | O_TRUNC), + SyscallFailsWithErrno(EACCES)); + + const FileDescriptor fd1 = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); + + struct stat stat; + EXPECT_THAT(fstat(fd1.get(), &stat), SyscallSucceeds()); + EXPECT_EQ(stat.st_size, test_data_.size()); +} + +// If we don't have read permission but have write permission, opening O_WRONLY +// and O_TRUNC should succeed. +TEST_F(OpenTest, CanTruncateWriteOnlyNoReadPermission_NoRandomSave) { + const DisableSave ds; // Permissions are dropped. + + EXPECT_THAT(fchmod(test_file_fd_.get(), S_IWUSR | S_IWGRP), + SyscallSucceeds()); + + const FileDescriptor fd1 = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_WRONLY | O_TRUNC)); + + EXPECT_THAT(fchmod(test_file_fd_.get(), S_IRUSR | S_IRGRP), + SyscallSucceeds()); + + const FileDescriptor fd2 = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); + + struct stat stat; + EXPECT_THAT(fstat(fd2.get(), &stat), SyscallSucceeds()); + EXPECT_EQ(stat.st_size, 0); +} + +TEST_F(OpenTest, CanTruncateWithStrangePermissions) { + 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(); + int fd; + // Create a file without user permissions. + EXPECT_THAT( // SAVE_BELOW + fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 055), + SyscallSucceeds()); + EXPECT_THAT(close(fd), SyscallSucceeds()); + + // Cannot open file because we are owner and have no permissions set. + EXPECT_THAT(open(path.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); + + // We *can* chmod the file, because we are the owner. + EXPECT_THAT(chmod(path.c_str(), 0755), SyscallSucceeds()); + + // Now we can open the file again. + EXPECT_THAT(fd = open(path.c_str(), O_RDWR), SyscallSucceeds()); + EXPECT_THAT(close(fd), SyscallSucceeds()); +} + +TEST_F(OpenTest, OpenNonDirectoryWithTrailingSlash) { + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const std::string bad_path = file.path() + "/"; + EXPECT_THAT(open(bad_path.c_str(), O_RDONLY), SyscallFailsWithErrno(ENOTDIR)); +} + +} // namespace + +} // namespace testing +} // namespace gvisor |