• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* 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 
20 namespace {
21 
22 constexpr char kValidUser[] = "nobody";
23 constexpr char kValidUid[] = "100";
24 constexpr char kValidGroup[] = "users";
25 constexpr char kValidGid[] = "100";
26 
27 class CliTest : public ::testing::Test {
28  protected:
SetUp()29   virtual void SetUp() {
30     // 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   }
TearDown()34   virtual void TearDown() {}
35 
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).
parse_args_(const std::vector<std::string> & argv,int * exit_immediately,ElfType * elftype)39   int parse_args_(const std::vector<std::string>& argv,
40                   int* exit_immediately,
41                   ElfType* elftype) {
42     // 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;
46 
47     // 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 
51     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();
59 
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);
64     testing::internal::GetCapturedStdout();
65 
66     minijail_destroy(j);
67 
68     return ret;
69   }
70 
parse_args_(const std::vector<std::string> & argv)71   int parse_args_(const std::vector<std::string>& argv) {
72     return parse_args_(argv, &exit_immediately_, &elftype_);
73   }
74 
75   ElfType elftype_;
76   int exit_immediately_;
77 };
78 
79 }  // namespace
80 
81 // Should exit non-zero when there's no arguments.
TEST_F(CliTest,no_args)82 TEST_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.
TEST_F(CliTest,help)88 TEST_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.
TEST_F(CliTest,valid_program)100 TEST_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.
TEST_F(CliTest,valid_set_user)106 TEST_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.
TEST_F(CliTest,invalid_set_user)117 TEST_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), "");
126 
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");
131 }
132 
133 // Valid calls to the change group option.
TEST_F(CliTest,valid_set_group)134 TEST_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.
TEST_F(CliTest,invalid_set_group)145 TEST_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), "");
154 
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");
159 }
160 
161 // Valid calls to the add-suppl-group option.
TEST_F(CliTest,valid_add_supp_group)162 TEST_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.
TEST_F(CliTest,invalid_add_supp_group)179 TEST_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 
191 // Valid calls to the skip securebits option.
TEST_F(CliTest,valid_skip_securebits)192 TEST_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.
TEST_F(CliTest,invalid_skip_securebits)205 TEST_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.
TEST_F(CliTest,valid_caps)213 TEST_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.
TEST_F(CliTest,invalid_caps)226 TEST_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.
TEST_F(CliTest,valid_logging)234 TEST_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 
243   for (const auto profile : profiles) {
244     argv[1] = profile;
245     ASSERT_TRUE(parse_args_(argv));
246   }
247 }
248 
249 // Invalid calls to the logging option.
TEST_F(CliTest,invalid_logging)250 TEST_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.
TEST_F(CliTest,valid_rlimit)259 TEST_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));
264 
265   argv[1] = "0,0x100,4";
266   ASSERT_TRUE(parse_args_(argv));
267 
268   argv[1] = "1,1,unlimited";
269   ASSERT_TRUE(parse_args_(argv));
270 
271   argv[1] = "2,unlimited,2";
272   ASSERT_TRUE(parse_args_(argv));
273 
274   argv[1] = "RLIMIT_AS,unlimited,unlimited";
275   ASSERT_TRUE(parse_args_(argv));
276 }
277 
278 // Invalid calls to the rlimit option.
TEST_F(CliTest,invalid_rlimit)279 TEST_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 
295   // Non-numeric limits
296   argv[1] = "0,0,invalid-limit";
297   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
298 
299   // Invalid number.
300   argv[1] = "0,0,0j";
301   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
302 
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), "");
310 }
311 
312 // Valid calls to the profile option.
TEST_F(CliTest,valid_profile)313 TEST_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",
319     "minimalistic-mountns-nodev",
320   };
321 
322   for (const auto profile : profiles) {
323     argv[1] = profile;
324     ASSERT_TRUE(parse_args_(argv));
325   }
326 }
327 
328 // Invalid calls to the profile option.
TEST_F(CliTest,invalid_profile)329 TEST_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.
TEST_F(CliTest,valid_chroot)338 TEST_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.
TEST_F(CliTest,valid_pivot_root)344 TEST_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.
TEST_F(CliTest,conflicting_roots)350 TEST_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.
TEST_F(CliTest,valid_uidmap)367 TEST_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.
TEST_F(CliTest,valid_gidmap)382 TEST_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.
TEST_F(CliTest,valid_binding)402 TEST_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.
TEST_F(CliTest,invalid_binding)425 TEST_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), "");
439 
440   // Bad value for <writable>.
441   argv = {"-b", "/,,writable", "/bin/sh"};
442   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
443 }
444 
445 // Valid calls to the mount option.
TEST_F(CliTest,valid_mount)446 TEST_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));
460 
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));
464 
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));
476 }
477 
478 // Invalid calls to the mount option.
TEST_F(CliTest,invalid_mount)479 TEST_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), "");
493 
494   // Unknown MS constant.
495   argv[2] = "none,/,none,MS_WHOOPS";
496   ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
497 }
498 
499 // Valid calls to the remount mode option.
TEST_F(CliTest,valid_remount_mode)500 TEST_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.
TEST_F(CliTest,invalid_remount_mode)522 TEST_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 }
529