blob: 0d6a07d4f8bc5f9dce14fbdb5310e0e1071d6c9c [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
161// Valid calls to the skip securebits option.
162TEST_F(CliTest, valid_skip_securebits) {
163 // An empty string is the same as 0.
164 std::vector<std::string> argv = {"-B", "", "/bin/sh"};
165 ASSERT_TRUE(parse_args_(argv));
166
167 argv[1] = "0xAB";
168 ASSERT_TRUE(parse_args_(argv));
169
170 argv[1] = "1234";
171 ASSERT_TRUE(parse_args_(argv));
172}
173
174// Invalid calls to the skip securebits option.
175TEST_F(CliTest, invalid_skip_securebits) {
176 std::vector<std::string> argv = {"-B", "", "/bin/sh"};
177
178 argv[1] = "xja";
179 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
180}
181
182// Valid calls to the caps option.
183TEST_F(CliTest, valid_caps) {
184 // An empty string is the same as 0.
185 std::vector<std::string> argv = {"-c", "", "/bin/sh"};
186 ASSERT_TRUE(parse_args_(argv));
187
188 argv[1] = "0xAB";
189 ASSERT_TRUE(parse_args_(argv));
190
191 argv[1] = "1234";
192 ASSERT_TRUE(parse_args_(argv));
193}
194
195// Invalid calls to the caps option.
196TEST_F(CliTest, invalid_caps) {
197 std::vector<std::string> argv = {"-c", "", "/bin/sh"};
198
199 argv[1] = "xja";
200 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
201}
202
203// Valid calls to the logging option.
204TEST_F(CliTest, valid_logging) {
205 std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
206
207 // This should list all valid logging targets.
208 const std::vector<std::string> profiles = {
209 "stderr",
210 "syslog",
211 };
212
213 for (const auto profile : profiles) {
214 argv[1] = profile;
215 ASSERT_TRUE(parse_args_(argv));
216 }
217}
218
219// Invalid calls to the logging option.
220TEST_F(CliTest, invalid_logging) {
221 std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
222 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
223
224 argv[1] = "stdout";
225 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
226}
227
228// Valid calls to the rlimit option.
229TEST_F(CliTest, valid_rlimit) {
230 std::vector<std::string> argv = {"-R", "", "/bin/sh"};
231
232 argv[1] = "0,1,2";
233 ASSERT_TRUE(parse_args_(argv));
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800234
Mike Frysingere34d7fe2018-05-23 04:18:30 -0400235 argv[1] = "0,0x100,4";
236 ASSERT_TRUE(parse_args_(argv));
237
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800238 argv[1] = "1,1,unlimited";
239 ASSERT_TRUE(parse_args_(argv));
240
241 argv[1] = "2,unlimited,2";
242 ASSERT_TRUE(parse_args_(argv));
Mike Frysingere34d7fe2018-05-23 04:18:30 -0400243
244 argv[1] = "RLIMIT_AS,unlimited,unlimited";
245 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500246}
247
248// Invalid calls to the rlimit option.
249TEST_F(CliTest, invalid_rlimit) {
250 std::vector<std::string> argv = {"-R", "", "/bin/sh"};
251 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
252
253 // Missing cur & max.
254 argv[1] = "0";
255 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
256
257 // Missing max.
258 argv[1] = "0,0";
259 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
260
261 // Too many options.
262 argv[1] = "0,0,0,0";
263 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
264
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800265 // Non-numeric limits
266 argv[1] = "0,0,invalid-limit";
267 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
268
Mike Frysingere34d7fe2018-05-23 04:18:30 -0400269 // Invalid number.
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800270 argv[1] = "0,0,0j";
271 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysingere34d7fe2018-05-23 04:18:30 -0400272
273 // Invalid hex number.
274 argv[1] = "0,0x1jf,0";
275 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
276
277 // Invalid rlimit constant.
278 argv[1] = "RLIMIT_GOGOOGOG,0,0";
279 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500280}
281
282// Valid calls to the profile option.
283TEST_F(CliTest, valid_profile) {
284 std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
285
286 // This should list all valid profiles.
287 const std::vector<std::string> profiles = {
288 "minimalistic-mountns",
289 };
290
291 for (const auto profile : profiles) {
292 argv[1] = profile;
293 ASSERT_TRUE(parse_args_(argv));
294 }
295}
296
297// Invalid calls to the profile option.
298TEST_F(CliTest, invalid_profile) {
299 std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
300 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
301
302 argv[1] = "random-unknown-profile";
303 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
304}
305
306// Valid calls to the chroot option.
307TEST_F(CliTest, valid_chroot) {
308 std::vector<std::string> argv = {"-C", "/", "/bin/sh"};
309 ASSERT_TRUE(parse_args_(argv));
310}
311
312// Valid calls to the pivot root option.
313TEST_F(CliTest, valid_pivot_root) {
314 std::vector<std::string> argv = {"-P", "/", "/bin/sh"};
315 ASSERT_TRUE(parse_args_(argv));
316}
317
318// We cannot handle multiple options with chroot/profile/pivot root.
319TEST_F(CliTest, conflicting_roots) {
320 std::vector<std::string> argv;
321
322 // Chroot & pivot root.
323 argv = {"-C", "/", "-P", "/", "/bin/sh"};
324 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
325
326 // Chroot & minimalistic-mountns profile.
327 argv = {"-C", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
328 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
329
330 // Pivot root & minimalistic-mountns profile.
331 argv = {"-P", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
332 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
333}
334
335// Valid calls to the uidmap option.
336TEST_F(CliTest, valid_uidmap) {
337 std::vector<std::string> argv = {"-m", "/bin/sh"};
338 // Use a default map (no option from user).
339 ASSERT_TRUE(parse_args_(argv));
340
341 // Use a single map.
342 argv = {"-m0 0 1", "/bin/sh"};
343 ASSERT_TRUE(parse_args_(argv));
344
345 // Multiple maps.
346 argv = {"-m0 0 1,100 100 1", "/bin/sh"};
347 ASSERT_TRUE(parse_args_(argv));
348}
349
350// Valid calls to the gidmap option.
351TEST_F(CliTest, valid_gidmap) {
352 std::vector<std::string> argv = {"-M", "/bin/sh"};
353 // Use a default map (no option from user).
354 ASSERT_TRUE(parse_args_(argv));
355
356 // Use a single map.
357 argv = {"-M0 0 1", "/bin/sh"};
358 ASSERT_TRUE(parse_args_(argv));
359
360 // Multiple maps.
361 argv = {"-M0 0 1,100 100 1", "/bin/sh"};
362 ASSERT_TRUE(parse_args_(argv));
363}
364
365// Invalid calls to the uidmap/gidmap options.
366// Note: Can't really test these as all validation is delayed/left to the
367// runtime kernel. Minijail will simply write verbatim what the user gave
368// it to the corresponding /proc/.../[ug]id_map.
369
370// Valid calls to the binding option.
371TEST_F(CliTest, valid_binding) {
372 std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
373
374 // Dest & writable are optional.
375 argv[1] = "/";
376 ASSERT_TRUE(parse_args_(argv));
377
378 // Writable is optional.
379 argv[1] = "/,/";
380 ASSERT_TRUE(parse_args_(argv));
381
382 // Writable is an integer.
383 argv[1] = "/,/,0";
384 ASSERT_TRUE(parse_args_(argv));
385 argv[1] = "/,/,1";
386 ASSERT_TRUE(parse_args_(argv));
387
388 // Dest is optional.
389 argv[1] = "/,,0";
390 ASSERT_TRUE(parse_args_(argv));
391}
392
393// Invalid calls to the binding option.
394TEST_F(CliTest, invalid_binding) {
395 std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
396
397 // Missing source.
398 argv[2] = "";
399 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
400
401 // Too many args.
402 argv[2] = "/,/,0,what";
403 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
404
405 // Missing mount namespace/etc...
406 argv = {"-b", "/", "/bin/sh"};
407 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
David Coles87ec5cd2019-06-13 17:20:10 -0700408
409 // Bad value for <writable>.
410 argv = {"-b", "/,,writable", "/bin/sh"};
411 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500412}
413
414// Valid calls to the mount option.
415TEST_F(CliTest, valid_mount) {
416 std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
417
418 // Flags & data are optional.
419 argv[2] = "none,/,none";
420 ASSERT_TRUE(parse_args_(argv));
421
422 // Data is optional.
423 argv[2] = "none,/,none,0xe";
424 ASSERT_TRUE(parse_args_(argv));
425
426 // Flags are optional.
427 argv[2] = "none,/,none,,mode=755";
428 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4f3e09f2018-01-24 18:01:16 -0500429
430 // Multiple data options to the kernel.
431 argv[2] = "none,/,none,0xe,mode=755,uid=0,gid=10";
432 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger6f4e93d2018-05-23 05:05:35 -0400433
434 // Single MS constant.
435 argv[2] = "none,/,none,MS_NODEV,mode=755";
436 ASSERT_TRUE(parse_args_(argv));
437
438 // Multiple MS constants.
439 argv[2] = "none,/,none,MS_NODEV|MS_NOEXEC,mode=755";
440 ASSERT_TRUE(parse_args_(argv));
441
442 // Mixed constant & number.
443 argv[2] = "none,/,none,MS_NODEV|0xf,mode=755";
444 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500445}
446
447// Invalid calls to the mount option.
448TEST_F(CliTest, invalid_mount) {
449 std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
450
451 // Missing source.
452 argv[2] = "";
453 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
454
455 // Missing dest.
456 argv[2] = "none";
457 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
458
459 // Missing type.
460 argv[2] = "none,/";
461 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysinger6f4e93d2018-05-23 05:05:35 -0400462
463 // Unknown MS constant.
464 argv[2] = "none,/,none,MS_WHOOPS";
465 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500466}
Mike Frysinger785b1c32018-02-23 15:47:24 -0500467
468// Valid calls to the remount mode option.
469TEST_F(CliTest, valid_remount_mode) {
470 std::vector<std::string> argv = {"-v", "", "/bin/sh"};
471
472 // Mode is optional.
473 argv[1] = "-K";
474 ASSERT_TRUE(parse_args_(argv));
475
476 // This should list all valid modes.
477 const std::vector<std::string> modes = {
478 "shared",
479 "private",
480 "slave",
481 "unbindable",
482 };
483
484 for (const auto& mode : modes) {
485 argv[1] = "-K" + mode;
486 ASSERT_TRUE(parse_args_(argv));
487 }
488}
489
490// Invalid calls to the remount mode option.
491TEST_F(CliTest, invalid_remount_mode) {
492 std::vector<std::string> argv = {"-v", "", "/bin/sh"};
493
494 // Unknown mode.
495 argv[1] = "-Kfoo";
496 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
497}