diff options
Diffstat (limited to 'test/syscalls/linux/fork.cc')
-rw-r--r-- | test/syscalls/linux/fork.cc | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/test/syscalls/linux/fork.cc b/test/syscalls/linux/fork.cc new file mode 100644 index 000000000..1bff5e50f --- /dev/null +++ b/test/syscalls/linux/fork.cc @@ -0,0 +1,413 @@ +// 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 <sched.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <atomic> + +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "test/util/logging.h" +#include "test/util/test_util.h" +#include "test/util/thread_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +using ::testing::Ge; + +class ForkTest : public ::testing::Test { + protected: + // SetUp creates a populated, open file. + void SetUp() override { + // Make a shared mapping. + shared_ = reinterpret_cast<char*>(mmap(0, kPageSize, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + ASSERT_NE(reinterpret_cast<void*>(shared_), MAP_FAILED); + + // Make a private mapping. + private_ = + reinterpret_cast<char*>(mmap(0, kPageSize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_NE(reinterpret_cast<void*>(private_), MAP_FAILED); + + // Make a pipe. + ASSERT_THAT(pipe(pipes_), SyscallSucceeds()); + } + + // TearDown frees associated resources. + void TearDown() override { + EXPECT_THAT(munmap(shared_, kPageSize), SyscallSucceeds()); + EXPECT_THAT(munmap(private_, kPageSize), SyscallSucceeds()); + EXPECT_THAT(close(pipes_[0]), SyscallSucceeds()); + EXPECT_THAT(close(pipes_[1]), SyscallSucceeds()); + } + + // Fork executes a clone system call. + pid_t Fork() { + pid_t pid = fork(); + MaybeSave(); + TEST_PCHECK_MSG(pid >= 0, "fork failed"); + return pid; + } + + // Wait waits for the given pid and returns the exit status. If the child was + // killed by a signal or an error occurs, then 256+signal is returned. + int Wait(pid_t pid) { + int status; + while (true) { + int rval = wait4(pid, &status, 0, NULL); + if (rval < 0) { + return rval; + } + if (rval != pid) { + continue; + } + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + if (WIFSIGNALED(status)) { + return 256 + WTERMSIG(status); + } + } + } + + // Exit exits the proccess. + void Exit(int code) { + _exit(code); + + // Should never reach here. Since the exit above failed, we really don't + // have much in the way of options to indicate failure. So we just try to + // log an assertion failure to the logs. The parent process will likely + // fail anyways if exit is not working. + TEST_CHECK_MSG(false, "_exit returned"); + } + + // ReadByte reads a byte from the shared pipe. + char ReadByte() { + char val = -1; + TEST_PCHECK(ReadFd(pipes_[0], &val, 1) == 1); + MaybeSave(); + return val; + } + + // WriteByte writes a byte from the shared pipe. + void WriteByte(char val) { + TEST_PCHECK(WriteFd(pipes_[1], &val, 1) == 1); + MaybeSave(); + } + + // Shared pipe. + int pipes_[2]; + + // Shared mapping (one page). + char* shared_; + + // Private mapping (one page). + char* private_; +}; + +TEST_F(ForkTest, Simple) { + pid_t child = Fork(); + if (child == 0) { + Exit(0); + } + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); +} + +TEST_F(ForkTest, ExitCode) { + pid_t child = Fork(); + if (child == 0) { + Exit(123); + } + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(123)); + child = Fork(); + if (child == 0) { + Exit(1); + } + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(1)); +} + +TEST_F(ForkTest, Multi) { + pid_t child1 = Fork(); + if (child1 == 0) { + Exit(0); + } + pid_t child2 = Fork(); + if (child2 == 0) { + Exit(1); + } + EXPECT_THAT(Wait(child1), SyscallSucceedsWithValue(0)); + EXPECT_THAT(Wait(child2), SyscallSucceedsWithValue(1)); +} + +TEST_F(ForkTest, Pipe) { + pid_t child = Fork(); + if (child == 0) { + WriteByte(1); + Exit(0); + } + EXPECT_EQ(ReadByte(), 1); + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); +} + +TEST_F(ForkTest, SharedMapping) { + pid_t child = Fork(); + if (child == 0) { + // Wait for the parent. + ReadByte(); + if (shared_[0] == 1) { + Exit(0); + } + // Failed. + Exit(1); + } + // Change the mapping. + ASSERT_EQ(shared_[0], 0); + shared_[0] = 1; + // Unblock the child. + WriteByte(0); + // Did it work? + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); +} + +TEST_F(ForkTest, PrivateMapping) { + pid_t child = Fork(); + if (child == 0) { + // Wait for the parent. + ReadByte(); + if (private_[0] == 0) { + Exit(0); + } + // Failed. + Exit(1); + } + // Change the mapping. + ASSERT_EQ(private_[0], 0); + private_[0] = 1; + // Unblock the child. + WriteByte(0); + // Did it work? + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); +} + +// Test that cpuid works after a fork. +TEST_F(ForkTest, Cpuid) { + pid_t child = Fork(); + + // We should be able to determine the CPU vendor. + ASSERT_NE(GetCPUVendor(), CPUVendor::kUnknownVendor); + + if (child == 0) { + Exit(0); + } + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); +} + +TEST_F(ForkTest, Mmap) { + pid_t child = Fork(); + + if (child == 0) { + void* addr = + mmap(0, kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + MaybeSave(); + Exit(addr == MAP_FAILED); + } + + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); +} + +static volatile int alarmed = 0; + +void AlarmHandler(int sig, siginfo_t* info, void* context) { alarmed = 1; } + +TEST_F(ForkTest, Alarm) { + // Setup an alarm handler. + struct sigaction sa; + sa.sa_sigaction = AlarmHandler; + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + EXPECT_THAT(sigaction(SIGALRM, &sa, nullptr), SyscallSucceeds()); + + pid_t child = Fork(); + + if (child == 0) { + alarm(1); + sleep(3); + if (!alarmed) { + Exit(1); + } + Exit(0); + } + + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); + EXPECT_EQ(0, alarmed); +} + +// Child cannot affect parent private memory. +TEST_F(ForkTest, PrivateMemory) { + std::atomic<uint32_t> local(0); + + pid_t child1 = Fork(); + if (child1 == 0) { + local++; + + pid_t child2 = Fork(); + if (child2 == 0) { + local++; + + TEST_CHECK(local.load() == 2); + + Exit(0); + } + + TEST_PCHECK(Wait(child2) == 0); + TEST_CHECK(local.load() == 1); + Exit(0); + } + + EXPECT_THAT(Wait(child1), SyscallSucceedsWithValue(0)); + EXPECT_EQ(0, local.load()); +} + +// Kernel-accessed buffers should remain coherent across COW. +TEST_F(ForkTest, COWSegment) { + constexpr int kBufSize = 1024; + char* read_buf = private_; + char* touch = private_ + kPageSize / 2; + + std::string contents(kBufSize, 'a'); + + ScopedThread t([&] { + // Wait to be sure the parent is blocked in read. + absl::SleepFor(absl::Seconds(3)); + + // Fork to mark private pages for COW. + // + // Use fork directly rather than the Fork wrapper to skip the multi-threaded + // check, and limit the child to async-signal-safe functions: + // + // "After a fork() in a multithreaded program, the child can safely call + // only async-signal-safe functions (see signal(7)) until such time as it + // calls execve(2)." + // + // Skip ASSERT in the child, as it isn't async-signal-safe. + pid_t child = fork(); + if (child == 0) { + // Wait to be sure parent touched memory. + sleep(3); + Exit(0); + } + + // Check success only in the parent. + ASSERT_THAT(child, SyscallSucceedsWithValue(Ge(0))); + + // Trigger COW on private page. + *touch = 42; + + // Write to pipe. Parent should still be able to read this. + EXPECT_THAT(WriteFd(pipes_[1], contents.c_str(), kBufSize), + SyscallSucceedsWithValue(kBufSize)); + + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); + }); + + EXPECT_THAT(ReadFd(pipes_[0], read_buf, kBufSize), + SyscallSucceedsWithValue(kBufSize)); + EXPECT_STREQ(contents.c_str(), read_buf); +} + +TEST_F(ForkTest, SigAltStack) { + std::vector<char> stack_mem(SIGSTKSZ); + stack_t stack = {}; + stack.ss_size = SIGSTKSZ; + stack.ss_sp = stack_mem.data(); + ASSERT_THAT(sigaltstack(&stack, nullptr), SyscallSucceeds()); + + pid_t child = Fork(); + + if (child == 0) { + stack_t oss = {}; + TEST_PCHECK(sigaltstack(nullptr, &oss) == 0); + MaybeSave(); + + TEST_CHECK((oss.ss_flags & SS_DISABLE) == 0); + TEST_CHECK(oss.ss_size == SIGSTKSZ); + TEST_CHECK(oss.ss_sp == stack.ss_sp); + + Exit(0); + } + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); +} + +TEST_F(ForkTest, Affinity) { + // Make a non-default cpumask. + cpu_set_t parent_mask; + EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &parent_mask), + SyscallSucceeds()); + // Knock out the lowest bit. + for (unsigned int n = 0; n < CPU_SETSIZE; n++) { + if (CPU_ISSET(n, &parent_mask)) { + CPU_CLR(n, &parent_mask); + break; + } + } + EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &parent_mask), + SyscallSucceeds()); + + pid_t child = Fork(); + if (child == 0) { + cpu_set_t child_mask; + + int ret = sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &child_mask); + MaybeSave(); + if (ret < 0) { + Exit(-ret); + } + + TEST_CHECK(CPU_EQUAL(&child_mask, &parent_mask)); + + Exit(0); + } + + EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); +} + +#ifdef __x86_64__ +// Clone with CLONE_SETTLS and a non-canonical TLS address is rejected. +TEST(CloneTest, NonCanonicalTLS) { + constexpr uintptr_t kNonCanonical = 1ull << 48; + + // We need a valid address for the stack pointer. We'll never actually execute + // on this. + char stack; + + EXPECT_THAT(syscall(__NR_clone, SIGCHLD | CLONE_SETTLS, &stack, nullptr, + nullptr, kNonCanonical), + SyscallFailsWithErrno(EPERM)); +} +#endif + +} // namespace +} // namespace testing +} // namespace gvisor |