423 lines
15 KiB
C++
423 lines
15 KiB
C++
// 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);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
} // namespace testing
|
|
} // namespace gvisor
|