Files

486 lines
16 KiB
C++
Raw Permalink Normal View History

2022-08-08 12:54:28 -07:00
// Copyright 2022 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 <asm-generic/errno-base.h>
#include <bits/types/siginfo_t.h>
2022-08-08 12:54:28 -07:00
#include <bits/types/struct_iovec.h>
#include <errno.h>
#include <linux/futex.h>
2022-08-08 12:54:28 -07:00
#include <string.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <algorithm>
#include <atomic>
2022-08-08 12:54:28 -07:00
#include <climits>
#include <csignal>
#include <cstddef>
#include <cstdint>
2022-08-08 12:54:28 -07:00
#include <functional>
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/cleanup/cleanup.h"
2022-08-08 12:54:28 -07:00
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "test/util/cleanup.h"
2022-11-17 13:48:19 -08:00
#include "test/util/logging.h"
#include "test/util/memory_util.h"
2022-08-08 12:54:28 -07:00
#include "test/util/posix_error.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
2022-08-08 12:54:28 -07:00
namespace gvisor {
namespace testing {
namespace {
class TestIovecs {
public:
TestIovecs(std::vector<std::string> data) {
data_.resize(data.size());
iovecs_.resize(data.size());
2023-03-09 13:13:04 -08:00
for (size_t i = 0; i < data.size(); ++i) {
2022-08-08 12:54:28 -07:00
data_[i] = data[i];
bytes_ += data[i].size();
iovecs_[i].iov_len = data_[i].size();
iovecs_[i].iov_base = data_[i].data();
2022-08-08 12:54:28 -07:00
}
}
void erase() {
for (size_t i = 0; i < data_.size(); i++) {
data_[i].clear();
2022-08-08 12:54:28 -07:00
}
}
// Backing data that will be read/written.
2022-08-08 12:54:28 -07:00
std::vector<std::string> data_;
// Total size of data_.
ssize_t bytes_ = 0;
// Iovec structs that point into data_
std::vector<iovec> iovecs_;
2022-08-08 12:54:28 -07:00
};
// bytes_match checks that the two TestIovecs are at least min_bytes in length,
// and that they agree in the first min_bytes.
bool bytes_match(TestIovecs first_iov, TestIovecs second_iov,
size_t min_bytes) {
auto first = absl::StrJoin(first_iov.data_, "");
if (first.size() < min_bytes) {
std::cout << "First buffer smaller than min_bytes: " << min_bytes
<< " buffer: " << first << std::endl;
return false;
}
first = first.substr(0, min_bytes);
auto second = absl::StrJoin(second_iov.data_, "");
if (second.size() < min_bytes) {
std::cout << "First buffer smaller than min_bytes: " << min_bytes
<< " buffer: " << second << std::endl;
return false;
}
second = second.substr(0, min_bytes);
if (first != second) {
std::cout << "Mismatch buffers:\n first: " << first
<< "\n second: " << second << std::endl;
return false;
}
return true;
}
2022-08-08 12:54:28 -07:00
struct ProcessVMTestCase {
std::string test_name;
std::vector<std::string> local_data;
std::vector<std::string> remote_data;
};
using ProcessVMTest = ::testing::TestWithParam<ProcessVMTestCase>;
2023-11-08 17:56:49 -08:00
std::string getTestBuffer(std::string pattern, size_t size) {
std::string s;
auto pattern_length = pattern.length();
s.reserve(size);
while (s.length() + pattern_length < size) {
s += pattern;
}
s += pattern.substr(0, size - s.length());
return s;
}
2022-08-08 12:54:28 -07:00
INSTANTIATE_TEST_SUITE_P(
ProcessVMTests, ProcessVMTest,
::testing::ValuesIn<ProcessVMTestCase>(
{{"BothEmpty" /*test name*/,
{""} /*local buffer*/,
{""} /*remote buffer*/},
{"EmptyLocal", {""}, {"All too easy."}},
{"EmptyRemote", {"Impressive. Most impressive."}, {""}},
{"SingleChar", {"l"}, {"r"}},
{"LargerRemoteBuffer",
{"OK, I'll try"},
{"No!", "Try not", "Do...or do not", "There is no try."}},
{"LargerLocalBuffer",
{"Look!", "The cave is collapsing!"},
{"This is no cave."}},
{"BothWithMultipleIovecs",
{"Obi-wan never told you what happened to your father.",
"He told me enough...he told me you killed him."},
{"No...I am your father.", "No. No.", "That's not true.",
2023-11-08 17:56:49 -08:00
"That's impossible!"}},
{
"LargeBuffer",
{
getTestBuffer(
"Train yourself to let go of everything you fear to lose.",
32 << 20),
"Hello there!",
},
{
"Do. Or do not. There is no try.",
getTestBuffer("The greatest teacher, failure is.", 32 << 20),
},
}}),
2022-08-08 12:54:28 -07:00
[](const ::testing::TestParamInfo<ProcessVMTest::ParamType>& info) {
return info.param.test_name;
});
// TestReadvSameProcess calls process_vm_readv in the same process with various
// local/remote buffers.
TEST_P(ProcessVMTest, TestReadvSameProcess) {
TestIovecs local(GetParam().local_data);
TestIovecs remote(GetParam().remote_data);
auto want_size = std::min(remote.bytes_, local.bytes_);
EXPECT_THAT(
process_vm_readv(getpid(), local.iovecs_.data(), local.iovecs_.size(),
remote.iovecs_.data(), remote.iovecs_.size(), 0),
SyscallSucceedsWithValue(want_size));
EXPECT_TRUE(bytes_match(local, remote, want_size));
}
// TestReadvSameProcessDifferentThread calls process_vm_readv in the same
// process, but with a different (non-leader) remote thread, with various
// local/remote buffers.
TEST_P(ProcessVMTest, TestReadvSameProcessDifferentThread) {
TestIovecs local(GetParam().local_data);
TestIovecs remote(GetParam().remote_data);
std::atomic<std::int32_t> sibling_tid{0};
std::atomic<std::uint32_t> sibling_exit{0};
ScopedThread t([&] {
sibling_tid.store(gettid());
syscall(SYS_futex, &sibling_tid, FUTEX_WAKE, 1);
while (sibling_exit.load() == 0) {
syscall(SYS_futex, &sibling_exit, FUTEX_WAIT, 0, nullptr);
}
});
Cleanup cleanup_t([&] {
sibling_exit.store(1);
syscall(SYS_futex, &sibling_exit, FUTEX_WAKE, 1);
});
while (sibling_tid.load() == 0) {
syscall(SYS_futex, &sibling_tid, FUTEX_WAIT, 0, nullptr);
}
auto want_size = std::min(remote.bytes_, local.bytes_);
EXPECT_THAT(process_vm_readv(sibling_tid.load(), local.iovecs_.data(),
local.iovecs_.size(), remote.iovecs_.data(),
remote.iovecs_.size(), 0),
SyscallSucceedsWithValue(want_size));
EXPECT_TRUE(bytes_match(local, remote, want_size));
}
// TestReadvSubProcess reads data from a forked child process.
TEST_P(ProcessVMTest, TestReadvSubProcess) {
TestIovecs local = TestIovecs(GetParam().local_data);
TestIovecs remote = TestIovecs(GetParam().remote_data);
pid_t pid = fork();
TEST_CHECK_SUCCESS(pid);
if (pid == 0) {
// Child. This is the "remote" process.
// Wait for parent to read data.
sleep(10); // NOLINT - SleepFor is not async-signal-safe.
_exit(0);
}
auto cleanup = absl::MakeCleanup([&] { kill(pid, SIGKILL); });
// Erase the string data in parent's copy of remote, to make sure we are
// reading data from the child.
remote.erase();
// Compare against actual remote (not what we sent to the child, since we
// emptied it).
TestIovecs want_remote = TestIovecs(GetParam().remote_data);
auto want_size = std::min(local.bytes_, want_remote.bytes_);
ASSERT_THAT(process_vm_readv(pid, local.iovecs_.data(), local.iovecs_.size(),
remote.iovecs_.data(), remote.iovecs_.size(), 0),
SyscallSucceedsWithValue(want_size));
EXPECT_TRUE(bytes_match(local, want_remote, want_size));
}
// TestWritevSameProcess calls process_vm_readv in the same process with various
// local/remote buffers.
TEST_P(ProcessVMTest, TestWritevSameProcess) {
TestIovecs local(GetParam().local_data);
TestIovecs remote(GetParam().remote_data);
auto want_size = std::min(remote.bytes_, local.bytes_);
EXPECT_THAT(
process_vm_writev(getpid(), local.iovecs_.data(), local.iovecs_.size(),
remote.iovecs_.data(), remote.iovecs_.size(), 0),
SyscallSucceedsWithValue(want_size));
EXPECT_TRUE(bytes_match(local, remote, want_size));
}
// TestWritevSubProcess writes data to a forked child process.
TEST_P(ProcessVMTest, TestWritevSubProcess) {
TestIovecs local(GetParam().local_data);
TestIovecs remote(GetParam().remote_data);
// A pipe is used to wait on the write call from the parent, so we can block
// asserting until the write is complete.
int pipefd[2];
ASSERT_THAT(pipe(pipefd), SyscallSucceeds());
pid_t pid = fork();
TEST_CHECK_SUCCESS(pid);
if (pid == 0) {
// Child. This is the "remote" process.
close(pipefd[1]);
// Wait on pipefd. It will be closed after the parent has written.
char buf;
TEST_CHECK_SUCCESS(read(pipefd[0], &buf, sizeof(buf)));
close(pipefd[0]);
// Check the data. This will exit non-0 in the case of a mismatch.
auto want_size = std::min(local.bytes_, remote.bytes_);
TEST_CHECK(bytes_match(local, remote, want_size));
_exit(0);
}
auto cleanup = absl::MakeCleanup([&] {
int status = 0;
EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceeds());
EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
});
// Write to the remote.
auto want_size = std::min(local.bytes_, remote.bytes_);
ASSERT_THAT(
process_vm_writev(pid, local.iovecs_.data(), local.iovecs_.size(),
remote.iovecs_.data(), remote.iovecs_.size(), 0),
SyscallSucceedsWithValue(want_size));
// Now that we've written, close pipefd to signal the child can continue.
close(pipefd[1]);
}
2022-08-08 12:54:28 -07:00
TEST(ProcessVMInvalidTest, NonZeroFlags) {
2023-03-09 13:13:04 -08:00
struct iovec iov = {};
2022-08-08 12:54:28 -07:00
// Flags should be 0.
EXPECT_THAT(process_vm_readv(0, &iov, 1, &iov, 1, 10),
SyscallFailsWithErrno(EINVAL));
EXPECT_THAT(process_vm_writev(0, &iov, 1, &iov, 1, 10),
SyscallFailsWithErrno(EINVAL));
}
TEST(ProcessVMInvalidTest, NullLocalIovec) {
2023-03-09 13:13:04 -08:00
struct iovec iov = {};
2022-08-08 12:54:28 -07:00
pid_t child = fork();
if (child == 0) {
sleep(10); // NOLINT - SleepFor is not async-signal-safe.
_exit(0);
2022-08-08 12:54:28 -07:00
}
EXPECT_THAT(process_vm_readv(child, nullptr, 1, &iov, 1, 0),
SyscallFailsWithErrno(EFAULT));
EXPECT_THAT(process_vm_writev(child, nullptr, 1, &iov, 1, 0),
SyscallFailsWithErrno(EFAULT));
EXPECT_THAT(kill(child, SIGKILL), SyscallSucceeds());
EXPECT_THAT(waitpid(child, 0, 0), SyscallSucceeds());
}
TEST(ProcessVMInvalidTest, NULLRemoteIovec) {
std::string contents = "3263827";
struct iovec iov;
iov.iov_base = contents.data();
iov.iov_len = contents.size();
2022-08-08 12:54:28 -07:00
pid_t pid = fork();
TEST_CHECK_SUCCESS(pid);
if (pid == 0) {
sleep(10); // NOLINT - SleepFor is not async-signal-safe.
_exit(0);
}
auto cleanup = absl::MakeCleanup([&] { kill(pid, SIGKILL); });
2022-08-08 12:54:28 -07:00
EXPECT_THAT(process_vm_readv(pid, &iov, contents.length(), nullptr, 1, 0),
SyscallFailsWithErrno(::testing::AnyOf(EFAULT, EINVAL)));
EXPECT_THAT(process_vm_writev(pid, &iov, contents.length(), nullptr, 1, 0),
SyscallFailsWithErrno(::testing::AnyOf(EFAULT, EINVAL)));
2022-08-08 12:54:28 -07:00
}
TEST(ProcessVMInvalidTest, ProcessNoExist) {
2024-12-20 13:35:12 -08:00
std::string contents = "3263827";
2022-08-08 12:54:28 -07:00
struct iovec iov;
2024-12-20 13:35:12 -08:00
iov.iov_base = contents.data();
iov.iov_len = contents.size();
2022-08-08 12:54:28 -07:00
EXPECT_THAT(process_vm_readv(-1, &iov, 1, &iov, 1, 0),
2024-12-20 13:35:12 -08:00
SyscallFailsWithErrno(ESRCH));
2022-08-08 12:54:28 -07:00
EXPECT_THAT(process_vm_writev(-1, &iov, 1, &iov, 1, 0),
2024-12-20 13:35:12 -08:00
SyscallFailsWithErrno(ESRCH));
2022-08-08 12:54:28 -07:00
}
2022-11-17 13:48:19 -08:00
TEST(ProcessVMInvalidTest, GreaterThanIOV_MAX) {
2022-08-08 12:54:28 -07:00
std::string contents = "3263827";
struct iovec iov;
iov.iov_base = contents.data();
iov.iov_len = contents.size();
2022-08-08 12:54:28 -07:00
pid_t pid = fork();
TEST_CHECK_SUCCESS(pid);
if (pid == 0) {
sleep(10); // NOLINT - SleepFor is not async-signal-safe.
_exit(0);
}
auto cleanup = absl::MakeCleanup([&] { kill(pid, SIGKILL); });
2022-08-08 12:54:28 -07:00
EXPECT_THAT(process_vm_readv(pid, &iov, 1, &iov, IOV_MAX + 1, 0),
SyscallFailsWithErrno(EINVAL));
EXPECT_THAT(process_vm_writev(pid, &iov, 1, &iov, IOV_MAX + 1, 0),
SyscallFailsWithErrno(EINVAL));
2022-08-08 12:54:28 -07:00
}
TEST(ProcessVMInvalidTest, PartialReadWrite) {
std::string iov_content_1 = "1138";
std::string iov_content_2 = "3720";
struct iovec iov[2];
iov[0].iov_base = iov_content_1.data();
iov[0].iov_len = iov_content_1.size();
iov[1].iov_base = iov_content_2.data();
iov[1].iov_len = iov_content_2.size();
std::string iov_corrupted_content_1 = iov_content_1;
struct iovec corrupted_iov[2];
corrupted_iov[0].iov_base = iov_corrupted_content_1.data();
corrupted_iov[0].iov_len = iov_corrupted_content_1.size();
corrupted_iov[1].iov_base = (void*)0xDEADBEEF;
corrupted_iov[1].iov_len = 42;
EXPECT_THAT(
RetryEINTR(process_vm_writev)(getpid(), iov, 2, corrupted_iov, 2, 0),
SyscallSucceedsWithValue(iov_content_1.size()));
EXPECT_THAT(
RetryEINTR(process_vm_readv)(getpid(), corrupted_iov, 2, iov, 2, 0),
SyscallSucceedsWithValue(iov_content_1.size()));
EXPECT_THAT(
RetryEINTR(process_vm_writev)(getpid(), corrupted_iov, 2, iov, 2, 0),
SyscallSucceedsWithValue(iov_content_1.size()));
EXPECT_THAT(
RetryEINTR(process_vm_readv)(getpid(), iov, 2, corrupted_iov, 2, 0),
SyscallSucceedsWithValue(iov_content_1.size()));
}
TEST(ProcessVMInvalidTest, AccessInvalidMemoryFailsWithEINVAL) {
auto const mapping =
ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE));
struct iovec iov_none, iov_valid;
char buf[128];
iov_valid.iov_base = buf;
iov_valid.iov_len = sizeof(buf);
iov_none.iov_base = mapping.ptr();
iov_none.iov_len = mapping.len();
EXPECT_THAT(
RetryEINTR(process_vm_writev)(getpid(), &iov_none, 1, &iov_valid, 1, 0),
SyscallFailsWithErrno(EFAULT));
EXPECT_THAT(
RetryEINTR(process_vm_writev)(getpid(), &iov_valid, 1, &iov_none, 1, 0),
SyscallFailsWithErrno(EFAULT));
}
TEST(ProcessVMTest, WriteToZombie) {
char* data = {0};
pid_t child;
ASSERT_THAT(child = fork(), SyscallSucceeds());
if (child == 0) {
_exit(0);
}
siginfo_t siginfo = {};
ASSERT_THAT(RetryEINTR(waitid)(P_PID, child, &siginfo, WEXITED | WNOWAIT),
SyscallSucceeds());
struct iovec iov;
iov.iov_base = data;
iov.iov_len = sizeof(data);
ASSERT_THAT(process_vm_writev(child, &iov, 1, &iov, 1, 0),
SyscallFailsWithErrno(ESRCH));
}
// TestReadvNull calls process_vm_readv with null iovecs and checks that they
// succeed but return 0;
TEST(ProcessVMTest, TestReadvNull) {
TestIovecs local(std::vector<std::string>{"foo"});
TestIovecs remote(std::vector<std::string>{"bar"});
// Pass 0 for local.
EXPECT_THAT(process_vm_readv(getpid(), 0, 0, remote.iovecs_.data(),
remote.iovecs_.size(), 0),
SyscallSucceedsWithValue(0));
// Pass 0 for remote.
EXPECT_THAT(process_vm_readv(getpid(), local.iovecs_.data(),
local.iovecs_.size(), 0, 0, 0),
SyscallSucceedsWithValue(0));
}
// TestWritevNull calls process_vm_writev with null iovecs and checks that they
// succeed but return 0;
TEST(ProcessVMTest, TestWritevNull) {
TestIovecs local(std::vector<std::string>{"foo"});
TestIovecs remote(std::vector<std::string>{"bar"});
// Pass 0 for local.
EXPECT_THAT(process_vm_writev(getpid(), 0, 0, remote.iovecs_.data(),
remote.iovecs_.size(), 0),
SyscallSucceedsWithValue(0));
// Pass 0 for remote.
EXPECT_THAT(process_vm_writev(getpid(), local.iovecs_.data(),
local.iovecs_.size(), 0, 0, 0),
SyscallSucceedsWithValue(0));
}
2022-08-08 12:54:28 -07:00
} // namespace
} // namespace testing
} // namespace gvisor