• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2018 The ChromiumOS Authors
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 <cstdio>
13 #include <fstream>
14 #include <stdio.h>
15 #include <stdlib.h>
16 
17 #include <gtest/gtest.h>
18 
19 #include "config_parser.h"
20 #include "libminijail.h"
21 #include "minijail0_cli.h"
22 #include "test_util.h"
23 
24 namespace {
25 
26 constexpr char kValidUser[] = "nobody";
27 constexpr char kValidUid[] = "100";
28 constexpr char kValidGroup[] = "users";
29 constexpr char kValidGid[] = "100";
30 
31 class CliTest : public ::testing::Test {
32  protected:
SetUp()33   virtual void SetUp() {
34     // Most tests do not care about this logic.  For the few that do, make
35     // them opt into it so they can validate specifically.
36     elftype_ = ELFDYNAMIC;
37   }
TearDown()38   virtual void TearDown() {}
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).
parse_args_(const std::vector<std::string> & argv,bool * exit_immediately,ElfType * elftype)43   int parse_args_(const std::vector<std::string>& argv,
44                   bool* exit_immediately,
45                   ElfType* elftype) {
46     // Make sure we reset the getopts state when scanning a new argv.  Setting
47     // this to 0 is a GNU extension, but AOSP/BSD also checks this (as an alias
48     // to their "optreset").
49     optind = 0;
50 
51     // We create & destroy this for every parse_args call because some API
52     // calls can dupe memory which confuses LSAN.  https://crbug.com/844615
53     struct minijail* j = minijail_new();
54 
55     std::vector<const char*> pargv;
56     pargv.push_back("minijail0");
57     for (const std::string& arg : argv)
58       pargv.push_back(arg.c_str());
59 
60     // We grab stdout from parse_args itself as it might dump things we don't
61     // usually care about like help output.
62     testing::internal::CaptureStdout();
63 
64     const char* preload_path = PRELOADPATH;
65     char** envp = NULL;
66     int ret =
67         parse_args(j, pargv.size(), const_cast<char* const*>(pargv.data()),
68                    NULL, exit_immediately, elftype, &preload_path, &envp);
69     testing::internal::GetCapturedStdout();
70 
71     minijail_destroy(j);
72 
73     return ret;
74   }
75 
parse_args_(const std::vector<std::string> & argv)76   int parse_args_(const std::vector<std::string>& argv) {
77     return parse_args_(argv, &exit_immediately_, &elftype_);
78   }
79 
80   ElfType elftype_;
81   bool exit_immediately_;
82 };
83 
84 }  // namespace
85 
86 // Should exit non-zero when there's no arguments.
TEST_F(CliTest,no_args)87 TEST_F(CliTest, no_args) {
88   std::vector<std::string> argv = {};
89   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
90 }
91 
92 // Should exit zero when we asked for help.
TEST_F(CliTest,help)93 TEST_F(CliTest, help) {
94   std::vector<std::string> argv = {"-h"};
95   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
96 
97   argv = {"--help"};
98   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
99 
100   argv = {"-H"};
101   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
102 }
103 
104 // Just a simple program to run.
TEST_F(CliTest,valid_program)105 TEST_F(CliTest, valid_program) {
106   std::vector<std::string> argv = {"/bin/sh"};
107   ASSERT_TRUE(parse_args_(argv));
108 }
109 
110 // Valid calls to the change user option.
TEST_F(CliTest,valid_set_user)111 TEST_F(CliTest, valid_set_user) {
112   std::vector<std::string> argv = {"-u", "", "/bin/sh"};
113 
114   argv[1] = kValidUser;
115   ASSERT_TRUE(parse_args_(argv));
116 
117   argv[1] = kValidUid;
118   ASSERT_TRUE(parse_args_(argv));
119 }
120 
121 // Invalid calls to the change user option.
TEST_F(CliTest,invalid_set_user)122 TEST_F(CliTest, invalid_set_user) {
123   std::vector<std::string> argv = {"-u", "", "/bin/sh"};
124   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
125 
126   argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
127   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
128 
129   argv[1] = "1000x";
130   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
131 
132   // Supplying -u more than once is bad.
133   argv = {"-u", kValidUser, "-u", kValidUid, "/bin/sh"};
134   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1),
135               "-u provided multiple times");
136 }
137 
138 // Valid calls to the change group option.
TEST_F(CliTest,valid_set_group)139 TEST_F(CliTest, valid_set_group) {
140   std::vector<std::string> argv = {"-g", "", "/bin/sh"};
141 
142   argv[1] = kValidGroup;
143   ASSERT_TRUE(parse_args_(argv));
144 
145   argv[1] = kValidGid;
146   ASSERT_TRUE(parse_args_(argv));
147 }
148 
149 // Invalid calls to the change group option.
TEST_F(CliTest,invalid_set_group)150 TEST_F(CliTest, invalid_set_group) {
151   std::vector<std::string> argv = {"-g", "", "/bin/sh"};
152   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
153 
154   argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
155   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
156 
157   argv[1] = "1000x";
158   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
159 
160   // Supplying -g more than once is bad.
161   argv = {"-g", kValidGroup, "-g", kValidGid, "/bin/sh"};
162   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1),
163               "-g provided multiple times");
164 }
165 
166 // Valid calls to the add-suppl-group option.
TEST_F(CliTest,valid_add_supp_group)167 TEST_F(CliTest, valid_add_supp_group) {
168   std::vector<std::string> argv = {"--add-suppl-group", "", "/bin/sh"};
169 
170   argv[1] = kValidGroup;
171   ASSERT_TRUE(parse_args_(argv));
172 
173   argv[1] = kValidGid;
174   ASSERT_TRUE(parse_args_(argv));
175 
176   std::vector<std::string> argv2 = {"--add-suppl-group", "",
177                                     "--add-suppl-group", "", "/bin/sh"};
178   argv[1] = kValidGroup;
179   argv[2] = kValidGid;
180   ASSERT_TRUE(parse_args_(argv));
181 }
182 
183 // Invalid calls to the add-suppl-group option.
TEST_F(CliTest,invalid_add_supp_group)184 TEST_F(CliTest, invalid_add_supp_group) {
185   std::vector<std::string> argv = {"--add-suppl-group", "", "/bin/sh"};
186 
187   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
188 
189   argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
190   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
191 
192   argv[1] = "1000x";
193   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
194 }
195 
196 // Valid calls to the skip securebits option.
TEST_F(CliTest,valid_skip_securebits)197 TEST_F(CliTest, valid_skip_securebits) {
198   // An empty string is the same as 0.
199   std::vector<std::string> argv = {"-B", "", "/bin/sh"};
200   ASSERT_TRUE(parse_args_(argv));
201 
202   argv[1] = "0xAB";
203   ASSERT_TRUE(parse_args_(argv));
204 
205   argv[1] = "1234";
206   ASSERT_TRUE(parse_args_(argv));
207 }
208 
209 // Invalid calls to the skip securebits option.
TEST_F(CliTest,invalid_skip_securebits)210 TEST_F(CliTest, invalid_skip_securebits) {
211   std::vector<std::string> argv = {"-B", "", "/bin/sh"};
212 
213   argv[1] = "xja";
214   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
215 }
216 
217 // Valid calls to the caps option.
TEST_F(CliTest,valid_caps)218 TEST_F(CliTest, valid_caps) {
219   // An empty string is the same as 0.
220   std::vector<std::string> argv = {"-c", "", "/bin/sh"};
221   ASSERT_TRUE(parse_args_(argv));
222 
223   argv[1] = "0xAB";
224   ASSERT_TRUE(parse_args_(argv));
225 
226   argv[1] = "1234";
227   ASSERT_TRUE(parse_args_(argv));
228 }
229 
230 // Invalid calls to the caps option.
TEST_F(CliTest,invalid_caps)231 TEST_F(CliTest, invalid_caps) {
232   std::vector<std::string> argv = {"-c", "", "/bin/sh"};
233 
234   argv[1] = "xja";
235   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
236 }
237 
238 // Valid calls to the logging option.
TEST_F(CliTest,valid_logging)239 TEST_F(CliTest, valid_logging) {
240   std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
241 
242   // This should list all valid logging targets.
243   const std::vector<std::string> profiles = {
244       "stderr",
245       "syslog",
246   };
247 
248   for (const auto& profile : profiles) {
249     argv[1] = profile;
250     ASSERT_TRUE(parse_args_(argv));
251   }
252 }
253 
254 // Invalid calls to the logging option.
TEST_F(CliTest,invalid_logging)255 TEST_F(CliTest, invalid_logging) {
256   std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
257   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
258 
259   argv[1] = "stdout";
260   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
261 }
262 
263 // Valid calls to the rlimit option.
TEST_F(CliTest,valid_rlimit)264 TEST_F(CliTest, valid_rlimit) {
265   std::vector<std::string> argv = {"-R", "", "/bin/sh"};
266 
267   argv[1] = "0,1,2";
268   ASSERT_TRUE(parse_args_(argv));
269 
270   argv[1] = "0,0x100,4";
271   ASSERT_TRUE(parse_args_(argv));
272 
273   argv[1] = "1,1,unlimited";
274   ASSERT_TRUE(parse_args_(argv));
275 
276   argv[1] = "2,unlimited,2";
277   ASSERT_TRUE(parse_args_(argv));
278 
279   argv[1] = "RLIMIT_AS,unlimited,unlimited";
280   ASSERT_TRUE(parse_args_(argv));
281 }
282 
283 // Invalid calls to the rlimit option.
TEST_F(CliTest,invalid_rlimit)284 TEST_F(CliTest, invalid_rlimit) {
285   std::vector<std::string> argv = {"-R", "", "/bin/sh"};
286   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
287 
288   // Missing cur & max.
289   argv[1] = "0";
290   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
291 
292   // Missing max.
293   argv[1] = "0,0";
294   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
295 
296   // Too many options.
297   argv[1] = "0,0,0,0";
298   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
299 
300   // Non-numeric limits
301   argv[1] = "0,0,invalid-limit";
302   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
303 
304   // Invalid number.
305   argv[1] = "0,0,0j";
306   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
307 
308   // Invalid hex number.
309   argv[1] = "0,0x1jf,0";
310   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
311 
312   // Invalid rlimit constant.
313   argv[1] = "RLIMIT_GOGOOGOG,0,0";
314   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
315 }
316 
317 // Valid calls to the profile option.
TEST_F(CliTest,valid_profile)318 TEST_F(CliTest, valid_profile) {
319   std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
320 
321   // This should list all valid profiles.
322   const std::vector<std::string> profiles = {
323       "minimalistic-mountns",
324       "minimalistic-mountns-nodev",
325   };
326 
327   for (const auto& profile : profiles) {
328     argv[1] = profile;
329     ASSERT_TRUE(parse_args_(argv));
330   }
331 }
332 
333 // Invalid calls to the profile option.
TEST_F(CliTest,invalid_profile)334 TEST_F(CliTest, invalid_profile) {
335   std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
336   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
337 
338   argv[1] = "random-unknown-profile";
339   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
340 }
341 
342 // Valid usage of the no-fs-restrictions option.
TEST_F(CliTest,valid_no_fs_restrictions)343 TEST_F(CliTest, valid_no_fs_restrictions) {
344   std::vector<std::string> argv = {"--profile", "minimalistic-mountns",
345                                    "--no-fs-restrictions", "/bin/sh"};
346 
347   ASSERT_TRUE(parse_args_(argv));
348 }
349 
350 // Invalid usage of the no-fs-restrictions option.
TEST_F(CliTest,invalid_no_fs_restrictions)351 TEST_F(CliTest, invalid_no_fs_restrictions) {
352   // Using an fs-path-* flag at the same time shouldn't be allowed.
353   std::vector<std::string> argv = {"--fs-path-rx", "/", "--no-fs-restrictions",
354                                    "/bin/sh"};
355 
356   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
357 }
358 
359 // Valid calls to the chroot option.
TEST_F(CliTest,valid_chroot)360 TEST_F(CliTest, valid_chroot) {
361   std::vector<std::string> argv = {"-C", "/", "/bin/sh"};
362   ASSERT_TRUE(parse_args_(argv));
363 }
364 
365 // Valid calls to the pivot root option.
TEST_F(CliTest,valid_pivot_root)366 TEST_F(CliTest, valid_pivot_root) {
367   std::vector<std::string> argv = {"-P", "/", "/bin/sh"};
368   ASSERT_TRUE(parse_args_(argv));
369 }
370 
371 // We cannot handle multiple options with chroot/profile/pivot root.
TEST_F(CliTest,conflicting_roots)372 TEST_F(CliTest, conflicting_roots) {
373   std::vector<std::string> argv;
374 
375   // Chroot & pivot root.
376   argv = {"-C", "/", "-P", "/", "/bin/sh"};
377   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
378 
379   // Chroot & minimalistic-mountns profile.
380   argv = {"-C", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
381   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
382 
383   // Pivot root & minimalistic-mountns profile.
384   argv = {"-P", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
385   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
386 }
387 
388 // Valid calls to the uidmap option.
TEST_F(CliTest,valid_uidmap)389 TEST_F(CliTest, valid_uidmap) {
390   std::vector<std::string> argv = {"-m", "/bin/sh"};
391   // Use a default map (no option from user).
392   ASSERT_TRUE(parse_args_(argv));
393 
394   // Use a single map.
395   argv = {"-m0 0 1", "/bin/sh"};
396   ASSERT_TRUE(parse_args_(argv));
397 
398   // Multiple maps.
399   argv = {"-m0 0 1,100 100 1", "/bin/sh"};
400   ASSERT_TRUE(parse_args_(argv));
401 }
402 
403 // Valid calls to the gidmap option.
TEST_F(CliTest,valid_gidmap)404 TEST_F(CliTest, valid_gidmap) {
405   std::vector<std::string> argv = {"-M", "/bin/sh"};
406   // Use a default map (no option from user).
407   ASSERT_TRUE(parse_args_(argv));
408 
409   // Use a single map.
410   argv = {"-M0 0 1", "/bin/sh"};
411   ASSERT_TRUE(parse_args_(argv));
412 
413   // Multiple maps.
414   argv = {"-M0 0 1,100 100 1", "/bin/sh"};
415   ASSERT_TRUE(parse_args_(argv));
416 }
417 
418 // Invalid calls to the uidmap/gidmap options.
419 // Note: Can't really test these as all validation is delayed/left to the
420 // runtime kernel.  Minijail will simply write verbatim what the user gave
421 // it to the corresponding /proc/.../[ug]id_map.
422 
423 // Valid calls to the binding option.
TEST_F(CliTest,valid_binding)424 TEST_F(CliTest, valid_binding) {
425   std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
426 
427   // Dest & writable are optional.
428   argv[1] = "/";
429   ASSERT_TRUE(parse_args_(argv));
430 
431   // Writable is optional.
432   argv[1] = "/,/";
433   ASSERT_TRUE(parse_args_(argv));
434 
435   // Writable is an integer.
436   argv[1] = "/,/,0";
437   ASSERT_TRUE(parse_args_(argv));
438   argv[1] = "/,/,1";
439   ASSERT_TRUE(parse_args_(argv));
440 
441   // Dest is optional.
442   argv[1] = "/,,0";
443   ASSERT_TRUE(parse_args_(argv));
444 }
445 
446 // Invalid calls to the binding option.
TEST_F(CliTest,invalid_binding)447 TEST_F(CliTest, invalid_binding) {
448   std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
449 
450   // Missing source.
451   argv[2] = "";
452   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
453 
454   // Too many args.
455   argv[2] = "/,/,0,what";
456   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
457 
458   // Missing mount namespace/etc...
459   argv = {"-b", "/", "/bin/sh"};
460   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
461 
462   // Bad value for <writable>.
463   argv = {"-b", "/,,writable", "/bin/sh"};
464   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
465 }
466 
467 // Valid calls to the mount option.
TEST_F(CliTest,valid_mount)468 TEST_F(CliTest, valid_mount) {
469   std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
470 
471   // Flags & data are optional.
472   argv[2] = "none,/,none";
473   ASSERT_TRUE(parse_args_(argv));
474 
475   // Data is optional.
476   argv[2] = "none,/,none,0xe";
477   ASSERT_TRUE(parse_args_(argv));
478 
479   // Flags are optional.
480   argv[2] = "none,/,none,,mode=755";
481   ASSERT_TRUE(parse_args_(argv));
482 
483   // Multiple data options to the kernel.
484   argv[2] = "none,/,none,0xe,mode=755,uid=0,gid=10";
485   ASSERT_TRUE(parse_args_(argv));
486 
487   // Single MS constant.
488   argv[2] = "none,/,none,MS_NODEV,mode=755";
489   ASSERT_TRUE(parse_args_(argv));
490 
491   // Multiple MS constants.
492   argv[2] = "none,/,none,MS_NODEV|MS_NOEXEC,mode=755";
493   ASSERT_TRUE(parse_args_(argv));
494 
495   // Mixed constant & number.
496   argv[2] = "none,/,none,MS_NODEV|0xf,mode=755";
497   ASSERT_TRUE(parse_args_(argv));
498 }
499 
500 // Invalid calls to the mount option.
TEST_F(CliTest,invalid_mount)501 TEST_F(CliTest, invalid_mount) {
502   std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
503 
504   // Missing source.
505   argv[2] = "";
506   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
507 
508   // Missing dest.
509   argv[2] = "none";
510   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
511 
512   // Missing type.
513   argv[2] = "none,/";
514   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
515 
516   // Unknown MS constant.
517   argv[2] = "none,/,none,MS_WHOOPS";
518   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
519 }
520 
521 // Valid calls to the remount mode option.
TEST_F(CliTest,valid_remount_mode)522 TEST_F(CliTest, valid_remount_mode) {
523   std::vector<std::string> argv = {"-v", "", "/bin/sh"};
524 
525   // Mode is optional.
526   argv[1] = "-K";
527   ASSERT_TRUE(parse_args_(argv));
528 
529   // This should list all valid modes.
530   const std::vector<std::string> modes = {
531       "shared",
532       "private",
533       "slave",
534       "unbindable",
535   };
536 
537   for (const auto& mode : modes) {
538     argv[1] = "-K" + mode;
539     ASSERT_TRUE(parse_args_(argv));
540   }
541 }
542 
543 // Invalid calls to the remount mode option.
TEST_F(CliTest,invalid_remount_mode)544 TEST_F(CliTest, invalid_remount_mode) {
545   std::vector<std::string> argv = {"-v", "", "/bin/sh"};
546 
547   // Unknown mode.
548   argv[1] = "-Kfoo";
549   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
550 }
551 
TEST_F(CliTest,invalid_L_combo)552 TEST_F(CliTest, invalid_L_combo) {
553   std::vector<std::string> argv = {"", "", "", "/bin/sh"};
554 
555   // Cannot call minijail0 with -L and a pre-compiled seccomp policy.
556   argv[0] = "-L";
557   argv[1] = "--seccomp-bpf-binary";
558   argv[2] = "source";
559   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
560 
561   argv[0] = "--seccomp-bpf-binary";
562   argv[1] = "source";
563   argv[2] = "-L";
564   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
565 }
566 
567 // Valid calls to the clear env option.
TEST_F(CliTest,valid_clear_env)568 TEST_F(CliTest, valid_clear_env) {
569   std::vector<std::string> argv = {"--env-reset", "/bin/sh"};
570 
571   ASSERT_TRUE(parse_args_(argv));
572 }
573 
574 // Valid calls to the set env option.
TEST_F(CliTest,valid_set_env)575 TEST_F(CliTest, valid_set_env) {
576   std::vector<std::string> argv1 = {"--env-add", "NAME=value", "/bin/sh"};
577   ASSERT_TRUE(parse_args_(argv1));
578 
579   // multiple occurences are allowed.
580   std::vector<std::string> argv2 = {"--env-add", "A=b", "--env-add", "b=C=D",
581                                     "/bin/sh"};
582   ASSERT_TRUE(parse_args_(argv2));
583 
584   // --env-reset before any --env-add to not pass our own env.
585   std::vector<std::string> argv3 = {"--env-reset", "--env-add", "A=b",
586                                     "/bin/sh"};
587   ASSERT_TRUE(parse_args_(argv3));
588 
589   // --env-add before an --env-reset doesn't have any effect, but is allowed.
590   std::vector<std::string> argv4 = {"--env-add", "A=b", "--env-reset",
591                                     "/bin/sh"};
592   ASSERT_TRUE(parse_args_(argv4));
593 }
594 
595 // Invalid calls to the set env options.
TEST_F(CliTest,invalid_set_env)596 TEST_F(CliTest, invalid_set_env) {
597   // invalid env=value arguments.
598   std::vector<std::string> argv2 = {"--env-add", "", "/bin/sh"};
599 
600   argv2[1] = "INVALID";
601   ASSERT_EXIT(parse_args_(argv2), testing::ExitedWithCode(1), "");
602 
603   argv2[1] = "=";
604   ASSERT_EXIT(parse_args_(argv2), testing::ExitedWithCode(1), "");
605 
606   argv2[1] = "=foo";
607   ASSERT_EXIT(parse_args_(argv2), testing::ExitedWithCode(1), "");
608 }
609 
610 // Valid calls to the gen-config option.
TEST_F(CliTest,valid_gen_config)611 TEST_F(CliTest, valid_gen_config) {
612   std::string config_path = std::tmpnam(NULL);
613   std::vector<std::string> argv = {"--gen-config=" + config_path, "--ambient",
614                                    "--fs-path-rx=/", "-n"};
615   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
616 }
617 
TEST_F(CliTest,valid_gen_config_with_users_mounts_not_found)618 TEST_F(CliTest, valid_gen_config_with_users_mounts_not_found) {
619   std::string config_path = std::tmpnam(NULL);
620   std::vector<std::string> argv = {"--gen-config=" + config_path,
621                                    "--ambient",
622                                    "-u fake-user",
623                                    "-g fake-group",
624                                    "-b /fake-mount",
625                                    "-k /fake-mount",
626                                    "-e /fake-path",
627                                    "-f /fake-path",
628                                    "-V /fake-path",
629                                    "-n"};
630   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
631 }
632 
633 // Invalid calls to the gen-config option.
TEST_F(CliTest,invalid_gen_config)634 TEST_F(CliTest, invalid_gen_config) {
635   std::vector<std::string> argv = {"--gen-config=/", "--ambient",
636                                    "--fs-path-rx=/", "-n"};
637   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
638 }
639 
TEST_F(CliTest,invalid_gen_config_not_writable)640 TEST_F(CliTest, invalid_gen_config_not_writable) {
641   std::vector<std::string> argv = {"--gen-config=/sys/foo", "--ambient",
642                                    "--fs-path-rx=/", "-n"};
643   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
644 }
645 
646 // Android unit tests do not support data file yet.
647 #if !defined(__ANDROID__)
648 
TEST_F(CliTest,conf_parsing_invalid_key)649 TEST_F(CliTest, conf_parsing_invalid_key) {
650   std::vector<std::string> argv = {"--config", source_path("test/invalid.conf"),
651                                    "/bin/sh"};
652 
653   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
654 }
655 
TEST_F(CliTest,conf_parsing)656 TEST_F(CliTest, conf_parsing) {
657   std::vector<std::string> argv = {"--config", source_path("test/valid.conf"),
658                                    "/bin/sh"};
659 
660   ASSERT_TRUE(parse_args_(argv));
661 }
662 
TEST_F(CliTest,conf_parsing_seccomp)663 TEST_F(CliTest, conf_parsing_seccomp) {
664   std::string seccomp_path = std::tmpnam(NULL);
665   std::ofstream seccomp_stream(seccomp_path);
666   // Intentionally empty policy.
667   std::string config_path = std::tmpnam(NULL);
668   std::ofstream config_stream(config_path);
669   config_stream << "% minijail-config-file v0\n"
670                    "S = " +
671                        seccomp_path;
672   config_stream.flush();
673 
674   std::vector<std::string> argv = {"--config", config_path, "/bin/sh"};
675 
676   ASSERT_TRUE(parse_args_(argv));
677 }
678 
TEST_F(CliTest,conf_parsing_with_dac_override)679 TEST_F(CliTest, conf_parsing_with_dac_override) {
680   std::vector<std::string> argv = {"-c 2", "--config",
681                                    source_path("test/valid.conf"), "/bin/sh"};
682 
683   ASSERT_TRUE(parse_args_(argv));
684 }
685 
TEST_F(CliTest,conf_fs_path)686 TEST_F(CliTest, conf_fs_path) {
687   std::vector<std::string> argv = {
688       "-c 2", "--config", source_path("test/landlock.conf"), "/bin/sh"};
689 
690   ASSERT_TRUE(parse_args_(argv));
691 }
692 
693 #endif  // !__ANDROID__
694