| // Copyright 2016 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fcntl.h> |
| #include <sys/mount.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <base/at_exit.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_file.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/macros.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/stringprintf.h> |
| #include <gtest/gtest.h> |
| #include <libcontainer.h> |
| #include <libminijail.h> |
| |
| namespace libcontainer { |
| |
| namespace { |
| |
| // A small RAII class that redirects stdout while it's alive. It also gets the |
| // first 4k of the output. |
| class ScopedCaptureStdout { |
| public: |
| ScopedCaptureStdout() { |
| original_stdout_fd_.reset(dup(STDOUT_FILENO)); |
| CHECK(original_stdout_fd_.is_valid()); |
| int pipe_fds[2]; |
| CHECK(pipe2(pipe_fds, O_NONBLOCK) != -1); |
| read_fd_.reset(pipe_fds[0]); |
| CHECK(dup2(pipe_fds[1], STDOUT_FILENO) != -1); |
| CHECK(close(pipe_fds[1]) != -1); |
| } |
| |
| ~ScopedCaptureStdout() { |
| CHECK(dup2(original_stdout_fd_.get(), STDOUT_FILENO) != -1); |
| } |
| |
| std::string GetContents() { |
| char buffer[4096]; |
| ssize_t read_bytes = read(read_fd_.get(), buffer, sizeof(buffer) - 1); |
| CHECK(read_bytes >= 0); |
| buffer[read_bytes] = '\0'; |
| return std::string(buffer, read_bytes); |
| } |
| |
| private: |
| base::ScopedFD read_fd_; |
| base::ScopedFD original_stdout_fd_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedCaptureStdout); |
| }; |
| |
| } // namespace |
| |
| class LibcontainerTargetTest : public ::testing::Test { |
| public: |
| LibcontainerTargetTest() = default; |
| ~LibcontainerTargetTest() override = default; |
| |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| |
| base::FilePath rootfs; |
| ASSERT_TRUE(base::CreateTemporaryDirInDir( |
| temp_dir_.GetPath(), FILE_PATH_LITERAL("rootfs"), &rootfs)); |
| |
| config_ = container_config_create(); |
| ASSERT_NE(nullptr, config_); |
| |
| ASSERT_EQ(0, container_config_uid_map(config_, "0 0 429496729")); |
| ASSERT_EQ(0, container_config_gid_map(config_, "0 0 429496729")); |
| ASSERT_EQ(0, container_config_rootfs(config_, "/")); |
| ASSERT_EQ(0, container_config_set_cgroup_parent( |
| config_, "chronos_containers", 1000, 1000)); |
| |
| container_ = container_new("containerUT", rootfs.value().c_str()); |
| ASSERT_NE(nullptr, container_); |
| } |
| |
| void TearDown() override { |
| container_destroy(container_); |
| container_ = nullptr; |
| container_config_destroy(config_); |
| config_ = nullptr; |
| ASSERT_TRUE(temp_dir_.Delete()); |
| } |
| |
| struct container* container() { |
| return container_; |
| } |
| struct container_config* config() { |
| return config_; |
| } |
| |
| private: |
| base::ScopedTempDir temp_dir_; |
| struct container* container_ = nullptr; |
| struct container_config* config_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(LibcontainerTargetTest); |
| }; |
| |
| TEST_F(LibcontainerTargetTest, AddHookRedirectTest) { |
| // Preserve stdout/stderr to get the output from the container. |
| int stdio_fds[] = {STDOUT_FILENO, STDERR_FILENO}; |
| ASSERT_EQ(0, container_config_inherit_fds(config(), stdio_fds, |
| arraysize(stdio_fds))); |
| |
| static const char* kPreChrootArgv[] = { |
| "/bin/cat", |
| }; |
| int stdin_fd; |
| ASSERT_EQ(0, container_config_add_hook( |
| config(), MINIJAIL_HOOK_EVENT_PRE_CHROOT, kPreChrootArgv[0], |
| kPreChrootArgv, arraysize(kPreChrootArgv), &stdin_fd, |
| nullptr, nullptr)); |
| EXPECT_EQ(1, write(stdin_fd, "1", 1)); |
| close(stdin_fd); |
| |
| static const char* kProgramArgv[] = { |
| "/bin/echo", |
| "-n", |
| "2", |
| }; |
| ASSERT_EQ(0, container_config_program_argv(config(), kProgramArgv, |
| arraysize(kProgramArgv))); |
| |
| std::string output; |
| { |
| ScopedCaptureStdout capture_stdout; |
| EXPECT_EQ(0, container_start(container(), config())); |
| EXPECT_EQ(0, container_wait(container())); |
| output = capture_stdout.GetContents(); |
| } |
| EXPECT_EQ("12", output); |
| } |
| |
| TEST_F(LibcontainerTargetTest, AddHookOrderTest) { |
| // Preserve stdout/stderr to get the output from the container. |
| int stdio_fds[] = {STDOUT_FILENO, STDERR_FILENO}; |
| ASSERT_EQ(0, container_config_inherit_fds(config(), stdio_fds, |
| arraysize(stdio_fds))); |
| |
| static const char* kProgramArgv[] = { |
| "/bin/echo", |
| "-n", |
| "3", |
| }; |
| ASSERT_EQ(0, container_config_program_argv(config(), kProgramArgv, |
| arraysize(kProgramArgv))); |
| |
| // Hooks are run in the following order: pre-chroot, pre-dropcaps, pre-execve |
| static const char* kPreExecveArgv[] = { |
| "/bin/echo", |
| "-n", |
| "2", |
| }; |
| ASSERT_EQ(0, container_config_add_hook( |
| config(), MINIJAIL_HOOK_EVENT_PRE_EXECVE, kPreExecveArgv[0], |
| kPreExecveArgv, arraysize(kPreExecveArgv), nullptr, nullptr, |
| nullptr)); |
| |
| static const char* kPreChrootArgv[] = { |
| "/bin/echo", |
| "-n", |
| "1", |
| }; |
| ASSERT_EQ(0, container_config_add_hook( |
| config(), MINIJAIL_HOOK_EVENT_PRE_CHROOT, kPreChrootArgv[0], |
| kPreChrootArgv, arraysize(kPreChrootArgv), nullptr, nullptr, |
| nullptr)); |
| |
| std::string output; |
| { |
| ScopedCaptureStdout capture_stdout; |
| EXPECT_EQ(0, container_start(container(), config())); |
| EXPECT_EQ(0, container_wait(container())); |
| output = capture_stdout.GetContents(); |
| } |
| EXPECT_EQ("123", output); |
| } |
| |
| TEST_F(LibcontainerTargetTest, AddHookPidArgument) { |
| // Preserve stdout/stderr to get the output from the container. |
| int stdio_fds[] = {STDOUT_FILENO, STDERR_FILENO}; |
| ASSERT_EQ(0, container_config_inherit_fds(config(), stdio_fds, |
| arraysize(stdio_fds))); |
| |
| static const char* kProgramArgv[] = { |
| "/bin/true", |
| }; |
| ASSERT_EQ(0, container_config_program_argv(config(), kProgramArgv, |
| arraysize(kProgramArgv))); |
| |
| static const char* kPreExecveArgv[] = { |
| "/bin/echo", |
| "-n", |
| "$PID", |
| }; |
| ASSERT_EQ(0, container_config_add_hook( |
| config(), MINIJAIL_HOOK_EVENT_PRE_EXECVE, kPreExecveArgv[0], |
| kPreExecveArgv, arraysize(kPreExecveArgv), nullptr, nullptr, |
| nullptr)); |
| |
| std::string output; |
| int pid; |
| { |
| ScopedCaptureStdout capture_stdout; |
| EXPECT_EQ(0, container_start(container(), config())); |
| pid = container_pid(container()); |
| EXPECT_EQ(0, container_wait(container())); |
| output = capture_stdout.GetContents(); |
| } |
| EXPECT_EQ(base::IntToString(pid), output); |
| } |
| |
| } // namespace libcontainer |
| |
| // Avoid including syslog.h, since it collides with some of the logging |
| // constants in libchrome. |
| #define SYSLOG_LOG_INFO 6 |
| |
| int main(int argc, char** argv) { |
| base::AtExitManager exit_manager; |
| testing::InitGoogleTest(&argc, argv); |
| testing::GTEST_FLAG(throw_on_failure) = true; |
| minijail_log_to_fd(STDERR_FILENO, SYSLOG_LOG_INFO); |
| return RUN_ALL_TESTS(); |
| } |