blob: 856243a2e3333093bf67190a36c201d1d85bad06 [file] [log] [blame]
Mike Frysinger4d2a81e2018-01-22 16:43:33 -05001/* Copyright 2018 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 * Test the minijail0 CLI using gtest.
6 *
7 * Note: We don't verify that the minijail struct was set correctly from these
8 * flags as only libminijail.c knows that definition. If we wanted to improve
9 * this test, we'd have to pull that struct into a common (internal) header.
10 */
11
12#include <stdio.h>
13#include <stdlib.h>
14
15#include <gtest/gtest.h>
16
17#include "libminijail.h"
18#include "minijail0_cli.h"
19
20namespace {
21
22constexpr char kValidUser[] = "nobody";
23constexpr char kValidUid[] = "100";
24constexpr char kValidGroup[] = "users";
25constexpr char kValidGid[] = "100";
26
27class CliTest : public ::testing::Test {
28 protected:
29 virtual void SetUp() {
30 j_ = minijail_new();
31
32 // Most tests do not care about this logic. For the few that do, make
33 // them opt into it so they can validate specifically.
34 elftype_ = ELFDYNAMIC;
35 }
36 virtual void TearDown() {
37 minijail_destroy(j_);
38 }
39
40 // We use a vector of strings rather than const char * pointers because we
41 // need the backing memory to be writable. The CLI might mutate the strings
42 // as it parses things (which is normally permissible with argv).
43 int parse_args_(const std::vector<std::string>& argv, int *exit_immediately,
44 ElfType *elftype) {
45 // Make sure we reset the getopts state when scanning a new argv.
46 optind = 1;
47
48 std::vector<const char *> pargv;
49 pargv.push_back("minijail0");
50 for (const std::string& arg : argv)
51 pargv.push_back(arg.c_str());
52
53 // We grab stdout from parse_args itself as it might dump things we don't
54 // usually care about like help output.
55 testing::internal::CaptureStdout();
56 int ret = parse_args(j_, pargv.size(),
57 const_cast<char* const*>(pargv.data()),
58 exit_immediately, elftype);
59 testing::internal::GetCapturedStdout();
60
61 return ret;
62 }
63
64 int parse_args_(const std::vector<std::string>& argv) {
65 return parse_args_(argv, &exit_immediately_, &elftype_);
66 }
67
68 struct minijail *j_;
69 ElfType elftype_;
70 int exit_immediately_;
71};
72
73} // namespace
74
75// Should exit non-zero when there's no arguments.
76TEST_F(CliTest, no_args) {
77 std::vector<std::string> argv = {};
78 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
79}
80
81// Should exit zero when we asked for help.
82TEST_F(CliTest, help) {
83 std::vector<std::string> argv = {"-h"};
84 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
85
86 argv = {"--help"};
87 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
88
89 argv = {"-H"};
90 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
91}
92
93// Just a simple program to run.
94TEST_F(CliTest, valid_program) {
95 std::vector<std::string> argv = {"/bin/sh"};
96 ASSERT_TRUE(parse_args_(argv));
97}
98
99// Valid calls to the change user option.
100TEST_F(CliTest, valid_set_user) {
101 std::vector<std::string> argv = {"-u", "", "/bin/sh"};
102
103 argv[1] = kValidUser;
104 ASSERT_TRUE(parse_args_(argv));
105
106 argv[1] = kValidUid;
107 ASSERT_TRUE(parse_args_(argv));
108}
109
110// Invalid calls to the change user option.
111TEST_F(CliTest, invalid_set_user) {
112 std::vector<std::string> argv = {"-u", "", "/bin/sh"};
113 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
114
115 argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
116 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
117
118 argv[1] = "1000x";
119 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
120}
121
122// Valid calls to the change group option.
123TEST_F(CliTest, valid_set_group) {
124 std::vector<std::string> argv = {"-g", "", "/bin/sh"};
125
126 argv[1] = kValidGroup;
127 ASSERT_TRUE(parse_args_(argv));
128
129 argv[1] = kValidGid;
130 ASSERT_TRUE(parse_args_(argv));
131}
132
133// Invalid calls to the change group option.
134TEST_F(CliTest, invalid_set_group) {
135 std::vector<std::string> argv = {"-g", "", "/bin/sh"};
136 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
137
138 argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
139 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
140
141 argv[1] = "1000x";
142 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
143}
144
145// Valid calls to the skip securebits option.
146TEST_F(CliTest, valid_skip_securebits) {
147 // An empty string is the same as 0.
148 std::vector<std::string> argv = {"-B", "", "/bin/sh"};
149 ASSERT_TRUE(parse_args_(argv));
150
151 argv[1] = "0xAB";
152 ASSERT_TRUE(parse_args_(argv));
153
154 argv[1] = "1234";
155 ASSERT_TRUE(parse_args_(argv));
156}
157
158// Invalid calls to the skip securebits option.
159TEST_F(CliTest, invalid_skip_securebits) {
160 std::vector<std::string> argv = {"-B", "", "/bin/sh"};
161
162 argv[1] = "xja";
163 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
164}
165
166// Valid calls to the caps option.
167TEST_F(CliTest, valid_caps) {
168 // An empty string is the same as 0.
169 std::vector<std::string> argv = {"-c", "", "/bin/sh"};
170 ASSERT_TRUE(parse_args_(argv));
171
172 argv[1] = "0xAB";
173 ASSERT_TRUE(parse_args_(argv));
174
175 argv[1] = "1234";
176 ASSERT_TRUE(parse_args_(argv));
177}
178
179// Invalid calls to the caps option.
180TEST_F(CliTest, invalid_caps) {
181 std::vector<std::string> argv = {"-c", "", "/bin/sh"};
182
183 argv[1] = "xja";
184 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
185}
186
187// Valid calls to the logging option.
188TEST_F(CliTest, valid_logging) {
189 std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
190
191 // This should list all valid logging targets.
192 const std::vector<std::string> profiles = {
193 "stderr",
194 "syslog",
195 };
196
197 for (const auto profile : profiles) {
198 argv[1] = profile;
199 ASSERT_TRUE(parse_args_(argv));
200 }
201}
202
203// Invalid calls to the logging option.
204TEST_F(CliTest, invalid_logging) {
205 std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
206 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
207
208 argv[1] = "stdout";
209 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
210}
211
212// Valid calls to the rlimit option.
213TEST_F(CliTest, valid_rlimit) {
214 std::vector<std::string> argv = {"-R", "", "/bin/sh"};
215
216 argv[1] = "0,1,2";
217 ASSERT_TRUE(parse_args_(argv));
218}
219
220// Invalid calls to the rlimit option.
221TEST_F(CliTest, invalid_rlimit) {
222 std::vector<std::string> argv = {"-R", "", "/bin/sh"};
223 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
224
225 // Missing cur & max.
226 argv[1] = "0";
227 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
228
229 // Missing max.
230 argv[1] = "0,0";
231 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
232
233 // Too many options.
234 argv[1] = "0,0,0,0";
235 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
236
237 // TODO: We probably should reject non-numbers, but the current CLI ignores
238 // them and converts them to zeros. Oops.
239}
240
241// Valid calls to the profile option.
242TEST_F(CliTest, valid_profile) {
243 std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
244
245 // This should list all valid profiles.
246 const std::vector<std::string> profiles = {
247 "minimalistic-mountns",
248 };
249
250 for (const auto profile : profiles) {
251 argv[1] = profile;
252 ASSERT_TRUE(parse_args_(argv));
253 }
254}
255
256// Invalid calls to the profile option.
257TEST_F(CliTest, invalid_profile) {
258 std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
259 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
260
261 argv[1] = "random-unknown-profile";
262 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
263}
264
265// Valid calls to the chroot option.
266TEST_F(CliTest, valid_chroot) {
267 std::vector<std::string> argv = {"-C", "/", "/bin/sh"};
268 ASSERT_TRUE(parse_args_(argv));
269}
270
271// Valid calls to the pivot root option.
272TEST_F(CliTest, valid_pivot_root) {
273 std::vector<std::string> argv = {"-P", "/", "/bin/sh"};
274 ASSERT_TRUE(parse_args_(argv));
275}
276
277// We cannot handle multiple options with chroot/profile/pivot root.
278TEST_F(CliTest, conflicting_roots) {
279 std::vector<std::string> argv;
280
281 // Chroot & pivot root.
282 argv = {"-C", "/", "-P", "/", "/bin/sh"};
283 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
284
285 // Chroot & minimalistic-mountns profile.
286 argv = {"-C", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
287 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
288
289 // Pivot root & minimalistic-mountns profile.
290 argv = {"-P", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
291 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
292}
293
294// Valid calls to the uidmap option.
295TEST_F(CliTest, valid_uidmap) {
296 std::vector<std::string> argv = {"-m", "/bin/sh"};
297 // Use a default map (no option from user).
298 ASSERT_TRUE(parse_args_(argv));
299
300 // Use a single map.
301 argv = {"-m0 0 1", "/bin/sh"};
302 ASSERT_TRUE(parse_args_(argv));
303
304 // Multiple maps.
305 argv = {"-m0 0 1,100 100 1", "/bin/sh"};
306 ASSERT_TRUE(parse_args_(argv));
307}
308
309// Valid calls to the gidmap option.
310TEST_F(CliTest, valid_gidmap) {
311 std::vector<std::string> argv = {"-M", "/bin/sh"};
312 // Use a default map (no option from user).
313 ASSERT_TRUE(parse_args_(argv));
314
315 // Use a single map.
316 argv = {"-M0 0 1", "/bin/sh"};
317 ASSERT_TRUE(parse_args_(argv));
318
319 // Multiple maps.
320 argv = {"-M0 0 1,100 100 1", "/bin/sh"};
321 ASSERT_TRUE(parse_args_(argv));
322}
323
324// Invalid calls to the uidmap/gidmap options.
325// Note: Can't really test these as all validation is delayed/left to the
326// runtime kernel. Minijail will simply write verbatim what the user gave
327// it to the corresponding /proc/.../[ug]id_map.
328
329// Valid calls to the binding option.
330TEST_F(CliTest, valid_binding) {
331 std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
332
333 // Dest & writable are optional.
334 argv[1] = "/";
335 ASSERT_TRUE(parse_args_(argv));
336
337 // Writable is optional.
338 argv[1] = "/,/";
339 ASSERT_TRUE(parse_args_(argv));
340
341 // Writable is an integer.
342 argv[1] = "/,/,0";
343 ASSERT_TRUE(parse_args_(argv));
344 argv[1] = "/,/,1";
345 ASSERT_TRUE(parse_args_(argv));
346
347 // Dest is optional.
348 argv[1] = "/,,0";
349 ASSERT_TRUE(parse_args_(argv));
350}
351
352// Invalid calls to the binding option.
353TEST_F(CliTest, invalid_binding) {
354 std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
355
356 // Missing source.
357 argv[2] = "";
358 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
359
360 // Too many args.
361 argv[2] = "/,/,0,what";
362 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
363
364 // Missing mount namespace/etc...
365 argv = {"-b", "/", "/bin/sh"};
366 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
367}
368
369// Valid calls to the mount option.
370TEST_F(CliTest, valid_mount) {
371 std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
372
373 // Flags & data are optional.
374 argv[2] = "none,/,none";
375 ASSERT_TRUE(parse_args_(argv));
376
377 // Data is optional.
378 argv[2] = "none,/,none,0xe";
379 ASSERT_TRUE(parse_args_(argv));
380
381 // Flags are optional.
382 argv[2] = "none,/,none,,mode=755";
383 ASSERT_TRUE(parse_args_(argv));
384}
385
386// Invalid calls to the mount option.
387TEST_F(CliTest, invalid_mount) {
388 std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
389
390 // Missing source.
391 argv[2] = "";
392 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
393
394 // Missing dest.
395 argv[2] = "none";
396 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
397
398 // Missing type.
399 argv[2] = "none,/";
400 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
401}