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 
21 namespace libcontainer {
22 
23 namespace {
24 
25 // A small RAII class that redirects stdout while it's alive. It also gets the
26 // first 4k of the output.
27 class ScopedCaptureStdout {
28  public:
ScopedCaptureStdout()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 
~ScopedCaptureStdout()39   ~ScopedCaptureStdout() {
40     CHECK(dup2(original_stdout_fd_.get(), STDOUT_FILENO) != -1);
41   }
42 
GetContents()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 
60 class LibcontainerTargetTest : public ::testing::Test {
61  public:
62   LibcontainerTargetTest() = default;
63   ~LibcontainerTargetTest() override = default;
64 
SetUp()65   void SetUp() override {
66     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
67 
68     base::FilePath rootfs;
69     ASSERT_TRUE(base::CreateTemporaryDirInDir(
70         temp_dir_.GetPath(), 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 
TearDown()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 
container()93   struct container* container() {
94     return container_;
95   }
config()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 
TEST_F(LibcontainerTargetTest,AddHookRedirectTest)108 TEST_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 
TEST_F(LibcontainerTargetTest,AddHookOrderTest)143 TEST_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 
TEST_F(LibcontainerTargetTest,AddHookPidArgument)188 TEST_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 
main(int argc,char ** argv)228 int 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