summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/partial_bad_buffer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/partial_bad_buffer.cc')
-rw-r--r--test/syscalls/linux/partial_bad_buffer.cc305
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