blob: f74bb3d8889dc1c330eb018d6493aa09e53865c4 [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) {
Mike Frysinger97413722018-02-05 13:39:39 -050045 // Make sure we reset the getopts state when scanning a new argv. Setting
46 // this to 0 is a GNU extension, but AOSP/BSD also checks this (as an alias
47 // to their "optreset").
48 optind = 0;
Mike Frysinger4d2a81e2018-01-22 16:43:33 -050049
50 std::vector<const char *> pargv;
51 pargv.push_back("minijail0");
52 for (const std::string& arg : argv)
53 pargv.push_back(arg.c_str());
54
55 // We grab stdout from parse_args itself as it might dump things we don't
56 // usually care about like help output.
57 testing::internal::CaptureStdout();
58 int ret = parse_args(j_, pargv.size(),
59 const_cast<char* const*>(pargv.data()),
60 exit_immediately, elftype);
61 testing::internal::GetCapturedStdout();
62
63 return ret;
64 }
65
66 int parse_args_(const std::vector<std::string>& argv) {
67 return parse_args_(argv, &exit_immediately_, &elftype_);
68 }
69
70 struct minijail *j_;
71 ElfType elftype_;
72 int exit_immediately_;
73};
74
75} // namespace
76
77// Should exit non-zero when there's no arguments.
78TEST_F(CliTest, no_args) {
79 std::vector<std::string> argv = {};
80 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
81}
82
83// Should exit zero when we asked for help.
84TEST_F(CliTest, help) {
85 std::vector<std::string> argv = {"-h"};
86 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
87
88 argv = {"--help"};
89 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
90
91 argv = {"-H"};
92 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
93}
94
95// Just a simple program to run.
96TEST_F(CliTest, valid_program) {
97 std::vector<std::string> argv = {"/bin/sh"};
98 ASSERT_TRUE(parse_args_(argv));
99}
100
101// Valid calls to the change user option.
102TEST_F(CliTest, valid_set_user) {
103 std::vector<std::string> argv = {"-u", "", "/bin/sh"};
104
105 argv[1] = kValidUser;
106 ASSERT_TRUE(parse_args_(argv));
107
108 argv[1] = kValidUid;
109 ASSERT_TRUE(parse_args_(argv));
110}
111
112// Invalid calls to the change user option.
113TEST_F(CliTest, invalid_set_user) {
114 std::vector<std::string> argv = {"-u", "", "/bin/sh"};
115 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
116
117 argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
118 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
119
120 argv[1] = "1000x";
121 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
122}
123
124// Valid calls to the change group option.
125TEST_F(CliTest, valid_set_group) {
126 std::vector<std::string> argv = {"-g", "", "/bin/sh"};
127
128 argv[1] = kValidGroup;
129 ASSERT_TRUE(parse_args_(argv));
130
131 argv[1] = kValidGid;
132 ASSERT_TRUE(parse_args_(argv));
133}
134
135// Invalid calls to the change group option.
136TEST_F(CliTest, invalid_set_group) {
137 std::vector<std::string> argv = {"-g", "", "/bin/sh"};
138 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
139
140 argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
141 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
142
143 argv[1] = "1000x";
144 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
145}
146
147// Valid calls to the skip securebits option.
148TEST_F(CliTest, valid_skip_securebits) {
149 // An empty string is the same as 0.
150 std::vector<std::string> argv = {"-B", "", "/bin/sh"};
151 ASSERT_TRUE(parse_args_(argv));
152
153 argv[1] = "0xAB";
154 ASSERT_TRUE(parse_args_(argv));
155
156 argv[1] = "1234";
157 ASSERT_TRUE(parse_args_(argv));
158}
159
160// Invalid calls to the skip securebits option.
161TEST_F(CliTest, invalid_skip_securebits) {
162 std::vector<std::string> argv = {"-B", "", "/bin/sh"};
163
164 argv[1] = "xja";
165 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
166}
167
168// Valid calls to the caps option.
169TEST_F(CliTest, valid_caps) {
170 // An empty string is the same as 0.
171 std::vector<std::string> argv = {"-c", "", "/bin/sh"};
172 ASSERT_TRUE(parse_args_(argv));
173
174 argv[1] = "0xAB";
175 ASSERT_TRUE(parse_args_(argv));
176
177 argv[1] = "1234";
178 ASSERT_TRUE(parse_args_(argv));
179}
180
181// Invalid calls to the caps option.
182TEST_F(CliTest, invalid_caps) {
183 std::vector<std::string> argv = {"-c", "", "/bin/sh"};
184
185 argv[1] = "xja";
186 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
187}
188
189// Valid calls to the logging option.
190TEST_F(CliTest, valid_logging) {
191 std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
192
193 // This should list all valid logging targets.
194 const std::vector<std::string> profiles = {
195 "stderr",
196 "syslog",
197 };
198
199 for (const auto profile : profiles) {
200 argv[1] = profile;
201 ASSERT_TRUE(parse_args_(argv));
202 }
203}
204
205// Invalid calls to the logging option.
206TEST_F(CliTest, invalid_logging) {
207 std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
208 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
209
210 argv[1] = "stdout";
211 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
212}
213
214// Valid calls to the rlimit option.
215TEST_F(CliTest, valid_rlimit) {
216 std::vector<std::string> argv = {"-R", "", "/bin/sh"};
217
218 argv[1] = "0,1,2";
219 ASSERT_TRUE(parse_args_(argv));
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800220
221 argv[1] = "1,1,unlimited";
222 ASSERT_TRUE(parse_args_(argv));
223
224 argv[1] = "2,unlimited,2";
225 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500226}
227
228// Invalid calls to the rlimit option.
229TEST_F(CliTest, invalid_rlimit) {
230 std::vector<std::string> argv = {"-R", "", "/bin/sh"};
231 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
232
233 // Missing cur & max.
234 argv[1] = "0";
235 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
236
237 // Missing max.
238 argv[1] = "0,0";
239 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
240
241 // Too many options.
242 argv[1] = "0,0,0,0";
243 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
244
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800245 // Non-numeric limits
246 argv[1] = "0,0,invalid-limit";
247 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
248
249 argv[1] = "0,0,0j";
250 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500251}
252
253// Valid calls to the profile option.
254TEST_F(CliTest, valid_profile) {
255 std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
256
257 // This should list all valid profiles.
258 const std::vector<std::string> profiles = {
259 "minimalistic-mountns",
260 };
261
262 for (const auto profile : profiles) {
263 argv[1] = profile;
264 ASSERT_TRUE(parse_args_(argv));
265 }
266}
267
268// Invalid calls to the profile option.
269TEST_F(CliTest, invalid_profile) {
270 std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
271 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
272
273 argv[1] = "random-unknown-profile";
274 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
275}
276
277// Valid calls to the chroot option.
278TEST_F(CliTest, valid_chroot) {
279 std::vector<std::string> argv = {"-C", "/", "/bin/sh"};
280 ASSERT_TRUE(parse_args_(argv));
281}
282
283// Valid calls to the pivot root option.
284TEST_F(CliTest, valid_pivot_root) {
285 std::vector<std::string> argv = {"-P", "/", "/bin/sh"};
286 ASSERT_TRUE(parse_args_(argv));
287}
288
289// We cannot handle multiple options with chroot/profile/pivot root.
290TEST_F(CliTest, conflicting_roots) {
291 std::vector<std::string> argv;
292
293 // Chroot & pivot root.
294 argv = {"-C", "/", "-P", "/", "/bin/sh"};
295 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
296
297 // Chroot & minimalistic-mountns profile.
298 argv = {"-C", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
299 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
300
301 // Pivot root & minimalistic-mountns profile.
302 argv = {"-P", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
303 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
304}
305
306// Valid calls to the uidmap option.
307TEST_F(CliTest, valid_uidmap) {
308 std::vector<std::string> argv = {"-m", "/bin/sh"};
309 // Use a default map (no option from user).
310 ASSERT_TRUE(parse_args_(argv));
311
312 // Use a single map.
313 argv = {"-m0 0 1", "/bin/sh"};
314 ASSERT_TRUE(parse_args_(argv));
315
316 // Multiple maps.
317 argv = {"-m0 0 1,100 100 1", "/bin/sh"};
318 ASSERT_TRUE(parse_args_(argv));
319}
320
321// Valid calls to the gidmap option.
322TEST_F(CliTest, valid_gidmap) {
323 std::vector<std::string> argv = {"-M", "/bin/sh"};
324 // Use a default map (no option from user).
325 ASSERT_TRUE(parse_args_(argv));
326
327 // Use a single map.
328 argv = {"-M0 0 1", "/bin/sh"};
329 ASSERT_TRUE(parse_args_(argv));
330
331 // Multiple maps.
332 argv = {"-M0 0 1,100 100 1", "/bin/sh"};
333 ASSERT_TRUE(parse_args_(argv));
334}
335
336// Invalid calls to the uidmap/gidmap options.
337// Note: Can't really test these as all validation is delayed/left to the
338// runtime kernel. Minijail will simply write verbatim what the user gave
339// it to the corresponding /proc/.../[ug]id_map.
340
341// Valid calls to the binding option.
342TEST_F(CliTest, valid_binding) {
343 std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
344
345 // Dest & writable are optional.
346 argv[1] = "/";
347 ASSERT_TRUE(parse_args_(argv));
348
349 // Writable is optional.
350 argv[1] = "/,/";
351 ASSERT_TRUE(parse_args_(argv));
352
353 // Writable is an integer.
354 argv[1] = "/,/,0";
355 ASSERT_TRUE(parse_args_(argv));
356 argv[1] = "/,/,1";
357 ASSERT_TRUE(parse_args_(argv));
358
359 // Dest is optional.
360 argv[1] = "/,,0";
361 ASSERT_TRUE(parse_args_(argv));
362}
363
364// Invalid calls to the binding option.
365TEST_F(CliTest, invalid_binding) {
366 std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
367
368 // Missing source.
369 argv[2] = "";
370 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
371
372 // Too many args.
373 argv[2] = "/,/,0,what";
374 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
375
376 // Missing mount namespace/etc...
377 argv = {"-b", "/", "/bin/sh"};
378 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
379}
380
381// Valid calls to the mount option.
382TEST_F(CliTest, valid_mount) {
383 std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
384
385 // Flags & data are optional.
386 argv[2] = "none,/,none";
387 ASSERT_TRUE(parse_args_(argv));
388
389 // Data is optional.
390 argv[2] = "none,/,none,0xe";
391 ASSERT_TRUE(parse_args_(argv));
392
393 // Flags are optional.
394 argv[2] = "none,/,none,,mode=755";
395 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4f3e09f2018-01-24 18:01:16 -0500396
397 // Multiple data options to the kernel.
398 argv[2] = "none,/,none,0xe,mode=755,uid=0,gid=10";
399 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500400}
401
402// Invalid calls to the mount option.
403TEST_F(CliTest, invalid_mount) {
404 std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
405
406 // Missing source.
407 argv[2] = "";
408 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
409
410 // Missing dest.
411 argv[2] = "none";
412 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
413
414 // Missing type.
415 argv[2] = "none,/";
416 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
417}
Mike Frysinger785b1c32018-02-23 15:47:24 -0500418
419// Valid calls to the remount mode option.
420TEST_F(CliTest, valid_remount_mode) {
421 std::vector<std::string> argv = {"-v", "", "/bin/sh"};
422
423 // Mode is optional.
424 argv[1] = "-K";
425 ASSERT_TRUE(parse_args_(argv));
426
427 // This should list all valid modes.
428 const std::vector<std::string> modes = {
429 "shared",
430 "private",
431 "slave",
432 "unbindable",
433 };
434
435 for (const auto& mode : modes) {
436 argv[1] = "-K" + mode;
437 ASSERT_TRUE(parse_args_(argv));
438 }
439}
440
441// Invalid calls to the remount mode option.
442TEST_F(CliTest, invalid_remount_mode) {
443 std::vector<std::string> argv = {"-v", "", "/bin/sh"};
444
445 // Unknown mode.
446 argv[1] = "-Kfoo";
447 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
448}