1// Copyright 2016 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <fcntl.h>
6#include <sys/mount.h>
7#include <sys/types.h>
8#include <unistd.h>
9
10#include <base/at_exit.h>
11#include <base/files/file_util.h>
12#include <base/files/scoped_file.h>
13#include <base/files/scoped_temp_dir.h>
14#include <base/macros.h>
15#include <base/strings/string_number_conversions.h>
16#include <base/strings/stringprintf.h>
17#include <gtest/gtest.h>
18#include <libcontainer.h>
19#include <libminijail.h>
20
21namespace libcontainer {
22
23namespace {
24
25// A small RAII class that redirects stdout while it's alive. It also gets the
26// first 4k of the output.
27class ScopedCaptureStdout {
28 public:
29  ScopedCaptureStdout() {
30    original_stdout_fd_.reset(dup(STDOUT_FILENO));
31    CHECK(original_stdout_fd_.is_valid());
32    int pipe_fds[2];
33    CHECK(pipe2(pipe_fds, O_NONBLOCK) != -1);
34    read_fd_.reset(pipe_fds[0]);
35    CHECK(dup2(pipe_fds[1], STDOUT_FILENO) != -1);
36    CHECK(close(pipe_fds[1]) != -1);
37  }
38
39  ~ScopedCaptureStdout() {
40    CHECK(dup2(original_stdout_fd_.get(), STDOUT_FILENO) != -1);
41  }
42
43  std::string GetContents() {
44    char buffer[4096];
45    ssize_t read_bytes = read(read_fd_.get(), buffer, sizeof(buffer) - 1);
46    CHECK(read_bytes >= 0);
47    buffer[read_bytes] = '\0';
48    return std::string(buffer, read_bytes);
49  }
50
51 private:
52  base::ScopedFD read_fd_;
53  base::ScopedFD original_stdout_fd_;
54
55  DISALLOW_COPY_AND_ASSIGN(ScopedCaptureStdout);
56};
57
58}  // namespace
59
60class LibcontainerTargetTest : public ::testing::Test {
61 public:
62  LibcontainerTargetTest() = default;
63  ~LibcontainerTargetTest() override = default;
64
65  void SetUp() override {
66    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
67
68    base::FilePath rootfs;
69    ASSERT_TRUE(base::CreateTemporaryDirInDir(
70        temp_dir_.path(), FILE_PATH_LITERAL("rootfs"), &rootfs));
71
72    config_ = container_config_create();
73    ASSERT_NE(nullptr, config_);
74
75    ASSERT_EQ(0, container_config_uid_map(config_, "0 0 429496729"));
76    ASSERT_EQ(0, container_config_gid_map(config_, "0 0 429496729"));
77    ASSERT_EQ(0, container_config_rootfs(config_, "/"));
78    ASSERT_EQ(0, container_config_set_cgroup_parent(
79                     config_, "chronos_containers", 1000, 1000));
80
81    container_ = container_new("containerUT", rootfs.value().c_str());
82    ASSERT_NE(nullptr, container_);
83  }
84
85  void TearDown() override {
86    container_destroy(container_);
87    container_ = nullptr;
88    container_config_destroy(config_);
89    config_ = nullptr;
90    ASSERT_TRUE(temp_dir_.Delete());
91  }
92
93  struct container* container() {
94    return container_;
95  }
96  struct container_config* config() {
97    return config_;
98  }
99
100 private:
101  base::ScopedTempDir temp_dir_;
102  struct container* container_ = nullptr;
103  struct container_config* config_ = nullptr;
104
105  DISALLOW_COPY_AND_ASSIGN(LibcontainerTargetTest);
106};
107
108TEST_F(LibcontainerTargetTest, AddHookRedirectTest) {
109  // Preserve stdout/stderr to get the output from the container.
110  int stdio_fds[] = {STDOUT_FILENO, STDERR_FILENO};
111  ASSERT_EQ(0, container_config_inherit_fds(config(), stdio_fds,
112                                            arraysize(stdio_fds)));
113
114  static const char* kPreChrootArgv[] = {
115      "/bin/cat",
116  };
117  int stdin_fd;
118  ASSERT_EQ(0, container_config_add_hook(
119                   config(), MINIJAIL_HOOK_EVENT_PRE_CHROOT, kPreChrootArgv[0],
120                   kPreChrootArgv, arraysize(kPreChrootArgv), &stdin_fd,
121                   nullptr, nullptr));
122  EXPECT_EQ(1, write(stdin_fd, "1", 1));
123  close(stdin_fd);
124
125  static const char* kProgramArgv[] = {
126      "/bin/echo",
127      "-n",
128      "2",
129  };
130  ASSERT_EQ(0, container_config_program_argv(config(), kProgramArgv,
131                                             arraysize(kProgramArgv)));
132
133  std::string output;
134  {
135    ScopedCaptureStdout capture_stdout;
136    EXPECT_EQ(0, container_start(container(), config()));
137    EXPECT_EQ(0, container_wait(container()));
138    output = capture_stdout.GetContents();
139  }
140  EXPECT_EQ("12", output);
141}
142
143TEST_F(LibcontainerTargetTest, AddHookOrderTest) {
144  // Preserve stdout/stderr to get the output from the container.
145  int stdio_fds[] = {STDOUT_FILENO, STDERR_FILENO};
146  ASSERT_EQ(0, container_config_inherit_fds(config(), stdio_fds,
147                                            arraysize(stdio_fds)));
148
149  static const char* kProgramArgv[] = {
150      "/bin/echo",
151      "-n",
152      "3",
153  };
154  ASSERT_EQ(0, container_config_program_argv(config(), kProgramArgv,
155                                             arraysize(kProgramArgv)));
156
157  // Hooks are run in the following order: pre-chroot, pre-dropcaps, pre-execve
158  static const char* kPreExecveArgv[] = {
159      "/bin/echo",
160      "-n",
161      "2",
162  };
163  ASSERT_EQ(0, container_config_add_hook(
164                   config(), MINIJAIL_HOOK_EVENT_PRE_EXECVE, kPreExecveArgv[0],
165                   kPreExecveArgv, arraysize(kPreExecveArgv), nullptr, nullptr,
166                   nullptr));
167
168  static const char* kPreChrootArgv[] = {
169      "/bin/echo",
170      "-n",
171      "1",
172  };
173  ASSERT_EQ(0, container_config_add_hook(
174                   config(), MINIJAIL_HOOK_EVENT_PRE_CHROOT, kPreChrootArgv[0],
175                   kPreChrootArgv, arraysize(kPreChrootArgv), nullptr, nullptr,
176                   nullptr));
177
178  std::string output;
179  {
180    ScopedCaptureStdout capture_stdout;
181    EXPECT_EQ(0, container_start(container(), config()));
182    EXPECT_EQ(0, container_wait(container()));
183    output = capture_stdout.GetContents();
184  }
185  EXPECT_EQ("123", output);
186}
187
188TEST_F(LibcontainerTargetTest, AddHookPidArgument) {
189  // Preserve stdout/stderr to get the output from the container.
190  int stdio_fds[] = {STDOUT_FILENO, STDERR_FILENO};
191  ASSERT_EQ(0, container_config_inherit_fds(config(), stdio_fds,
192                                            arraysize(stdio_fds)));
193
194  static const char* kProgramArgv[] = {
195      "/bin/true",
196  };
197  ASSERT_EQ(0, container_config_program_argv(config(), kProgramArgv,
198                                             arraysize(kProgramArgv)));
199
200  static const char* kPreExecveArgv[] = {
201      "/bin/echo",
202      "-n",
203      "$PID",
204  };
205  ASSERT_EQ(0, container_config_add_hook(
206                   config(), MINIJAIL_HOOK_EVENT_PRE_EXECVE, kPreExecveArgv[0],
207                   kPreExecveArgv, arraysize(kPreExecveArgv), nullptr, nullptr,
208                   nullptr));
209
210  std::string output;
211  int pid;
212  {
213    ScopedCaptureStdout capture_stdout;
214    EXPECT_EQ(0, container_start(container(), config()));
215    pid = container_pid(container());
216    EXPECT_EQ(0, container_wait(container()));
217    output = capture_stdout.GetContents();
218  }
219  EXPECT_EQ(base::IntToString(pid), output);
220}
221
222}  // namespace libcontainer
223
224// Avoid including syslog.h, since it collides with some of the logging
225// constants in libchrome.
226#define SYSLOG_LOG_INFO 6
227
228int main(int argc, char** argv) {
229  base::AtExitManager exit_manager;
230  testing::InitGoogleTest(&argc, argv);
231  testing::GTEST_FLAG(throw_on_failure) = true;
232  minijail_log_to_fd(STDERR_FILENO, SYSLOG_LOG_INFO);
233  return RUN_ALL_TESTS();
234}
235