blob: 76ff37f2a2d6a8b497f0495eb326722d648dc4bf [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() {
Mike Frysinger4d2a81e2018-01-22 16:43:33 -050030 // Most tests do not care about this logic. For the few that do, make
31 // them opt into it so they can validate specifically.
32 elftype_ = ELFDYNAMIC;
33 }
Mike Frysinger2892c1d2018-05-21 07:36:38 -040034 virtual void TearDown() {}
Mike Frysinger4d2a81e2018-01-22 16:43:33 -050035
36 // We use a vector of strings rather than const char * pointers because we
37 // need the backing memory to be writable. The CLI might mutate the strings
38 // as it parses things (which is normally permissible with argv).
Luis Hector Chavez9acba452018-10-11 10:13:25 -070039 int parse_args_(const std::vector<std::string>& argv,
40 int* exit_immediately,
41 ElfType* elftype) {
Mike Frysinger97413722018-02-05 13:39:39 -050042 // Make sure we reset the getopts state when scanning a new argv. Setting
43 // this to 0 is a GNU extension, but AOSP/BSD also checks this (as an alias
44 // to their "optreset").
45 optind = 0;
Mike Frysinger4d2a81e2018-01-22 16:43:33 -050046
Mike Frysinger2892c1d2018-05-21 07:36:38 -040047 // We create & destroy this for every parse_args call because some API
48 // calls can dupe memory which confuses LSAN. https://crbug.com/844615
49 struct minijail *j = minijail_new();
50
Mike Frysinger4d2a81e2018-01-22 16:43:33 -050051 std::vector<const char *> pargv;
52 pargv.push_back("minijail0");
53 for (const std::string& arg : argv)
54 pargv.push_back(arg.c_str());
55
56 // We grab stdout from parse_args itself as it might dump things we don't
57 // usually care about like help output.
58 testing::internal::CaptureStdout();
Luis Hector Chavez9acba452018-10-11 10:13:25 -070059
60 const char* preload_path = PRELOADPATH;
61 int ret =
62 parse_args(j, pargv.size(), const_cast<char* const*>(pargv.data()),
63 exit_immediately, elftype, &preload_path);
Mike Frysinger4d2a81e2018-01-22 16:43:33 -050064 testing::internal::GetCapturedStdout();
65
Mike Frysinger2892c1d2018-05-21 07:36:38 -040066 minijail_destroy(j);
67
Mike Frysinger4d2a81e2018-01-22 16:43:33 -050068 return ret;
69 }
70
71 int parse_args_(const std::vector<std::string>& argv) {
72 return parse_args_(argv, &exit_immediately_, &elftype_);
73 }
74
Mike Frysinger4d2a81e2018-01-22 16:43:33 -050075 ElfType elftype_;
76 int exit_immediately_;
77};
78
79} // namespace
80
81// Should exit non-zero when there's no arguments.
82TEST_F(CliTest, no_args) {
83 std::vector<std::string> argv = {};
84 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
85}
86
87// Should exit zero when we asked for help.
88TEST_F(CliTest, help) {
89 std::vector<std::string> argv = {"-h"};
90 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
91
92 argv = {"--help"};
93 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
94
95 argv = {"-H"};
96 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
97}
98
99// Just a simple program to run.
100TEST_F(CliTest, valid_program) {
101 std::vector<std::string> argv = {"/bin/sh"};
102 ASSERT_TRUE(parse_args_(argv));
103}
104
105// Valid calls to the change user option.
106TEST_F(CliTest, valid_set_user) {
107 std::vector<std::string> argv = {"-u", "", "/bin/sh"};
108
109 argv[1] = kValidUser;
110 ASSERT_TRUE(parse_args_(argv));
111
112 argv[1] = kValidUid;
113 ASSERT_TRUE(parse_args_(argv));
114}
115
116// Invalid calls to the change user option.
117TEST_F(CliTest, invalid_set_user) {
118 std::vector<std::string> argv = {"-u", "", "/bin/sh"};
119 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
120
121 argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
122 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
123
124 argv[1] = "1000x";
125 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Luis Hector Chavez8ddef8f2019-01-02 08:40:54 -0800126
127 // Supplying -u more than once is bad.
128 argv = {"-u", kValidUser, "-u", kValidUid, "/bin/sh"};
129 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1),
130 "-u provided multiple times");
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500131}
132
133// Valid calls to the change group option.
134TEST_F(CliTest, valid_set_group) {
135 std::vector<std::string> argv = {"-g", "", "/bin/sh"};
136
137 argv[1] = kValidGroup;
138 ASSERT_TRUE(parse_args_(argv));
139
140 argv[1] = kValidGid;
141 ASSERT_TRUE(parse_args_(argv));
142}
143
144// Invalid calls to the change group option.
145TEST_F(CliTest, invalid_set_group) {
146 std::vector<std::string> argv = {"-g", "", "/bin/sh"};
147 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
148
149 argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
150 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
151
152 argv[1] = "1000x";
153 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Luis Hector Chavez8ddef8f2019-01-02 08:40:54 -0800154
155 // Supplying -g more than once is bad.
156 argv = {"-g", kValidGroup, "-g", kValidGid, "/bin/sh"};
157 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1),
158 "-g provided multiple times");
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500159}
160
Stéphane Lesimple8d7174b2020-02-07 20:51:08 +0100161// Valid calls to the add-suppl-group option.
162TEST_F(CliTest, valid_add_supp_group) {
163 std::vector<std::string> argv = {"--add-suppl-group", "", "/bin/sh"};
164
165 argv[1] = kValidGroup;
166 ASSERT_TRUE(parse_args_(argv));
167
168 argv[1] = kValidGid;
169 ASSERT_TRUE(parse_args_(argv));
170
171 std::vector<std::string> argv2 = {"--add-suppl-group", "",
172 "--add-suppl-group", "", "/bin/sh"};
173 argv[1] = kValidGroup;
174 argv[2] = kValidGid;
175 ASSERT_TRUE(parse_args_(argv));
176}
177
178// Invalid calls to the add-suppl-group option.
179TEST_F(CliTest, invalid_add_supp_group) {
180 std::vector<std::string> argv = {"--add-suppl-group", "", "/bin/sh"};
181
182 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
183
184 argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
185 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
186
187 argv[1] = "1000x";
188 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
189}
190
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500191// Valid calls to the skip securebits option.
192TEST_F(CliTest, valid_skip_securebits) {
193 // An empty string is the same as 0.
194 std::vector<std::string> argv = {"-B", "", "/bin/sh"};
195 ASSERT_TRUE(parse_args_(argv));
196
197 argv[1] = "0xAB";
198 ASSERT_TRUE(parse_args_(argv));
199
200 argv[1] = "1234";
201 ASSERT_TRUE(parse_args_(argv));
202}
203
204// Invalid calls to the skip securebits option.
205TEST_F(CliTest, invalid_skip_securebits) {
206 std::vector<std::string> argv = {"-B", "", "/bin/sh"};
207
208 argv[1] = "xja";
209 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
210}
211
212// Valid calls to the caps option.
213TEST_F(CliTest, valid_caps) {
214 // An empty string is the same as 0.
215 std::vector<std::string> argv = {"-c", "", "/bin/sh"};
216 ASSERT_TRUE(parse_args_(argv));
217
218 argv[1] = "0xAB";
219 ASSERT_TRUE(parse_args_(argv));
220
221 argv[1] = "1234";
222 ASSERT_TRUE(parse_args_(argv));
223}
224
225// Invalid calls to the caps option.
226TEST_F(CliTest, invalid_caps) {
227 std::vector<std::string> argv = {"-c", "", "/bin/sh"};
228
229 argv[1] = "xja";
230 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
231}
232
233// Valid calls to the logging option.
234TEST_F(CliTest, valid_logging) {
235 std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
236
237 // This should list all valid logging targets.
238 const std::vector<std::string> profiles = {
239 "stderr",
240 "syslog",
241 };
242
Manoj Gupta0bb824a2020-02-25 12:54:30 -0800243 for (const auto& profile : profiles) {
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500244 argv[1] = profile;
245 ASSERT_TRUE(parse_args_(argv));
246 }
247}
248
249// Invalid calls to the logging option.
250TEST_F(CliTest, invalid_logging) {
251 std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
252 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
253
254 argv[1] = "stdout";
255 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
256}
257
258// Valid calls to the rlimit option.
259TEST_F(CliTest, valid_rlimit) {
260 std::vector<std::string> argv = {"-R", "", "/bin/sh"};
261
262 argv[1] = "0,1,2";
263 ASSERT_TRUE(parse_args_(argv));
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800264
Mike Frysingere34d7fe2018-05-23 04:18:30 -0400265 argv[1] = "0,0x100,4";
266 ASSERT_TRUE(parse_args_(argv));
267
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800268 argv[1] = "1,1,unlimited";
269 ASSERT_TRUE(parse_args_(argv));
270
271 argv[1] = "2,unlimited,2";
272 ASSERT_TRUE(parse_args_(argv));
Mike Frysingere34d7fe2018-05-23 04:18:30 -0400273
274 argv[1] = "RLIMIT_AS,unlimited,unlimited";
275 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500276}
277
278// Invalid calls to the rlimit option.
279TEST_F(CliTest, invalid_rlimit) {
280 std::vector<std::string> argv = {"-R", "", "/bin/sh"};
281 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
282
283 // Missing cur & max.
284 argv[1] = "0";
285 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
286
287 // Missing max.
288 argv[1] = "0,0";
289 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
290
291 // Too many options.
292 argv[1] = "0,0,0,0";
293 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
294
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800295 // Non-numeric limits
296 argv[1] = "0,0,invalid-limit";
297 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
298
Mike Frysingere34d7fe2018-05-23 04:18:30 -0400299 // Invalid number.
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800300 argv[1] = "0,0,0j";
301 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysingere34d7fe2018-05-23 04:18:30 -0400302
303 // Invalid hex number.
304 argv[1] = "0,0x1jf,0";
305 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
306
307 // Invalid rlimit constant.
308 argv[1] = "RLIMIT_GOGOOGOG,0,0";
309 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500310}
311
312// Valid calls to the profile option.
313TEST_F(CliTest, valid_profile) {
314 std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
315
316 // This should list all valid profiles.
317 const std::vector<std::string> profiles = {
318 "minimalistic-mountns",
Mike Frysingercc5917c2020-02-03 12:34:14 -0500319 "minimalistic-mountns-nodev",
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500320 };
321
Manoj Gupta0bb824a2020-02-25 12:54:30 -0800322 for (const auto& profile : profiles) {
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500323 argv[1] = profile;
324 ASSERT_TRUE(parse_args_(argv));
325 }
326}
327
328// Invalid calls to the profile option.
329TEST_F(CliTest, invalid_profile) {
330 std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
331 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
332
333 argv[1] = "random-unknown-profile";
334 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
335}
336
337// Valid calls to the chroot option.
338TEST_F(CliTest, valid_chroot) {
339 std::vector<std::string> argv = {"-C", "/", "/bin/sh"};
340 ASSERT_TRUE(parse_args_(argv));
341}
342
343// Valid calls to the pivot root option.
344TEST_F(CliTest, valid_pivot_root) {
345 std::vector<std::string> argv = {"-P", "/", "/bin/sh"};
346 ASSERT_TRUE(parse_args_(argv));
347}
348
349// We cannot handle multiple options with chroot/profile/pivot root.
350TEST_F(CliTest, conflicting_roots) {
351 std::vector<std::string> argv;
352
353 // Chroot & pivot root.
354 argv = {"-C", "/", "-P", "/", "/bin/sh"};
355 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
356
357 // Chroot & minimalistic-mountns profile.
358 argv = {"-C", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
359 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
360
361 // Pivot root & minimalistic-mountns profile.
362 argv = {"-P", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
363 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
364}
365
366// Valid calls to the uidmap option.
367TEST_F(CliTest, valid_uidmap) {
368 std::vector<std::string> argv = {"-m", "/bin/sh"};
369 // Use a default map (no option from user).
370 ASSERT_TRUE(parse_args_(argv));
371
372 // Use a single map.
373 argv = {"-m0 0 1", "/bin/sh"};
374 ASSERT_TRUE(parse_args_(argv));
375
376 // Multiple maps.
377 argv = {"-m0 0 1,100 100 1", "/bin/sh"};
378 ASSERT_TRUE(parse_args_(argv));
379}
380
381// Valid calls to the gidmap option.
382TEST_F(CliTest, valid_gidmap) {
383 std::vector<std::string> argv = {"-M", "/bin/sh"};
384 // Use a default map (no option from user).
385 ASSERT_TRUE(parse_args_(argv));
386
387 // Use a single map.
388 argv = {"-M0 0 1", "/bin/sh"};
389 ASSERT_TRUE(parse_args_(argv));
390
391 // Multiple maps.
392 argv = {"-M0 0 1,100 100 1", "/bin/sh"};
393 ASSERT_TRUE(parse_args_(argv));
394}
395
396// Invalid calls to the uidmap/gidmap options.
397// Note: Can't really test these as all validation is delayed/left to the
398// runtime kernel. Minijail will simply write verbatim what the user gave
399// it to the corresponding /proc/.../[ug]id_map.
400
401// Valid calls to the binding option.
402TEST_F(CliTest, valid_binding) {
403 std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
404
405 // Dest & writable are optional.
406 argv[1] = "/";
407 ASSERT_TRUE(parse_args_(argv));
408
409 // Writable is optional.
410 argv[1] = "/,/";
411 ASSERT_TRUE(parse_args_(argv));
412
413 // Writable is an integer.
414 argv[1] = "/,/,0";
415 ASSERT_TRUE(parse_args_(argv));
416 argv[1] = "/,/,1";
417 ASSERT_TRUE(parse_args_(argv));
418
419 // Dest is optional.
420 argv[1] = "/,,0";
421 ASSERT_TRUE(parse_args_(argv));
422}
423
424// Invalid calls to the binding option.
425TEST_F(CliTest, invalid_binding) {
426 std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
427
428 // Missing source.
429 argv[2] = "";
430 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
431
432 // Too many args.
433 argv[2] = "/,/,0,what";
434 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
435
436 // Missing mount namespace/etc...
437 argv = {"-b", "/", "/bin/sh"};
438 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
David Coles87ec5cd2019-06-13 17:20:10 -0700439
440 // Bad value for <writable>.
441 argv = {"-b", "/,,writable", "/bin/sh"};
442 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500443}
444
445// Valid calls to the mount option.
446TEST_F(CliTest, valid_mount) {
447 std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
448
449 // Flags & data are optional.
450 argv[2] = "none,/,none";
451 ASSERT_TRUE(parse_args_(argv));
452
453 // Data is optional.
454 argv[2] = "none,/,none,0xe";
455 ASSERT_TRUE(parse_args_(argv));
456
457 // Flags are optional.
458 argv[2] = "none,/,none,,mode=755";
459 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4f3e09f2018-01-24 18:01:16 -0500460
461 // Multiple data options to the kernel.
462 argv[2] = "none,/,none,0xe,mode=755,uid=0,gid=10";
463 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger6f4e93d2018-05-23 05:05:35 -0400464
465 // Single MS constant.
466 argv[2] = "none,/,none,MS_NODEV,mode=755";
467 ASSERT_TRUE(parse_args_(argv));
468
469 // Multiple MS constants.
470 argv[2] = "none,/,none,MS_NODEV|MS_NOEXEC,mode=755";
471 ASSERT_TRUE(parse_args_(argv));
472
473 // Mixed constant & number.
474 argv[2] = "none,/,none,MS_NODEV|0xf,mode=755";
475 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500476}
477
478// Invalid calls to the mount option.
479TEST_F(CliTest, invalid_mount) {
480 std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
481
482 // Missing source.
483 argv[2] = "";
484 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
485
486 // Missing dest.
487 argv[2] = "none";
488 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
489
490 // Missing type.
491 argv[2] = "none,/";
492 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysinger6f4e93d2018-05-23 05:05:35 -0400493
494 // Unknown MS constant.
495 argv[2] = "none,/,none,MS_WHOOPS";
496 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500497}
Mike Frysinger785b1c32018-02-23 15:47:24 -0500498
499// Valid calls to the remount mode option.
500TEST_F(CliTest, valid_remount_mode) {
501 std::vector<std::string> argv = {"-v", "", "/bin/sh"};
502
503 // Mode is optional.
504 argv[1] = "-K";
505 ASSERT_TRUE(parse_args_(argv));
506
507 // This should list all valid modes.
508 const std::vector<std::string> modes = {
509 "shared",
510 "private",
511 "slave",
512 "unbindable",
513 };
514
515 for (const auto& mode : modes) {
516 argv[1] = "-K" + mode;
517 ASSERT_TRUE(parse_args_(argv));
518 }
519}
520
521// Invalid calls to the remount mode option.
522TEST_F(CliTest, invalid_remount_mode) {
523 std::vector<std::string> argv = {"-v", "", "/bin/sh"};
524
525 // Unknown mode.
526 argv[1] = "-Kfoo";
527 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
528}