diff options
Diffstat (limited to 'test/syscalls/linux/partial_bad_buffer.cc')
-rw-r--r-- | test/syscalls/linux/partial_bad_buffer.cc | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/test/syscalls/linux/partial_bad_buffer.cc b/test/syscalls/linux/partial_bad_buffer.cc new file mode 100644 index 000000000..073a6b8c1 --- /dev/null +++ b/test/syscalls/linux/partial_bad_buffer.cc @@ -0,0 +1,305 @@ +// Copyright 2018 Google LLC +// +// 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 <sys/mman.h> +#include <sys/syscall.h> +#include <sys/uio.h> +#include <unistd.h> + +#include "gtest/gtest.h" +#include "test/util/fs_util.h" +#include "test/util/temp_path.h" +#include "test/util/test_util.h" + +using ::testing::Gt; + +namespace gvisor { +namespace testing { + +namespace { + +constexpr char kMessage[] = "hello world"; + +// PartialBadBufferTest checks the result of various IO syscalls when passed a +// buffer that does not have the space specified in the syscall (most of it is +// PROT_NONE). Linux is annoyingly inconsistent among different syscalls, so we +// test all of them. +class PartialBadBufferTest : public ::testing::Test { + protected: + void SetUp() override { + // Create and open a directory for getdents cases. + directory_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + ASSERT_THAT( + directory_fd_ = open(directory_.path().c_str(), O_RDONLY | O_DIRECTORY), + SyscallSucceeds()); + + // Create and open a normal file, placing it in the directory + // so the getdents cases have some dirents. + name_ = JoinPath(directory_.path(), "a"); + ASSERT_THAT(fd_ = open(name_.c_str(), O_RDWR | O_CREAT, 0644), + SyscallSucceeds()); + + // Write some initial data. + size_t size = sizeof(kMessage) - 1; + EXPECT_THAT(WriteFd(fd_, &kMessage, size), SyscallSucceedsWithValue(size)); + + ASSERT_THAT(lseek(fd_, 0, SEEK_SET), SyscallSucceeds()); + + addr_ = mmap(0, 2 * kPageSize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(addr_, MAP_FAILED); + char* buf = reinterpret_cast<char*>(addr_); + + // Guard page for our read to run into. + ASSERT_THAT(mprotect(reinterpret_cast<void*>(buf + kPageSize), kPageSize, + PROT_NONE), + SyscallSucceeds()); + + // Leave only one free byte in the buffer. + bad_buffer_ = buf + kPageSize - 1; + } + + void TearDown() override { + EXPECT_THAT(munmap(addr_, 2 * kPageSize), SyscallSucceeds()) << addr_; + EXPECT_THAT(close(fd_), SyscallSucceeds()); + EXPECT_THAT(unlink(name_.c_str()), SyscallSucceeds()); + EXPECT_THAT(close(directory_fd_), SyscallSucceeds()); + } + + // Return buffer with n bytes of free space. + // N.B. this is the same buffer used to back bad_buffer_. + char* FreeBytes(size_t n) { + TEST_CHECK(n <= static_cast<size_t>(4096)); + return reinterpret_cast<char*>(addr_) + kPageSize - n; + } + + std::string name_; + int fd_; + TempPath directory_; + int directory_fd_; + void* addr_; + char* bad_buffer_; +}; + +// We do both "big" and "small" tests to try to hit the "zero copy" and +// non-"zero copy" paths, which have different code paths for handling faults. + +TEST_F(PartialBadBufferTest, ReadBig) { + EXPECT_THAT(RetryEINTR(read)(fd_, bad_buffer_, kPageSize), + SyscallSucceedsWithValue(1)); + EXPECT_EQ('h', bad_buffer_[0]); +} + +TEST_F(PartialBadBufferTest, ReadSmall) { + EXPECT_THAT(RetryEINTR(read)(fd_, bad_buffer_, 10), + SyscallSucceedsWithValue(1)); + EXPECT_EQ('h', bad_buffer_[0]); +} + +TEST_F(PartialBadBufferTest, PreadBig) { + EXPECT_THAT(RetryEINTR(pread)(fd_, bad_buffer_, kPageSize, 0), + SyscallSucceedsWithValue(1)); + EXPECT_EQ('h', bad_buffer_[0]); +} + +TEST_F(PartialBadBufferTest, PreadSmall) { + EXPECT_THAT(RetryEINTR(pread)(fd_, bad_buffer_, 10, 0), + SyscallSucceedsWithValue(1)); + EXPECT_EQ('h', bad_buffer_[0]); +} + +TEST_F(PartialBadBufferTest, ReadvBig) { + struct iovec vec; + vec.iov_base = bad_buffer_; + vec.iov_len = kPageSize; + + EXPECT_THAT(RetryEINTR(readv)(fd_, &vec, 1), SyscallSucceedsWithValue(1)); + EXPECT_EQ('h', bad_buffer_[0]); +} + +TEST_F(PartialBadBufferTest, ReadvSmall) { + struct iovec vec; + vec.iov_base = bad_buffer_; + vec.iov_len = 10; + + EXPECT_THAT(RetryEINTR(readv)(fd_, &vec, 1), SyscallSucceedsWithValue(1)); + EXPECT_EQ('h', bad_buffer_[0]); +} + +TEST_F(PartialBadBufferTest, PreadvBig) { + struct iovec vec; + vec.iov_base = bad_buffer_; + vec.iov_len = kPageSize; + + EXPECT_THAT(RetryEINTR(preadv)(fd_, &vec, 1, 0), SyscallSucceedsWithValue(1)); + EXPECT_EQ('h', bad_buffer_[0]); +} + +TEST_F(PartialBadBufferTest, PreadvSmall) { + struct iovec vec; + vec.iov_base = bad_buffer_; + vec.iov_len = 10; + + EXPECT_THAT(RetryEINTR(preadv)(fd_, &vec, 1, 0), SyscallSucceedsWithValue(1)); + EXPECT_EQ('h', bad_buffer_[0]); +} + +TEST_F(PartialBadBufferTest, WriteBig) { + // FIXME: The sentry write syscalls will return immediately + // if Access returns an error, but Access may not return an error + // and the sentry will instead perform a partial write. + SKIP_IF(IsRunningOnGvisor()); + + EXPECT_THAT(RetryEINTR(write)(fd_, bad_buffer_, kPageSize), + SyscallFailsWithErrno(EFAULT)); +} + +TEST_F(PartialBadBufferTest, WriteSmall) { + // FIXME: The sentry write syscalls will return immediately + // if Access returns an error, but Access may not return an error + // and the sentry will instead perform a partial write. + SKIP_IF(IsRunningOnGvisor()); + + EXPECT_THAT(RetryEINTR(write)(fd_, bad_buffer_, 10), + SyscallFailsWithErrno(EFAULT)); +} + +TEST_F(PartialBadBufferTest, PwriteBig) { + // FIXME: The sentry write syscalls will return immediately + // if Access returns an error, but Access may not return an error + // and the sentry will instead perform a partial write. + SKIP_IF(IsRunningOnGvisor()); + + EXPECT_THAT(RetryEINTR(pwrite)(fd_, bad_buffer_, kPageSize, 0), + SyscallFailsWithErrno(EFAULT)); +} + +TEST_F(PartialBadBufferTest, PwriteSmall) { + // FIXME: The sentry write syscalls will return immediately + // if Access returns an error, but Access may not return an error + // and the sentry will instead perform a partial write. + SKIP_IF(IsRunningOnGvisor()); + + EXPECT_THAT(RetryEINTR(pwrite)(fd_, bad_buffer_, 10, 0), + SyscallFailsWithErrno(EFAULT)); +} + +TEST_F(PartialBadBufferTest, WritevBig) { + // FIXME: The sentry write syscalls will return immediately + // if Access returns an error, but Access may not return an error + // and the sentry will instead perform a partial write. + SKIP_IF(IsRunningOnGvisor()); + + struct iovec vec; + vec.iov_base = bad_buffer_; + vec.iov_len = kPageSize; + + EXPECT_THAT(RetryEINTR(writev)(fd_, &vec, 1), SyscallFailsWithErrno(EFAULT)); +} + +TEST_F(PartialBadBufferTest, WritevSmall) { + // FIXME: The sentry write syscalls will return immediately + // if Access returns an error, but Access may not return an error + // and the sentry will instead perform a partial write. + SKIP_IF(IsRunningOnGvisor()); + + struct iovec vec; + vec.iov_base = bad_buffer_; + vec.iov_len = 10; + + EXPECT_THAT(RetryEINTR(writev)(fd_, &vec, 1), SyscallFailsWithErrno(EFAULT)); +} + +TEST_F(PartialBadBufferTest, PwritevBig) { + // FIXME: The sentry write syscalls will return immediately + // if Access returns an error, but Access may not return an error + // and the sentry will instead perform a partial write. + SKIP_IF(IsRunningOnGvisor()); + + struct iovec vec; + vec.iov_base = bad_buffer_; + vec.iov_len = kPageSize; + + EXPECT_THAT(RetryEINTR(pwritev)(fd_, &vec, 1, 0), + SyscallFailsWithErrno(EFAULT)); +} + +TEST_F(PartialBadBufferTest, PwritevSmall) { + // FIXME: The sentry write syscalls will return immediately + // if Access returns an error, but Access may not return an error + // and the sentry will instead perform a partial write. + SKIP_IF(IsRunningOnGvisor()); + + struct iovec vec; + vec.iov_base = bad_buffer_; + vec.iov_len = 10; + + EXPECT_THAT(RetryEINTR(pwritev)(fd_, &vec, 1, 0), + SyscallFailsWithErrno(EFAULT)); +} + +// getdents returns EFAULT when the you claim the buffer is large enough, but +// it actually isn't. +TEST_F(PartialBadBufferTest, GetdentsBig) { + EXPECT_THAT(RetryEINTR(syscall)(SYS_getdents64, directory_fd_, bad_buffer_, + kPageSize), + SyscallFailsWithErrno(EFAULT)); +} + +// getdents returns EINVAL when the you claim the buffer is too small. +TEST_F(PartialBadBufferTest, GetdentsSmall) { + EXPECT_THAT( + RetryEINTR(syscall)(SYS_getdents64, directory_fd_, bad_buffer_, 10), + SyscallFailsWithErrno(EINVAL)); +} + +// getdents will write entries into a buffer if there is space before it faults. +TEST_F(PartialBadBufferTest, GetdentsOneEntry) { + // 30 bytes is enough for one (small) entry. + char* buf = FreeBytes(30); + + EXPECT_THAT( + RetryEINTR(syscall)(SYS_getdents64, directory_fd_, buf, kPageSize), + SyscallSucceedsWithValue(Gt(0))); +} + +// Verify that when write returns EFAULT the kernel hasn't silently written +// the initial valid bytes. +TEST_F(PartialBadBufferTest, WriteEfaultIsntPartial) { + // FIXME: The sentry write syscalls will return immediately + // if Access returns an error, but Access may not return an error + // and the sentry will instead perform a partial write. + SKIP_IF(IsRunningOnGvisor()); + + bad_buffer_[0] = 'A'; + EXPECT_THAT(RetryEINTR(write)(fd_, bad_buffer_, 10), + SyscallFailsWithErrno(EFAULT)); + + size_t size = 255; + char buf[255]; + memset(buf, 0, size); + + EXPECT_THAT(RetryEINTR(pread)(fd_, buf, size, 0), + SyscallSucceedsWithValue(sizeof(kMessage) - 1)); + + // 'A' has not been written. + EXPECT_STREQ(buf, kMessage); +} + +} // namespace + +} // namespace testing +} // namespace gvisor |