// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "absl/cleanup/cleanup.h" #include "absl/strings/str_cat.h" #include "absl/time/clock.h" #include "test/util/eventfd_util.h" #include "test/util/file_descriptor.h" #include "test/util/memory_util.h" #include "test/util/multiprocess_util.h" #include "test/util/posix_error.h" #include "test/util/test_util.h" namespace gvisor { namespace testing { void runForkExecve() { auto root_or_error = Open("/", O_RDONLY, 0); auto& root = root_or_error.ValueOrDie(); pid_t child; int execve_errno; ExecveArray argv = {"/bin/true"}; ExecveArray envv = {"TEST=123"}; auto kill_or_error = ForkAndExecveat(root.get(), "/bin/true", argv, envv, 0, nullptr, &child, &execve_errno); ASSERT_EQ(0, execve_errno); // Don't kill child, just wait for gracefully exit. kill_or_error.ValueOrDie().Release(); RetryEINTR(waitpid)(child, nullptr, 0); } // Creates a simple UDS in the abstract namespace and send one byte from the // client to the server. void runSocket() { auto path = absl::StrCat(std::string("\0", 1), "trace_test.", getpid(), absl::GetCurrentTimeNanos()); struct sockaddr_un addr; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path.c_str(), path.size() + 1); int parent_sock = socket(AF_UNIX, SOCK_STREAM, 0); if (parent_sock < 0) { err(1, "socket"); } auto sock_closer = absl::MakeCleanup([parent_sock] { close(parent_sock); }); if (bind(parent_sock, reinterpret_cast(&addr), sizeof(addr))) { err(1, "bind"); } if (listen(parent_sock, 5) < 0) { err(1, "listen"); } pid_t pid = fork(); if (pid < 0) { // Fork error. err(1, "fork"); } else if (pid == 0) { // Child. close(parent_sock); // ensure it's not mistakely used in child. int server = socket(AF_UNIX, SOCK_STREAM, 0); if (server < 0) { err(1, "socket"); } auto server_closer = absl::MakeCleanup([server] { close(server); }); if (connect(server, reinterpret_cast(&addr), sizeof(addr)) < 0) { err(1, "connect"); } char buf = 'A'; int bytes = write(server, &buf, sizeof(buf)); if (bytes != 1) { err(1, "write: %d", bytes); } exit(0); } else { // Parent. int client = RetryEINTR(accept)(parent_sock, nullptr, nullptr); if (client < 0) { err(1, "accept"); } auto client_closer = absl::MakeCleanup([client] { close(client); }); char buf; int bytes = read(client, &buf, sizeof(buf)); if (bytes != 1) { err(1, "read: %d", bytes); } // Wait to reap the child. RetryEINTR(waitpid)(pid, nullptr, 0); } } void runReadWrite() { const std::string path = "read-write.txt"; auto fd_or = Open(path, O_RDWR | O_CREAT, 0644); if (!fd_or.ok()) { err(1, "open(O_CREAT): %s", fd_or.error().ToString().c_str()); } auto cleanup = absl::MakeCleanup([path] { unlink(path.c_str()); }); auto fd = std::move(fd_or.ValueOrDie()); // Test different flavors of write. char msg[] = "hello world"; if (WriteFd(fd.get(), msg, ABSL_ARRAYSIZE(msg)) < 0) { err(1, "write"); } if (PwriteFd(fd.get(), msg, ABSL_ARRAYSIZE(msg), 10) < 0) { err(1, "pwrite"); } struct iovec write_vecs[] = { { .iov_base = msg, .iov_len = ABSL_ARRAYSIZE(msg), }, { .iov_base = msg, .iov_len = ABSL_ARRAYSIZE(msg) / 2, }, }; if (writev(fd.get(), write_vecs, ABSL_ARRAYSIZE(write_vecs)) < 0) { err(1, "writev"); } if (pwritev(fd.get(), write_vecs, ABSL_ARRAYSIZE(write_vecs), 10) < 0) { err(1, "pwritev"); } if (pwritev2(fd.get(), write_vecs, ABSL_ARRAYSIZE(write_vecs), 10, RWF_HIPRI) < 0) { err(1, "pwritev2"); } // Rewind the file and test different flavors of read. if (lseek(fd.get(), 0, SEEK_SET) < 0) { err(1, "seek(0)"); } char buf[1024]; if (ReadFd(fd.get(), buf, ABSL_ARRAYSIZE(buf)) < 0) { err(1, "read"); } if (PreadFd(fd.get(), buf, ABSL_ARRAYSIZE(buf), 20) < 0) { err(1, "read"); } // Reuse same buffer, since it's not using the result anyways. struct iovec read_vecs[] = { { .iov_base = buf, .iov_len = ABSL_ARRAYSIZE(msg), }, { .iov_base = buf, .iov_len = ABSL_ARRAYSIZE(msg) / 2, }, }; if (readv(fd.get(), read_vecs, ABSL_ARRAYSIZE(read_vecs)) < 0) { err(1, "writev"); } if (preadv(fd.get(), read_vecs, ABSL_ARRAYSIZE(read_vecs), 20) < 0) { err(1, "pwritev"); } if (preadv2(fd.get(), read_vecs, ABSL_ARRAYSIZE(read_vecs), 20, RWF_HIPRI) < 0) { err(1, "pwritev2"); } } void runChdir() { const auto pathname = "trace_test.abc"; static constexpr mode_t kDefaultDirMode = 0755; int path_or_error = mkdir(pathname, kDefaultDirMode); if (path_or_error != 0) { err(1, "mkdir"); } int res = chdir(pathname); if (res != 0) { err(1, "chdir"); } rmdir(pathname); } void runFchdir() { const auto pathname = "trace_test.abc"; static constexpr mode_t kDefaultDirMode = 0755; int path_or_error = mkdir(pathname, kDefaultDirMode); if (path_or_error != 0) { err(1, "mkdir"); } int fd = open(pathname, O_DIRECTORY | O_RDONLY); int res = fchdir(fd); if (res != 0) { err(1, "fchdir"); } rmdir(pathname); close(fd); } void runSetgid() { auto get = setgid(0); if (get != 0) { err(1, "setgid"); } } void runSetuid() { auto get = setuid(0); if (get != 0) { err(1, "setuid"); } } void runSetsid() { auto get = setsid(); // Operation is not permitted so we get an error. if (get != -1) { err(1, "setsid"); } } void runSetresuid() { auto get = setresuid(0, 0, 0); if (get != 0) { err(1, "setresuid"); } } void runSetresgid() { auto get = setresgid(0, 0, 0); if (get != 0) { err(1, "setresgid"); } } void runChroot() { const auto pathname = "trace_test.abc"; static constexpr mode_t kDefaultDirMode = 0755; int path_or_error = mkdir(pathname, kDefaultDirMode); if (path_or_error != 0) { err(1, "mkdir"); } if (chroot(pathname)) { err(1, "chroot"); } } void runDup() { const auto pathname = "trace_test.abc"; static constexpr mode_t kDefaultDirMode = 0755; int path_or_error = mkdir(pathname, kDefaultDirMode); if (path_or_error != 0) { err(1, "mkdir"); } int fd = open(pathname, O_DIRECTORY | O_RDONLY); if (fd < 0) { err(1, "open"); } int res = dup(fd); if (res < 0) { err(1, "dup"); } rmdir(pathname); } void runDup2() { const auto pathname = "trace_test.abc"; static constexpr mode_t kDefaultDirMode = 0755; int path_or_error = mkdir(pathname, kDefaultDirMode); if (path_or_error != 0) { err(1, "mkdir"); } int oldfd = open(pathname, O_DIRECTORY | O_RDONLY); if (oldfd < 0) { err(1, "open"); } int newfd = open(pathname, O_DIRECTORY | O_RDONLY); if (newfd < 0) { err(1, "open"); } int res = dup2(oldfd, newfd); if (res != newfd) { err(1, "dup2"); } rmdir(pathname); } void runDup3() { const auto pathname = "trace_test.abc"; static constexpr mode_t kDefaultDirMode = 0755; int path_or_error = mkdir(pathname, kDefaultDirMode); if (path_or_error != 0) { err(1, "mkdir"); } int oldfd = open(pathname, O_DIRECTORY | O_RDONLY); if (oldfd < 0) { err(1, "open"); } int newfd = open(pathname, O_DIRECTORY | O_RDONLY); if (newfd < 0) { err(1, "open"); } int res = dup3(oldfd, newfd, O_CLOEXEC); if (res != newfd) { err(1, "dup3"); } rmdir(pathname); } void runPrlimit64() { struct rlimit setlim; setlim.rlim_cur = 0; setlim.rlim_max = RLIM_INFINITY; int res = prlimit(0, RLIMIT_DATA, &setlim, nullptr); if (res != 0) { err(1, "prlimit64"); } } void runEventfd() { int res = eventfd(0, EFD_NONBLOCK); if (res < 0) { err(1, "eventfd"); } } void runEventfd2() { int res = Eventdfd2Setup(0, EFD_NONBLOCK); if (res < 0) { err(1, "eventfd2"); } } void runBind() { auto path = absl::StrCat(std::string("\0", 1), "trace_test.abc"); struct sockaddr_un addr; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path.c_str(), path.size() + 1); int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { err(1, "socket"); } auto sock_closer = absl::MakeCleanup([fd] { close(fd); }); if (bind(fd, reinterpret_cast(&addr), sizeof(addr))) { err(1, "bind"); } } void runAccept() { auto path = absl::StrCat(std::string("\0", 1), "trace_test.abc"); struct sockaddr_un addr; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path.c_str(), path.size() + 1); int server = socket(AF_UNIX, SOCK_STREAM, 0); if (server < 0) { err(1, "socket"); } auto sock_closer = absl::MakeCleanup([server] { close(server); }); if (bind(server, reinterpret_cast(&addr), sizeof(addr)) < 0) { err(1, "bind"); } if (listen(server, 5) < 0) { err(1, "listen"); } int client = socket(AF_UNIX, SOCK_STREAM, 0); if (client < 0) { err(1, "socket"); } auto client_closer = absl::MakeCleanup([client] { close(client); }); if (connect(client, reinterpret_cast(&addr), sizeof(addr)) < 0) { err(1, "connect"); } int fd = RetryEINTR(accept)(server, nullptr, nullptr); if (fd < 0) { err(1, "accept"); } close(fd); } void runAccept4() { auto path = absl::StrCat(std::string("\0", 1), "trace_test.abc"); struct sockaddr_un addr; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path.c_str(), path.size() + 1); int server = socket(AF_UNIX, SOCK_STREAM, 0); if (server < 0) { err(1, "socket"); } auto sock_closer = absl::MakeCleanup([server] { close(server); }); if (bind(server, reinterpret_cast(&addr), sizeof(addr)) < 0) { err(1, "bind"); } if (listen(server, 5) < 0) { err(1, "listen"); } int client = socket(AF_UNIX, SOCK_STREAM, 0); if (client < 0) { err(1, "socket"); } auto client_closer = absl::MakeCleanup([client] { close(client); }); if (connect(client, reinterpret_cast(&addr), sizeof(addr)) < 0) { err(1, "connect"); } int fd = RetryEINTR(accept4)(server, nullptr, nullptr, SOCK_CLOEXEC); if (fd < 0) { err(1, "accept4"); } close(fd); } void runSignalfd4() { sigset_t mask; sigemptyset(&mask); int res = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK); if (res < 0) { err(1, "signalfd4"); } } void runFcntl() { const auto pathname = "trace_test.abc"; static constexpr mode_t kDefaultDirMode = 0755; int path_or_error = mkdir(pathname, kDefaultDirMode); if (path_or_error != 0) { err(1, "mkdir"); } int fd = open(pathname, O_DIRECTORY | O_RDONLY); if (fd < 0) { err(1, "open"); } auto fd_closer = absl::MakeCleanup([fd] { close(fd); }); int res = fcntl(fd, F_GETFL); if (res < 0) { err(1, "fcntl"); } rmdir(pathname); } void runPipe() { int fd[2]; int res = pipe(fd); if (res < 0) { err(1, "pipe"); } close(fd[0]); close(fd[1]); } void runPipe2() { int fd[2]; int res = pipe2(fd, O_CLOEXEC); if (res < 0) { err(1, "pipe2"); } close(fd[0]); close(fd[1]); } void runTimerfdCreate() { int fd = timerfd_create(CLOCK_REALTIME, 0); if (fd < 0) { err(1, "timerfd_create"); } close(fd); } void runTimerfdSettime() { int fd = timerfd_create(CLOCK_REALTIME, 0); if (fd < 0) { err(1, "timerfd_create"); } auto fd_closer = absl::MakeCleanup([fd] { close(fd); }); constexpr auto kInitial = absl::Milliseconds(10); constexpr auto kInterval = absl::Milliseconds(25); const itimerspec val = {absl::ToTimespec(kInitial), absl::ToTimespec(kInterval)}; int res = timerfd_settime(fd, TFD_TIMER_ABSTIME, &val, 0); if (res < 0) { err(1, "timerfd_settime"); } } void runTimerfdGettime() { int fd = timerfd_create(CLOCK_REALTIME, 0); if (fd < 0) { err(1, "timerfd_create"); } auto fd_closer = absl::MakeCleanup([fd] { close(fd); }); itimerspec val; int res = timerfd_gettime(fd, &val); if (res < 0) { err(1, "timerfd_gettime"); } } // signalfd(2), fork(2), and vfork(2) system calls are not supported in arm // architecture. #ifdef __x86_64__ void runFork() { pid_t pid = syscall(__NR_fork); if (pid < 0) { err(1, "fork"); } else if (pid == 0) { exit(0); } RetryEINTR(waitpid)(pid, nullptr, 0); } void runVfork() { pid_t pid = vfork(); if (pid < 0) { err(1, "vfork"); } else if (pid == 0) { _exit(0); } RetryEINTR(waitpid)(pid, nullptr, 0); } void runSignalfd() { sigset_t mask; sigemptyset(&mask); constexpr int kSizeofKernelSigset = 8; int res = syscall(__NR_signalfd, -1, &mask, kSizeofKernelSigset); if (res < 0) { err(1, "signalfd"); } } #endif void runClone() { Mapping child_stack = ASSERT_NO_ERRNO_AND_VALUE( MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); int child_pid; child_pid = clone( +[](void*) { return 0; }, reinterpret_cast(child_stack.addr() + kPageSize), SIGCHLD | CLONE_VFORK | CLONE_FILES, nullptr); if (child_pid < 0) { err(1, "clone"); } RetryEINTR(waitpid)(child_pid, nullptr, 0); } void runInotifyInit() { int fd = inotify_init(); if (fd < 0) { err(1, "inotify_init"); } close(fd); } void runInotifyInit1() { int fd = inotify_init1(IN_NONBLOCK); if (fd < 0) { err(1, "inotify_init1"); } close(fd); } void runInotifyAddWatch() { const auto pathname = "timer_trace_test.abc"; static constexpr mode_t kDefaultDirMode = 0755; int path_or_error = mkdir(pathname, kDefaultDirMode); if (path_or_error != 0) { err(1, "mkdir"); } int fd = inotify_init1(IN_NONBLOCK); if (fd < 0) { err(1, "inotify_init1"); } auto fd_closer = absl::MakeCleanup([fd] { close(fd); }); int res = inotify_add_watch(fd, pathname, IN_NONBLOCK); if (res < 0) { err(1, "inotify_add_watch"); } rmdir(pathname); } void runInotifyRmWatch() { const auto pathname = "timer_trace_test.abc"; static constexpr mode_t kDefaultDirMode = 0755; int path_or_error = mkdir(pathname, kDefaultDirMode); if (path_or_error != 0) { err(1, "mkdir"); } int fd = inotify_init1(IN_NONBLOCK); if (fd < 0) { err(1, "inotify_init1"); } auto fd_closer = absl::MakeCleanup([fd] { close(fd); }); int wd = inotify_add_watch(fd, pathname, IN_NONBLOCK); if (wd < 0) { err(1, "inotify_add_watch"); } int res = inotify_rm_watch(fd, wd); if (res < 0) { err(1, "inotify_rm_watch"); } rmdir(pathname); } } // namespace testing } // namespace gvisor int main(int argc, char** argv) { ::gvisor::testing::runForkExecve(); ::gvisor::testing::runSocket(); ::gvisor::testing::runReadWrite(); ::gvisor::testing::runChdir(); ::gvisor::testing::runFchdir(); ::gvisor::testing::runSetgid(); ::gvisor::testing::runSetuid(); ::gvisor::testing::runSetsid(); ::gvisor::testing::runSetresuid(); ::gvisor::testing::runSetresgid(); ::gvisor::testing::runDup(); ::gvisor::testing::runDup2(); ::gvisor::testing::runDup3(); ::gvisor::testing::runPrlimit64(); ::gvisor::testing::runEventfd(); ::gvisor::testing::runEventfd2(); ::gvisor::testing::runBind(); ::gvisor::testing::runAccept(); ::gvisor::testing::runAccept4(); ::gvisor::testing::runSignalfd4(); ::gvisor::testing::runFcntl(); ::gvisor::testing::runPipe(); ::gvisor::testing::runPipe2(); ::gvisor::testing::runTimerfdCreate(); ::gvisor::testing::runTimerfdSettime(); ::gvisor::testing::runTimerfdGettime(); ::gvisor::testing::runClone(); ::gvisor::testing::runInotifyInit(); ::gvisor::testing::runInotifyInit1(); ::gvisor::testing::runInotifyAddWatch(); ::gvisor::testing::runInotifyRmWatch(); // signalfd(2), fork(2), and vfork(2) system calls are not supported in arm // architecture. #ifdef __x86_64__ ::gvisor::testing::runSignalfd(); ::gvisor::testing::runFork(); ::gvisor::testing::runVfork(); #endif // Run chroot at the end since it changes the root for all other tests. ::gvisor::testing::runChroot(); return 0; }